การเปรียบเทียบ UI เว็บธุรกิจเดียวกันที่ใช้งานโดยใช้ Vanilla HTML / Vue / React / Thymeleaf — ความแตกต่างระหว่าง 4 สแต็กและแนวทางการเลือก
สิ่งที่คุณสามารถเรียนรู้ได้จากบทความนี้
- อะไรคือความแตกต่างในโค้ดเมื่อใช้ Business Web UI ที่มีข้อกำหนดเดียวกันโดยใช้สี่สแต็ก: Vanilla HTML / Vue 3 / React / Thymeleaf (พร้อมตัวอย่างโค้ด)
- ความแตกต่างในการกำหนดค่า/บิลด์โปรเจ็กต์ (package.json / Vite / pom.xml / HTML ธรรมดา)
- การเปรียบเทียบตามมุมมอง ของไคลเอนต์ API การจัดการสถานะ การประมวลผลแบบฟอร์ม การแสดงข้อผิดพลาด และการปรับใช้
- แกนตัดสินว่า “จะเลือกกองไหนในสถานการณ์ไหน”
กลุ่มเป้าหมาย
- ผู้ที่สงสัยว่าควรใช้ Framework ใดระหว่าง Vanilla / Vue / React / Thymeleaf
- แม้ว่าคุณจะใช้ผลิตภัณฑ์แต่ละชนิดแยกกัน แต่คุณก็อยากทราบว่าเมื่อเลือกใช้ผลิตภัณฑ์ที่มีข้อมูลจำเพาะเหมือนกันจะมีความแตกต่างกันอย่างไร
- ผู้ที่ต้องการเปรียบเทียบสแต็กบนสถานที่ตั้งของการสร้าง UI ของเว็บที่เกี่ยวข้องกับธุรกิจ (แบบฟอร์ม + การเรียก API + การแสดงผลลัพธ์)
สภาพแวดล้อมการทำงาน
| รายการ | เวอร์ชั่น |
|---|---|
| วานิลลา HTML | ไม่จำเป็นต้องสร้าง (สคริปต์โมดูล) |
| วิว | 3.x + Vite 5.x + TypeScript 5.x + เราเตอร์ Vue 4.x |
| ตอบสนอง | 18.x + Vite 5.x + TypeScript 5.x + ตอบสนองเราเตอร์ 6.x |
| ไธม์ลีฟ | Spring Boot 3.x + Java 21 + Thymeleaf 3.x |
1. บทนำ
พูดตามตรง เดิมทีฉันไม่ได้ตั้งใจจะเขียนบทความเปรียบเทียบ
ในขณะที่พัฒนาแบ็กเอนด์สำหรับ banklink-service ฉันคิดว่า ฉันต้องการหน้าจอที่สามารถตรวจสอบได้ว่า API ทำงานหรือไม่'' ดังนั้นฉันจึงสุ่มรวบรวมมันโดยใช้ Vanilla HTML ซึ่งเป็นวิธีที่รวดเร็วที่สุดในการสร้างมันขึ้นมาทันที **มันเป็น UI แบบใช้แล้วทิ้งที่ดูเหมือนรูปภาพ "แค่ย้ายไปตอนนี้"**เมื่อดำเนินการพัฒนาต่อ ฉันคิดว่า ในที่สุดฉันก็จะแทนที่มันด้วย Vue หรือ React และทำให้มันเป็นเทมเพลตการพัฒนาที่ครบถ้วน” ฉันวางแผนที่จะเริ่มการใช้งานหลักของส่วนหน้าจากที่นั่น
**แต่ในขณะที่ฉันพยายามจะขยับมือ ฉันก็สังเกตเห็นอะไรบางอย่าง **
ด้วยการประมวลผลจำนวนนี้ (แบบฟอร์ม + การเรียก API + การแสดงผลลัพธ์) จะใช้เวลาไม่นานในการปรับใช้โดยใช้ทั้ง 4 สแต็ก รวมถึง Thymeleaf ด้วย ยิ่งกว่านั้นถ้าพวกมันมีฟังก์ชั่นเหมือนกัน จะเป็นเรื่องดีหรือไม่ที่จะเปรียบเทียบความแตกต่างในแต่ละเทคโนโลยีไปพร้อมๆ กัน? ** และ.
เท่าที่งานพัฒนาดำเนินไป นี่ถือเป็น เส้นทางที่ผิดทาง โดยสิ้นเชิง ก่อนที่ฉันจะรู้ แผนการของฉันในการ “สร้างเทมเพลตเดียว” กลายเป็น “การใช้งานแบบขนานกับ 4 สแต็กแล้วเปรียบเทียบกัน”
อย่างไรก็ตาม เมื่อฉันเขียนมันลงไป ฉันสามารถแยกแยะเกณฑ์การตัดสินใจในการเลือกเทคโนโลยีได้ด้วยตัวเอง และดูเหมือนว่าจะมีประโยชน์สำหรับผู้ที่ไม่แน่ใจเกี่ยวกับสถานการณ์เดียวกัน ดังนั้นฉันจึงตัดสินใจทิ้งไว้เป็นบทความ **บทความนี้เป็นผลพลอยได้จาก “การพูดนอกเรื่องสนุกๆ” **
ดังนั้นหัวข้อหลัก
เมื่อสร้าง UI ของเว็บที่เกี่ยวข้องกับธุรกิจ การตัดสินใจแรกที่คุณต้องทำคือ “Vanilla HTML / Vue / React / Thymeleaf ใดเป็นตัวเลือกที่ดีที่สุด” แต่ละคนมักถูกพูดถึงกันคนละเรื่อง แต่มีบทความไม่กี่บทความที่น่าประหลาดใจที่อธิบายว่า “อะไรคือความแตกต่างเมื่อคุณจัดเรียงสี่บทความที่มีสเปคเหมือนกัน? ด้วยรหัสจริง
ดังนั้นเราจึงใช้ UI ธุรกิจที่มีข้อกำหนดเดียวกันทุกประการโดยใช้ 4 สแต็กขนานกันโดยใช้บริการ Banking API Wrapper banklink-service (โครงการฝึกหัดส่วนบุคคล) บทความนี้เป็นบันทึกการเปรียบเทียบนั้น> 🔗 บทความที่เกี่ยวข้อง: การออกแบบแบ็กเอนด์ ของ banklink-service (คำจำกัดความข้อกำหนด → การออกแบบพื้นฐาน → การออกแบบโดยละเอียด → PoC, การตัดสินใจออกแบบโดเมน 5 รายการ, การใช้งาน DDD / ArchUnit / คุณภาพทางการเงิน) มีการสรุปไว้ในบทความแยกต่างหาก บทความนี้เป็น การเปรียบเทียบ 4 สแต็กที่ส่วนหน้า ที่เชื่อมต่อกับแบ็คเอนด์นั้น
จุดยืนของบทความนี้ นี่ไม่ใช่บทความที่นำเสนอ “คำตอบที่ถูกต้องเพียงข้อเดียว” จุดประสงค์คือเพื่อจัดเตรียมเอกสารสำหรับการเปรียบเทียบโค้ดที่เขียนใน 4 สแต็กสำหรับข้อกำหนดเดียวกัน และพิจารณาว่าโค้ดใดเหมาะสมกับโปรเจ็กต์ของคุณ
2. ข้อกำหนดทั่วไป (สมมติว่า 4 กองอยู่ในแนวเดียวกัน)
การใช้งานทั้ง 4 รายการตรงตามข้อกำหนดต่อไปนี้:
คุณสมบัติ
- หน้า 6: หน้าแรก / บัญชี / สินเชื่อ / สกุลเงินต่างประเทศ / การลงทุน / KYC
- ส่วนการทำงานของ API หลายส่วนในแต่ละหน้า (ตัวอย่าง: หน้าบัญชีมี 5 ส่วน: “การได้มาของรายการ, การได้มาซึ่งยอดคงเหลือ, การฝาก, การถอนเงิน, ประวัติการทำธุรกรรม”)
- ตั้งค่าโทเค็นการตรวจสอบสิทธิ์ API จากช่องป้อนข้อมูล โทเค็นผู้ถือ ที่ด้านบนของหน้าจอ
- การนำทางช่วยให้คุณสามารถกลับไปกลับมาระหว่างหน้าต่างๆ
- กดปุ่มเพื่อเข้าสู่ API และแสดงผลบนหน้าจอ
แบ็กเอนด์ที่จะเชื่อมต่อ
- Spring Boot API เดียวกัน (
/api/v1/accounts,/api/v1/loans, …) - การตอบสนองคือ JSON ข้อผิดพลาดจะรวมเป็นหนึ่งเดียวกับสถานะ HTTP + โครงสร้างเนื้อหา
จุดที่ไม่ตรงกัน (จุดแตกต่าง)
- รูปลักษณ์ CSS (ตั้งใจเปลี่ยนใน UI ภายนอก/UI ภายใน)
- การใช้งานภายใน (ตามลักษณะกรอบงาน)
กล่าวอีกนัยหนึ่ง เราได้สร้างฐานการเปรียบเทียบ “หน้าจอและฟังก์ชันเดียวกัน โดยมีเนื้อหาต่างกันเพียง 4 รายการเท่านั้น”
หน้าจอบัญชี (ด้านบน) มี 5 ส่วน: “รับรายการบัญชี รับยอด ฝาก ถอน และประวัติการทำธุรกรรม” หน้าจอเดียวกันและฟังก์ชันเดียวกันนี้ใช้งานแยกกันใน 4 สแต็ค เป็นเรื่องของการเปรียบเทียบในบทความนี้
3. ภาพรวมการกำหนดค่าสแต็ก 4
ก่อนอื่น มาดู “การกำหนดค่าขั้นต่ำ” แต่ละรายการกันก่อน
วานิลลา HTML
banklink-web-vanilla-html/
├─ index.html ← หน้าแรก
├─ บัญชี.html ← หน้าบัญชี
├─กู้ยืมเงิน.html ← หน้าเงินกู้
├─ ... (อีก 4 หน้า)
├─ common-external.js ← การจัดการโทเค็น + การรวมทั่วไป
└─ แบ่งปัน/
├─ api/client.js ← ดึงข้อมูล wrapper
└─ common.js ← bindAction / renderResponse
ไม่มีเครื่องมือสร้าง .html สามารถเปิดได้โดยตรงในเบราว์เซอร์ (หรือแจกจ่ายด้วย nginx) นำเข้า JS ด้วย <script type="module">
วิว````
banklink-เว็บ-vue/ ├─vite.config.ts ├─ index.html ← รายการสปา └─ src/ ├─ main.ts ← createApp + ภูเขา ├─ App.vue ← เค้าโครง + RouterView ├─ เราเตอร์/index.ts ← การตั้งค่าเราเตอร์ Vue ├─ api/client.ts ← ดึงข้อมูล wrapper (TypeScript) └─ จำนวนการดู/ ├─ HomeView.vue ├─ AccountsView.vue ← หน้าบัญชี ├─ … (อีก 4 หน้า)
สร้างไฟล์คงที่ใน `dist/` ด้วย `npm run build` → แจกจ่ายด้วย nginx
### โต้ตอบ
banklink-เว็บตอบสนอง/ ├─vite.config.ts ├─ index.html └─ src/ ├─ main.tsx ← createRoot + เรนเดอร์ ├─ App.tsx ← รวมทั้ง 6 หน้าเป็นไฟล์เดียว (เพราะมันเล็ก) └─ … (shared/api/client.ts)
คล้ายกับ Vue แต่ทุกหน้าเขียนด้วย `App.tsx` (ลดจำนวนส่วนประกอบให้เหลือน้อยที่สุด)
### ไธม์ลีฟ````
banklink-web-thymeleaf/
├─ pom.xml
└─ banklink-external-web-thymeleaf/
└─ src/หลัก/
├─ java/com/y104autumn/banklink/ภายนอก/
│ ├─ BanklinkExternalApplication.java
│ ├─ ตัวควบคุม/
│ │ ├─ ExternalTopPageController.java
│ │ ├─ AccountsController.java
│ │ └─ ... (อีก 4 หน้า)
│ ├─ บริการ/
│ │ ├─ AccountsService.java ← การเรียก API ด้วย RestClient
│ │ └─ ...
│ └─ แบบฟอร์ม/
│ ├─ AccountsForm.java ← สำหรับ @ModelAttribute
│ └─ ...
└─ ทรัพยากร/เทมเพลต/
├─ index.html
├─ account.html ← ด้วย th:field
└─ ...
สร้าง jar ที่สามารถดำเนินการได้ด้วย mvn package → เริ่มต้นด้วย java -jar หรือ Docker
การเปรียบเทียบจำนวนไฟล์การกำหนดค่า| | โครงการทั้งหมด | ไฟล์ที่จำเป็นสำหรับการใช้งานหน้าเดียว |
|---|---|---|
| วานิลลา HTML | ประมาณ 10 ไฟล์ | 1 (accounts.html เท่านั้น) |
| วิว | ประมาณ 15 ไฟล์ | 1 (AccountsView.vue) |
| ตอบสนอง | ประมาณ 5 ไฟล์ | 1 (ฟังก์ชั่นใน App.tsx) |
| ไธม์ลีฟ | ประมาณ 25 ไฟล์ | 3 (ตัวควบคุม + บริการ + แบบฟอร์ม + เทมเพลต) |
Thymeleaf มีโค้ดมาตรฐานมากมายสำหรับแบ่ง MVC ออกเป็นสามชั้น แม้ว่าจะมีจำนวนไฟล์มากที่สุด แต่บทบาทของแต่ละไฟล์ก็มีความชัดเจน
6 หน้าที่ใช้งาน (ภาพหน้าจออ้างอิง)
ทั้ง 5 หน้านอกเหนือจากหน้าจอบัญชีจะมีฟังก์ชันเหมือนกันในทั้ง 4 สแต็ค เพื่อเป็นข้อมูลอ้างอิง หน้าจอของเวอร์ชัน Vanilla HTML มีดังต่อไปนี้





การสร้างความแตกต่างด้วยภาพของ UI ภายนอกและ UI ภายใน
เราได้ใช้นโยบาย “ฟังก์ชันเหมือนกัน แต่รูปลักษณ์ภายนอกและ UI ภายในมีความแตกต่างกันโดยเจตนา” และ UI ภายใน (ใกล้กับเทอร์มินัลธุรกิจมากขึ้น) จะมีลักษณะเช่นนี้
การเปรียบเทียบในบทความนี้อิงตาม UI ภายนอก แต่แต่ละสแต็กใช้สองชุด: ภายนอก/ภายใน และโครงสร้างสามารถรองรับทั้งสองอย่างได้โดยแทนที่เฉพาะ CSS เท่านั้น
4. ความแตกต่างในการกำหนดค่า/การสร้างโปรเจ็กต์
Vanilla HTML — ไม่มีการตั้งค่าบิลด์
ทั้ง package.json หรือ tsconfig.json เพียงเปิดไฟล์โดยตรงในเบราว์เซอร์ของคุณหรือให้บริการแบบคงที่ด้วย nginx
# การพัฒนา: เปิดโดยตรงในเบราว์เซอร์
เปิดบัญชี.html
# การผลิต: วางในรูทเอกสาร nginx
nginx -c nginx-external.conf
ไม่มีแพ็คเกจที่ต้องพึ่งพา ไม่มีกระบวนการสร้าง ไม่มี node_modules
Vue — Vite + TypeScript
// package.json (ส่วนหลัก)
{
"สคริปต์": {
"dev": "vite",
"build": "vite build",
"ดูตัวอย่าง": "ดูตัวอย่าง"
},
"การอ้างอิง": {
"วิว": "^3.4.0",
"vue-เราเตอร์": "^4.3.0"
},
"devDependency": {
"@vitejs/plugin-vue": "^5.0.0",
"typescript": "^5.5.0",
"เยี่ยม": "^5.3.0",
"vue-tsc": "^2.0.0"
}
}
``````` ทุบตี
ติดตั้ง npm # สร้าง node_modules
การพัฒนารีโหลดร้อนแรงด้วย npm run dev # http://localhost:5173
รัน npm build # สร้างไฟล์คงที่ใน dist/
React — Vite + TypeScript (เหมือนกับ Vue, Vite)
{
"สคริปต์": {
"dev": "vite",
"build": "tsc && vite build"
},
"การอ้างอิง": {
"ตอบสนอง": "^18.3.0",
"react-dom": "^18.3.0",
"react-เราเตอร์-dom": "^6.24.0"
},
"devDependency": {
"@vitejs/plugin-react": "^4.3.0",
"typescript": "^5.5.0",
"เยี่ยม": "^5.3.0"
}
}
ความรู้สึกในการใช้งานเกือบจะเหมือนกับ Vue ข้อแตกต่างเพียงอย่างเดียวคือ @vitejs/plugin-vue กับ @vitejs/plugin-react
Thymeleaf — Maven + Spring Boot
<!-- pom.xml (ส่วนหลัก) -->
<ผู้ปกครอง>
<groupId>org.springframework.boot</groupId>
<artifactId>สปริงบูตสตาร์ทเตอร์พาเรนต์</artifactId>
<เวอร์ชั่น>3.4.5</เวอร์ชั่น>
</ผู้ปกครอง><การพึ่งพา>
<การพึ่งพา>
<groupId>org.springframework.boot</groupId>
<artifactId>สปริงบูตสตาร์ทเว็บ</artifactId>
</การพึ่งพา>
<การพึ่งพา>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</การพึ่งพา>
</การพึ่งพา>
mvn clean package # สร้างเป้าหมาย/*.jar
java -jar target/banklink-external-web-thymeleaf.jar # เริ่มต้น
ต้องใช้ JVM jar ที่สร้างโดย target/ มี Tomcat ฝังอยู่ด้วย ดังนั้นจึงไม่จำเป็นต้องใช้เซิร์ฟเวอร์แอปพลิเคชันเพิ่มเติม
เปรียบเทียบประสบการณ์เวลาสร้าง
| สแต็ค | เริ่มต้น install ฯลฯ | สร้างการผลิต | เวลาเริ่มต้น |
|---|---|---|---|
| วานิลลา HTML | 0 วินาที | 0 วินาที | ทันที |
| วิว | 30-60 วินาที (npm install) | 5-15 วินาที | นาทีเริ่มต้น nginx |
| ตอบสนอง | 30-60 วินาที | 5-15 วินาที | นาทีเริ่มต้น nginx |
| ไธม์ลีฟ | 30 ถึง 120 วินาที (DL ขึ้นอยู่กับ Maven) | 20 ถึง 40 วินาที (mvn package) | นาทีเริ่มต้น JVM (5 ถึง 15 วินาที) |
5. ใช้งานหน้าจอเดียวกัน (หน้าบัญชี) มี 4 สแต็ค
นี่คือจุดเน้นของบทความนี้ ลองดูโค้ดที่เขียนด้วย 4 วิธีที่แตกต่างกันสำหรับ 5 ส่วนที่เหมือนกัน: “การได้มาของรายการบัญชี, การได้มาของยอดคงเหลือ, การฝาก, การถอน, ประวัติการทำธุรกรรม” ตามลำดับ### เวอร์ชัน HTML วานิลลา
HTML และ JS อยู่ร่วมกันใน accounts.html เดียวกัน (โมดูลใน <script type="module">)
<!-- Accounts.html (ข้อความที่ตัดตอนมาจากรายการบัญชีและส่วนที่ฝาก) -->
<ส่วนชั้น="ส่วนบัตร">
<h2>รับรายการบัญชี</h2>
<button id="accounts-list-btn">รับ /api/v1/accounts</button>
<div id="บัญชีรายการตอบกลับ"></div>
</ส่วน>
<ส่วนชั้น="ส่วนบัตร">
<h2>ฝากเงิน</h2>
<label>accountId<input id="deposit-account-id" value="ACC-0001" /></label>
<label>จำนวน<input id="deposit-amount" type="number" value="10000" /></label>
<label>Idempotency-Key<input id="deposit-key" value="dep-key-001" /></label>
<button id="accounts-deposit-btn">โพสต์ /api/v1/accounts/{id}/deposit</button>
<div id="accounts-deposit-response"></div>
</ส่วน>
<ประเภทสคริปต์ = "โมดูล">
นำเข้า { bindAction, numberValue, ค่า } จาก "./common-external.js";// รหัสปุ่มการแมปและรหัสปลายทางแสดงการตอบสนอง
bindAction("accounts-list-btn", "accounts-list-response", () => ({
วิธีการ: "รับ"
เส้นทาง: "/api/v1/accounts",
}));
bindAction("accounts-deposit-btn", "accounts-deposit-response", () => ({
วิธีการ: "โพสต์"
เส้นทาง: `/api/v1/accounts/${value("deposit-account-id")}/deposit`,
idempotencyKey: value("รหัสเงินฝาก"),
เนื้อความ: {
จำนวน: numberValue("เงินฝาก-จำนวน"),
สกุลเงิน: value("เงินฝาก-สกุลเงิน"),
การอ้างอิง: value("เงินฝาก-อ้างอิง"),
},
}));
</สคริปต์>
คุณสมบัติ:
- ระบุองค์ประกอบ HTML ด้วย
id→ อ้างอิงด้วยdocument.getElementByIdจาก JS (ทำในฟังก์ชันbindAction) - ค่าอินพุตจะได้รับในแต่ละครั้งโดยใช้ตัวช่วย
value("input-id")(อ่าน “ค่า ณ เวลาที่คลิก” แทนที่จะอ่านแบบโต้ตอบ) - ผลลัพธ์จะถูกวาดเป็นสตริงที่มี
innerHTML
เวอร์ชั่นวิว
<!-- AccountsView.vue (การตั้งค่าสคริปต์ + เทมเพลต) -->
<การตั้งค่าสคริปต์ lang="ts">
นำเข้า { ฉีด อ้างอิง } จาก "vue";
ประเภทการนำเข้า { อ้างอิง } จาก "vue";
นำเข้า { requestApi } จาก "../api/client";const token = inject<Ref<string>>("token"!;
ฟังก์ชั่น fmt (r: ไม่ทราบ) { กลับ JSON.stringify (r, null, 2); }
// รายการบัญชี
const listRes = ref<string>("");
const listStatus = อ้างอิง <หมายเลข | โมฆะ>(โมฆะ);
ฟังก์ชัน async getAccounts() {
const r = รอ requestApi ({ วิธีการ: "GET", เส้นทาง: "/api/v1/accounts", โทเค็น: token.value });
listStatus.value = r.สถานะ; listRes.value = fmt(r.body);
}//ฝาก
const depId = อ้างอิง ("ACC-0001"), depAmount = อ้างอิง (10,000)
depKey = ref("dep-key-001"), depCurrency = ref("JPY"), depRef = ref("TEST-DEP-001");
const depRes = อ้างอิง (""); const depStatus = อ้างอิง <หมายเลข | โมฆะ>(โมฆะ);
ฟังก์ชั่น async ฝาก () {
const r = รอ requestApi ({
วิธีการ: "โพสต์"
เส้นทาง: `/api/v1/accounts/${depId.value}/deposit`,
โทเค็น: token.value,
idempotencyKey: depKey.value,
body: { จำนวน: depAmount.value, สกุลเงิน: depCurrency.value, การอ้างอิง: depRef.value },
});
depStatus.value = r.สถานะ; depRes.value = fmt(r.body);
}
</สคริปต์>
<แม่แบบ>
<ส่วนชั้น="ส่วนบัตร">
<h2>รับรายการบัญชี</h2>
<button @click="getAccounts">รับ /api/v1/accounts</button>
<pre v-if="listStatus !== null">HTTP {{ listStatus }}\n{{ listRes }}</pre>
</ส่วน><ส่วนชั้น="ส่วนบัตร">
<h2>ฝากเงิน</h2>
<label>accountId<input v-model="depId" /></label>
<label>จำนวน<input v-model.number="depAmount" type="number" /></label>
<label>Idempotency-Key<input v-model="depKey" /></label>
<button @click="deposit">โพสต์ /api/v1/accounts/{id}/deposit</button>
<pre v-if="depStatus !== null">HTTP {{ depStatus }}\n{{ depRes }}</pre>
</ส่วน>
</แม่แบบ>
คุณสมบัติ:
- ประกาศ ตัวแปรปฏิกิริยา ด้วย
ref()การเชื่อมโยงสองทางด้วยv-model - รับโทเค็นที่แชร์จากผู้ปกครองด้วย
inject<Ref<string>>("token") - แสดงการตอบสนองสาขาแบบมีเงื่อนไขด้วย
v-ifฝังตัวแปรด้วย{{ }}
เวอร์ชั่นตอบโต้
// ฟังก์ชั่น AccountsPage ของ App.tsx (ข้อความที่ตัดตอนมา)
ฟังก์ชั่น AccountsPage ({ โทเค็น }: PageProps) {
const [listResponse, setListResponse] = useState<ApiResult | โมฆะ>(โมฆะ);
const [depositResponse, setDepositResponse] = useState<ApiResult | โมฆะ>(โมฆะ);const [depositAccountId, setDepositAccountId] = useState("ACC-0001");
const [depositAmount, setDepositAmount] = useState("10,000");
const [depositKey, setDepositKey] = useState("dep-key-001");
const [depositCurrency, setDepositCurrency] = useState("JPY");
const [depositReference, setDepositReference] = useState("TEST-DEP-001");
กลับ (
<div className="หน้าตาราง">
<Section title="การได้มาของรายการบัญชี">
<ปุ่ม
onClick={async () =>
setListResponse(รอ requestApi ({ วิธีการ: "GET", เส้นทาง: "/api/v1/accounts", โทเค็น }))
}
>
รับ /api/v1/accounts.php
</ปุ่ม>
<ResponsePanel การตอบสนอง={listResponse} />
</ส่วน><ชื่อหัวข้อ = "เงินฝาก">
<label>รหัสบัญชี
<input value={depositAccountId} onChange={e => setDepositAccountId(e.target.value)} />
</ฉลาก>
<ฉลาก>จำนวน
<input type="number" value={depositAmount} onChange={e => setDepositAmount(e.target.value)} />
</ฉลาก>
<label>Idempotency-Key
<input value={depositKey} onChange={e => setDepositKey(e.target.value)} />
</ฉลาก>
<ปุ่ม
onClick={async () =>
setDepositResponse (รอคำขอ API ({
วิธีการ: "โพสต์"
เส้นทาง: `/api/v1/accounts/${depositAccountId}/deposit`,
โทเค็น,
idempotencyKey: เงินฝากคีย์,
เนื้อความ: {
จำนวน: หมายเลข (จำนวนเงินฝาก)
สกุลเงิน: เงินฝากสกุลเงิน,
อ้างอิง: อ้างอิงเงินฝาก,
},
}))
}
>
POST /api/v1/accounts/{"{id}"}/deposit
</ปุ่ม>
<ResponsePanel การตอบสนอง={depositResponse} />
</ส่วน>
</div>
);
}
````**คุณสมบัติ**:
- ประกาศสถานะด้วย `useState` สำหรับแต่ละช่องอินพุตและแต่ละคำตอบ (**ประกาศมากกว่า `ref` ของ Vue**)
- คุณต้องเขียนตัวจัดการเหตุการณ์สำหรับ `onChange={e => setX(e.target.value)}` ทุกครั้ง (ไม่มีไวยากรณ์น้ำตาลเช่น `v-model` ใน Vue)
- คุณสามารถเขียนตัวจัดการ async สำหรับปุ่มได้โดยตรงภายใน `JSX`
### เวอร์ชั่นไทม์ลีฟ
โครงสร้าง 3 ชั้น: เทมเพลต + ตัวควบคุม + บริการ + วัตถุแบบฟอร์ม
```html
<!-- Accounts.html (ข้อความที่ตัดตอนมาจากรายการบัญชีและส่วนที่ฝาก) -->
<ส่วนชั้น="ส่วนบัตร">
<h2>การได้มาของรายการบัญชี (แบบฟอร์มฝั่งเซิร์ฟเวอร์)</h2>
<form id="accounts-form" th:action="@{/api/v1/accounts}" th:object="${accountsForm}" method="โพสต์">
<input type="hidden" th:field="*{authorization}" />
<button type="submit" class="action-button">โพสต์ /api/v1/accounts</button>
</แบบฟอร์ม>
<div th:if="${accountsForm != null และ accountForm.apiResponse != null}" class="response-box">
<p><strong>สถานะ:</strong> <span th:text="${accountsForm.apiResponse.statusCode}">0</span></p>
<pre th:text="${accountsForm.apiResponse.body}"></pre>
</div>
</ส่วน><ส่วนชั้น="ส่วนบัตร">
<h2>ฝากเงิน</h2>
<form th:action="@{/api/v1/accounts/deposit}" th:object="${accountsForm}" method="โพสต์">
<input type="hidden" th:field="*{authorization}" />
<label>accountId<input th:field="*{depositAccountId}" /></label>
<label>จำนวน<input th:field="*{depositAmount}" type="number" /></label>
<label>Idempotency-Key<input th:field="*{depositIdempotencyKey}" /></label>
<button type="submit" class="action-button">โพสต์ /api/v1/accounts/deposit</button>
</แบบฟอร์ม>
</ส่วน>
// AccountsController.java
@คอนโทรลเลอร์
@RequiredArgsConstructor
AccountsController ระดับสาธารณะ {
บัญชีส่วนตัวขั้นสุดท้ายบัญชีบริการบริการ;@PostMapping("/api/v1/accounts")
รายการสตริงสาธารณะบัญชี (
@ModelAttribute("accountsForm") แบบฟอร์มบัญชี
รุ่น รุ่น) {
ApplyToken(form.getAuthorization());
form.setApiResponse(accountsService.listAccounts());
model.addAttribute("รูปแบบบัญชี", แบบฟอร์ม);
ส่งคืน "บัญชี"; // แสดงผล account.html อีกครั้ง
}@PostMapping("/api/v1/accounts/deposit")
ฝากสตริงสาธารณะ(
@ModelAttribute("accountsForm") แบบฟอร์มบัญชี
รุ่น รุ่น) {
ApplyToken(form.getAuthorization());
แผนที่ <String, Object> body = Map.of(
"จำนวน", form.getDepositAmount(),
"สกุลเงิน", form.getDepositCurrency(),
"ข้อมูลอ้างอิง", form.getDepositReference()
);
form.setApiResponse(accountsService.deposit(
form.getDepositAccountId(), เนื้อความ, form.getDepositIdempotencyKey()));
model.addAttribute("รูปแบบบัญชี", แบบฟอร์ม);
ส่งคืน "บัญชี";
}
}
```````จาวา
// AccountsForm.java (ข้อความที่ตัดตอนมา)
@ข้อมูล
แบบฟอร์มบัญชีคลาสสาธารณะ {
การอนุญาตสตริงส่วนตัว
สตริงส่วนตัวDepositAccountId;
จำนวนเงินฝากส่วนตัวจำนวนเต็ม;
เงินฝากสตริงส่วนตัวสกุลเงิน;
การอ้างอิงการฝากสตริงส่วนตัว;
สตริงส่วนตัวฝากIdempotencyKey;
apiResponse ภายนอกส่วนตัว apiResponse;
// ... (ช่องอื่นๆ)
}
คุณสมบัติ:
- โฟลว์เว็บแบบดั้งเดิมของ HTTP POST → การประมวลผลของเซิร์ฟเวอร์ → การวาดหน้าใหม่ สำหรับการดำเนินการแต่ละครั้ง
- การเชื่อมโยงสองทางระหว่างฟิลด์ Form Object และอินพุตโดยใช้
th:field="*{depositAmount}"(Spring ส่งค่าโดยอัตโนมัติ) - ผลลัพธ์ยังแสดงผลเป็น HTML บนฝั่งเซิร์ฟเวอร์ → JS บนฝั่งไคลเอ็นต์นั้นมีน้อยมาก
เปรียบเทียบจำนวนบรรทัดโค้ด (ทั้งหน้าบัญชี)
| บรรทัดของรหัส | ภาษา/ไฟล์ | |
|---|---|---|
| วานิลลา HTML | ประมาณ 130 บรรทัด | HTML + JS (1 ไฟล์) |
| วิว | ประมาณ 130 บรรทัด | TypeScript + เทมเพลต (1 ไฟล์) |
| ตอบสนอง | ประมาณ 350 บรรทัด | TypeScript JSX (1 ฟังก์ชัน) |
| ไธม์ลีฟ | ประมาณ 250 บรรทัด | HTML + Java (กระจายมากกว่า 5 ไฟล์) |
6. ความแตกต่างระหว่างไคลเอนต์ API
พวกเขาทั้งหมดเข้าถึง API แบ็กเอนด์เดียวกัน (/api/v1/accounts ฯลฯ) แต่การใช้งานไคลเอนต์แตกต่างกันเล็กน้อย
วานิลลา HTML / Vue / React — JavaScript fetch
// Vue เวอร์ชัน client.ts
ส่งออกอินเทอร์เฟซ ApiResult {
สถานะ: หมายเลข;
ร่างกาย: ไม่ทราบ;
}
ส่งออกฟังก์ชัน async requestApi (ตัวเลือก: {
วิธีการ: สตริง;
เส้นทาง: สตริง;
โทเค็น: สตริง;
idempotencyKey?: สตริง;
ร่างกาย?: ไม่ทราบ;
}): สัญญา<ApiResult> {
ส่วนหัว const: บันทึก<string, string> = {
"Content-Type": "application/json",
การอนุญาต: `Bearer ${opts.token}`,
};
ถ้า (opts.idempotencyKey) {
ส่วนหัว ["Idempotency-Key"] = opts.idempotencyKey;
}ลอง {
const res = รอการดึงข้อมูล (opts.path, {
วิธีการ: opts.method,
ส่วนหัว,
เนื้อความ: opts.body !== ไม่ได้กำหนด ? JSON.stringify(opts.body) : ไม่ได้กำหนด,
});
const text = รอ res.text();
const responseBody = ข้อความ ? JSON.parse(ข้อความ) : null;
กลับ { สถานะ: res.status ร่างกาย: responseBody };
} จับ (e) {
กลับ { สถานะ: 0, เนื้อความ: สตริง (e) };
}
}
เกือบจะเหมือนกัน ใน Vanilla/Vue/React ข้อแตกต่างเพียงอย่างเดียวคือการมีหรือไม่มีคำอธิบายประกอบประเภท (วานิลลาคือ .js, Vue / React คือ .ts)
Thymeleaf — รองเท้าบูทสปริง RestClient
// AccountsService.java
@บริการ
@RequiredArgsConstructor
AccountsService ระดับสาธารณะ {
RestClient สุดท้ายส่วนตัว restClient;
โทเค็นสตริงส่วนตัวรายการบัญชี ExternalApiResponse สาธารณะ () {
ResponseEntity<String> การตอบสนอง = restClient
.get()
.uri("/api/v1/accounts")
.header("การอนุญาต", "ผู้ถือ" + โทเค็น)
.ดึง()
.toEntity(String.คลาส);
ส่งคืน ExternalApiResponse ใหม่ (
response.getStatusCode().value()
จริง,
การตอบสนองgetBody()
);
}
}
สัญกรณ์ตัวสร้างใน Java RestClient (Spring 6.1+) เมื่อเทียบกับ fetch ประเภทจะเข้มงวดกว่าและการทำให้ IDE สมบูรณ์มีประสิทธิภาพมากกว่า
ความเหมือนและความแตกต่าง
| มุมมอง | วานิลลา/วิว/รีแอค | ไธม์ลีฟ |
|---|---|---|
| ภาษา | จาวาสคริปต์ / TypeScript | ชวา |
| ไคลเอ็นต์ HTTP | fetch (มาตรฐาน) | RestClient (สปริง สแตนดาร์ด) |
| การจัดการข้อผิดพลาด | ลอง/จับ + รหัสสถานะ | ลอง/จับ + HttpClientErrorException |
| พิมพ์ความปลอดภัย | พิมพ์คำอธิบายประกอบใน TypeScript | พิมพ์ ความปลอดภัย (ค่าเริ่มต้น) ใน Java |
| ตำแหน่งที่เกิดเครือข่าย | เบราว์เซอร์ → API | เซิร์ฟเวอร์ → API |
- สำหรับระบบ SPA (Vanilla/Vue/React) เข้าถึง API ได้โดยตรงจากเบราว์เซอร์ → จำเป็นต้องมีการตั้งค่า CORS / ข้อมูลการตรวจสอบสิทธิ์ API ถูกส่งไปยังไคลเอนต์
- Thymeleaf อนุญาตให้เซิร์ฟเวอร์เข้าถึง API → ไม่จำเป็นต้องใช้ CORS / ข้อมูลการรับรองความถูกต้องถูกเก็บไว้ภายในเซิร์ฟเวอร์
7. ความแตกต่างระหว่างการจัดการสถานะและการผูกข้อมูล
ความเป็นเอกเทศของแต่ละสแต็กจะถูกกำหนดโดยวิธีที่จะรักษา “ค่าอินพุตบนหน้าจอ การตอบสนองที่ได้รับ และโทเค็น”
Vanilla HTML — DOM เป็นแหล่งที่มาของสถานะ
// ค่าอินพุต: ดึงข้อมูลจาก DOM ได้ตลอดเวลา
const accountId = document.getElementById("balance-account-id").value;
// แสดงการตอบสนอง: เขียนสตริงโดยตรงใน innerHTML
document.getElementById("สมดุลตอบสนอง").innerHTML = formatResponse(ผลลัพธ์);
// โทเค็น: บันทึกลงใน localStorage
localStorage.setItem("banklink_token", โทเค็น);
“รัฐ” ไม่มีแนวคิด อ่านค่า DOM หรือ localStorage เสมอ มันง่าย แต่เมื่อแอปเติบโตขึ้น การซิงโครไนซ์ก็จะกลายเป็นเรื่องยาก
Vue — การประกาศเชิงโต้ตอบด้วย ref
const balanceId = อ้างอิง ("ACC-0001"); // ตัวแปรปฏิกิริยาพร้อมค่าสตริง
const balanceRes = อ้างอิง (""); // เขียน {{ balanceRes }} ในเทมเพลตเพื่ออัปเดตอัตโนมัติ// หากคุณเขียน <input v-model="balanceId" /> ที่ด้านเทมเพลต
// การป้อนข้อมูลของผู้ใช้จะสะท้อนให้เห็นโดยอัตโนมัติใน balanceId.value (การผูกสองทาง)
โมเดล “การประกาศตัวแปรรีแอกทีฟ → เทมเพลตจะตามมาโดยอัตโนมัติ” ผู้เขียนไม่จำเป็นต้องตระหนักถึงการซิงโครไนซ์สถานะ
React — ประกาศสถานะ hooks ด้วย useState
const [balanceId, setBalanceId] = useState("ACC-0001");
const [balanceRes, setBalanceRes] = useState<ApiResult | โมฆะ>(โมฆะ);
// ด้านเทมเพลต: ส่งค่าและตัวจัดการแยกกัน
<input value={balanceId} onChange={e => setBalanceId(e.target.value)} />
“การประกาศคู่ตัวตั้งค่าตัวแปร → อัปเดตผ่านตัวตั้งค่า → แสดงผลซ้ำ” โมเดล ไม่มีไวยากรณ์เหมือน v-model ใน Vue ดังนั้นคุณต้องเขียน onChange สำหรับแต่ละฟิลด์อินพุต ซึ่งจะเพิ่มจำนวนโค้ด
Thymeleaf — สถานะรวมบนฝั่งเซิร์ฟเวอร์ด้วย Form Object
@ข้อมูล
แบบฟอร์มบัญชีคลาสสาธารณะ {
การอนุญาตสตริงส่วนตัว
สตริงส่วนตัว balanceAccountId;
apiResponse ภายนอกส่วนตัว apiResponse;
// ...สาขาอื่นๆ
}
<input th:field="*{balanceAccountId}" />
````**Form Object บนฝั่งเซิร์ฟเวอร์คือ "สำเนาต้นฉบับของสถานะ"** ทุกครั้งที่มี POST ค่าอินพุตจะถูกบรรจุลงใน Form Object และผู้ควบคุมจะประมวลผล → ผลลัพธ์จะถูกบรรจุลงใน Form Object และวาดใหม่ โมเดลเว็บแบบคลาสสิกที่ไม่มีสถานะในฝั่งไคลเอ็นต์
### ไม่เหมาะกับแต่ละฉาก
| ลักษณะของแอป | สแต็คโดยตรง |
|---|---|
| จอเล็ก 1 จอ แทบไม่มีสภาพ | **วานิลลา HTML** |
| การใช้งาน Reactive UI อย่างหนัก หลายรูปแบบ | **วิว** |
| การใช้ส่วนประกอบซ้ำสูง การมุ่งเน้นระบบนิเวศ | **โต้ตอบ** |
| ฉันต้องการเก็บสถานะไว้ที่ฝั่งเซิร์ฟเวอร์ (ระบบธุรกิจ) | **ไธม์ลีฟ** |
---
## 8. ความแตกต่างในการประมวลผลแบบฟอร์ม
วิธีใช้แบบฟอร์มการฝากเงิน (5 ช่อง: accountId / amount / currency / Reference / Idempotency-Key)
### วานิลลา HTML
```จาวาสคริปต์
// ระบุ ID ให้กับแต่ละอินพุต
<input id="deposit-account-id" value="ACC-0001" />
// อ่านค่าทั้งหมดพร้อมกันเมื่อคลิก
ร่างกาย const = {
accountId: document.getElementById("deposit-account-id").value,
จำนวน: Number(document.getElementById("deposit-amount").value)
// ...
};
คุณสามารถลดการซ้ำซ้อนนี้ได้โดยการเขียนฟังก์ชันตัวช่วย value() แต่แนวคิดของ “รูปแบบ” นั้นมีอยู่ในฝั่ง HTML/JS เท่านั้น
Vue — v-model รับเพียง 5 บรรทัดเท่านั้น```วิว
แปลงเป็นประเภทตัวเลขอัตโนมัติด้วย `v-model.number` **จำนวนการเขียนน้อยที่สุด**
### React — useState + onChange สำหรับแต่ละฟิลด์
``` ซ
const [depositAccountId, setDepositAccountId] = useState("ACC-0001");
const [depositAmount, setDepositAmount] = useState("10,000");
const [depositCurrency, setDepositCurrency] = useState("JPY");
// ... สำหรับแต่ละฟิลด์
<input value={depositAccountId} onChange={e => setDepositAccountId(e.target.value)} />
<input type="number" value={depositAmount} onChange={e => setDepositAmount(e.target.value)} />
// ...
5 ช่อง = useState 5 ครั้ง + onChange 5 ครั้ง สิ่งนี้สามารถละเว้นได้ด้วยไลบรารีภายนอก เช่น react-hook-form แต่ในบทความนี้ เราจะใช้ “plain React” เพื่อเปรียบเทียบ### Thymeleaf — ผสมผสานอัตโนมัติกับ Form Object ด้วย th:field
<form th:action="@{/api/v1/accounts/deposit}" th:object="${accountsForm}" method="โพสต์">
<input type="hidden" th:field="*{authorization}" />
<label>accountId<input th:field="*{depositAccountId}" /></label>
<label>จำนวน<input th:field="*{depositAmount}" type="number" /></label>
<label>Idempotency-Key<input th:field="*{depositIdempotencyKey}" /></label>
<button type="submit">ส่ง</button>
</แบบฟอร์ม>
th:field ตั้งค่า “แอตทริบิวต์ชื่อ คุณลักษณะ id และแอตทริบิวต์ค่า” โดยอัตโนมัติด้วยแอตทริบิวต์เดียว และเชื่อมต่อ Form Object และฟิลด์บนฝั่งเซิร์ฟเวอร์ ประสบการณ์การเขียนคล้ายกับ v-model ของ Vue
9. ความแตกต่างในการแสดงข้อผิดพลาดในการโหลด
แสดงรูปแบบเมื่อ API ล้มเหลว
วานิลลา HTML```จาวาสคริปต์
ฟังก์ชั่น renderResponse (targetId, การตอบสนอง) {
เป้าหมาย const = document.getElementById(targetId);
const badgeClass = response.status >= 200 && response.status < 300 ? “ตกลง” : “ผิดพลาด”;
target.innerHTML = <div class="response-panel"> <span class="badge ${badgeClass}">HTTP ${response.status}</span> <pre>${escapeHtml(JSON.stringify(response.body, null, 2))}</pre> </div> ;
}
**ต้องใช้การหลบหนีด้วยตนเอง** `escapeHtml` เตรียมฟังก์ชั่นของคุณเองหรือใช้ `textContent`
### Vue / React - การหลบหนีอัตโนมัติ + การเรนเดอร์แบบมีเงื่อนไข
```วิว
<!-- วิว -->
<div v-if="สถานะ !== null" class="response-panel">
<span :class="status < 400 ? 'badge-ok' : 'badge-err'">HTTP {{ สถานะ }}</span>
<pre>{{ ตอบกลับ }}</pre>
</div>
``````` ซ
//โต้ตอบ
{ตอบกลับ && (
<div className="แผงตอบกลับ">
<span className={response.status < 400 ? "badge-ok" : "badge-err"}>HTTP {response.status}</span>
<pre>{JSON.stringify(response.body, null, 2)}</pre>
</div>
)}
หากคุณฝังค่าด้วย {{ }} หรือ {} ค่านั้นจะถูก ใช้ Escape อัตโนมัติ ไม่จำเป็นต้องตระหนักถึงมาตรการรับมือ XSS
Thymeleaf — หลบหนีอัตโนมัติด้วย th:text
<div th:if="${accountsForm.apiResponse != null}" class="response-box">
<p>สถานะ: <span th:text="${accountsForm.apiResponse.statusCode}"></span></p>
<pre th:text="${accountsForm.apiResponse.body}"></pre>
</div>
th:text จะถูก Escape โดยอัตโนมัติเช่นกัน th:utext ทำให้เกิด unescape (ความเสี่ยง XSS)
10. ความแตกต่างในการกำหนดค่าการเริ่มต้น/ปรับใช้
Vanilla HTML — การจัดส่งแบบคงที่ด้วย nginx
# nginx-external.conf
เซิร์ฟเวอร์ {
ฟัง 8080;
รูท /app/banklink-external-web-vanilla-html;
ดัชนี index.html;
ตำแหน่ง /api/ {
proxy_pass http://banklink-api:8080; # API เฮลิบาโปร
}
}
```````นักเทียบท่าไฟล์
จาก nginx:alpine
คัดลอก banklink-web-vanilla-html /app/
คัดลอก nginx-external.conf /etc/nginx/conf.d/default.conf
การกำหนดค่าขั้นต่ำ ส่ง HTML/JS/CSS ตามที่เป็นอยู่
Vue / React — Vite build → การกระจาย nginx
#ขั้นตอนการสร้าง
จากโหนด: ตัวสร้าง AS 20-อัลไพน์
WORKDIR /แอป
คัดลอกแพ็คเกจ*.json ./
วิ่ง npm ci
คัดลอก
RUN npm run build # สร้าง dist/
#เวทีเสิร์ฟ
จาก nginx:alpine
คัดลอก --from=builder /app/dist /usr/share/nginx/html
คัดลอก nginx-external.conf /etc/nginx/conf.d/default.conf
ปรับใช้บิวด์อาร์ติแฟกต์กับ nginx ข้อแตกต่างเพียงอย่างเดียวจาก Vanilla คือการเพิ่มขั้นตอนการสร้าง
Thymeleaf - โถปฏิบัติการ Spring Boot
จาก eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /แอป
คัดลอก pom.xml
คัดลอก src ./src
เรียกใช้แพ็คเกจ mvn ใหม่ -DskipTests
จากคราส-เทมูริน:21-jre-อัลไพน์
คัดลอก --from=builder /app/target/*.jar app.jar
จุดเข้า ["java", "-jar", "/app.jar"]
จำเป็นต้องมี JVM ขนาดคอนเทนเนอร์คือหลายสิบ MB + JVM (ประมาณ 200 MB)
เปรียบเทียบขนาดภาพ| | ขนาดอิมเมจคอนเทนเนอร์ | เวลาเริ่มต้น |
|---|---|---| | วานิลลา HTML (nginx) | ~25 เมกะไบต์ | < 1 วินาที | | Vue (การส่ง nginx) | ~30 เมกะไบต์ | < 1 วินาที | | ตอบสนอง (การส่ง nginx) | ~30 เมกะไบต์ | < 1 วินาที | | Thymeleaf (สปริงบูท + JVM) | ~200 เมกะไบต์ | 5-15 วินาที |
หากคุณให้ความสำคัญกับการลดน้ำหนัก ระบบการกระจาย nginx (สามระบบก่อนหน้านี้) จะเป็นประโยชน์ แม้ว่า Spring Boot จะมีน้ำหนักมาก แต่ก็มีจุดแข็งคือสามารถจัดการตรรกะฝั่งเซิร์ฟเวอร์ พร็อกซี API การรวมการรับรองความถูกต้อง ฯลฯ ในกระบวนการเดียวกันได้
11. ตารางเปรียบเทียบแนวนอน| มุมมอง | วานิลลา HTML | วิว 3 | ตอบสนอง | ไธม์ลีฟ |
|---|---|---|---|---|
| ภาษา | เจเอส | ทีเอส | TS (TSX) | ชวา |
| สร้าง | ไม่จำเป็น | เยี่ยม | เยี่ยม | มาเวน |
| ค่าเล่าเรียน | ต่ำ | ปานกลาง | ปานกลางถึงสูง | ปานกลาง (ต่ำหากคุณได้เรียนรู้ Java แล้ว) |
| จำนวนบรรทัดรหัส (หน้าบัญชี) | ประมาณ 130 บรรทัด | ประมาณ 130 บรรทัด | ประมาณ 350 เส้น | ประมาณ 250 บรรทัด (กระจาย) |
| รูปแบบการบริหารจัดการของรัฐ | โดม | การอ้างอิงเชิงโต้ตอบ | ใช้สถานะ | แบบฟอร์มวัตถุ (เซิร์ฟเวอร์) |
| แบบฟอร์มเข้าร่วม | คู่มือ | v-model | onChange บุคคล | th:field |
| XSS หลบหนีอัตโนมัติ | คู่มือ | อัตโนมัติ | อัตโนมัติ | อัตโนมัติ |
| ตำแหน่งการเรียก API | เบราว์เซอร์ | เบราว์เซอร์ | เบราว์เซอร์ | เซิร์ฟเวอร์ |
| จะค้นหาข้อมูลรับรองของคุณได้ที่ไหน | พื้นที่เก็บข้อมูลท้องถิ่น | พื้นที่เก็บข้อมูลท้องถิ่น | พื้นที่เก็บข้อมูลท้องถิ่น | เซสชันเซิร์ฟเวอร์ |
| ต้องใช้ CORS | ใช่ | ใช่ | ใช่ | ไม่ |
| จำนวนแพ็คเกจที่ต้องพึ่งพา | 0 | ~10 | ~10 | ~20 (มาเวน) |
| ภาพคอนเทนเนอร์ | ~25 เมกะไบต์ | ~30 เมกะไบต์ | ~30 เมกะไบต์ | ~200 เมกะไบต์ |
| เวลาเริ่มต้น | ทันที | ทันที | ทันที | 5-15 วินาที (JVM) |
| ** UI ไดนามิก ** | อ่อนแอ | แข็งแกร่ง | แข็งแกร่ง | อ่อนแอ (ต้องโหลดซ้ำ) |
| ระบบนิเวศ | ไม่มี | ปานกลาง | ใหญ่โต | ระบบนิเวศฤดูใบไม้ผลิ |
12. วิธีเลือก — จุดแข็งและจุดอ่อนของ 4 สแต็คและฐานการตัดสินใจ
วานิลลา HTMLจุดแข็ง:
- การพึ่งพาเป็นศูนย์, การสร้างเป็นศูนย์, ค่าใช้จ่ายในการเรียนรู้เป็นศูนย์ (พื้นฐาน HTML/JS เท่านั้น)
- ค่าจัดส่งขั้นต่ำ/อิมเมจคอนเทนเนอร์ขั้นต่ำ
- ดีที่สุดสำหรับ “การสร้างการสาธิตการทำงานใน 30 นาที”
จุดอ่อน:
- การจัดการสถานะพังเมื่อแอปเติบโตขึ้น (ข้อจำกัดของการจัดการ DOM โดยตรง)
- ไม่มีความปลอดภัยประเภท TypeScript
- ฉันไม่ชอบ UI แบบโต้ตอบ
เลือกเวลา:
- เครื่องมือยืนยันการทำงานของ API, แบบฟอร์มการตรวจสอบภายใน, แดชบอร์ดที่เรียบง่าย
- “ฉันไม่ต้องการเฟรมเวิร์ก ฉันแค่ต้องการหน้าจอ”
- ต้นแบบจะถูกแทนที่ด้วย SPA ในภายหลัง
วิว
จุดแข็ง:
- น้ำตาลไวยากรณ์เช่น
v-modelโค้ดน้อยกว่า React - ไวยากรณ์เทมเพลตคล้ายกับ HTML และอ่านง่ายแม้สำหรับผู้เริ่มต้น
- ส่วนประกอบไฟล์เดี่ยว (.vue) ช่วยให้ตรรกะ/เทมเพลต/สไตล์พอดีกับไฟล์เดียว
จุดอ่อน:
- ระบบนิเวศเล็กกว่าเมื่อเทียบกับ React
- ด้อยกว่า React ทั้งในด้านจำนวนโครงการรับสมัครงานและทรัพยากรบุคคล
เลือกเวลา:
- Business SPA มีหลายรูปแบบ/อินพุต UI
- “หากคุณมีปัญหาในการเลือกเฟรมเวิร์ก ลองใช้ Vue”
- โครงการที่ตลาดทรัพยากรบุคคลของวิศวกรเว็บส่วนใหญ่อยู่ในญี่ปุ่น
โต้ตอบ
จุดแข็ง:
- ระบบนิเวศขนาดใหญ่ (ไลบรารี UI, การจัดการสถานะ, การทดสอบ, อุปกรณ์เคลื่อนที่)
- เข้ากันได้ดีกับ TypeScript (คำจำกัดความประเภทที่หลากหลาย)
- ตำแหน่งงานว่างส่วนใหญ่ในตลาดงาน
จุดอ่อน:
- มีแนวโน้มที่จะต้องใช้บรรทัดมากกว่า Vue ในการเขียนฟังก์ชันเดียวกัน
- ค่าใช้จ่ายในการเรียนรู้วิธีใช้
useState,useEffect,useMemo,useCallback - ต้องมีความเข้าใจเกี่ยวกับการเรนเดอร์องค์ประกอบของฟังก์ชันใหม่
เลือกเวลา:
- กำลังมองหาการเชื่อมต่อกับ SPA ขนาดใหญ่, Next.js / React Native
- องค์กรที่เน้นการจ้างงานวิศวกร
- ฉันต้องการนำไลบรารี UI มาใช้ซ้ำ (MUI / Mantine / shadcn ฯลฯ )### ไธม์ลีฟ
จุดแข็ง:
- JS ฝั่งไคลเอ็นต์สามารถย่อให้เล็กสุดได้ (ระบบธุรกิจ)
- ข้อมูลการตรวจสอบความถูกต้อง/โทเค็น API ถูก ปิดภายในเซิร์ฟเวอร์ (มีประโยชน์ในสถานการณ์ที่มีข้อกำหนดด้านความปลอดภัยที่เข้มงวด)
- ใช้ประโยชน์จากระบบนิเวศของ Spring Security / Spring Boot อย่างเต็มที่
- วิศวกร Java สามารถสร้าง UI ของเว็บโดยใช้ทักษะที่มีอยู่
จุดอ่อน:
- ต้องใช้เซิร์ฟเวอร์ไปกลับสำหรับการเปลี่ยนหน้าแต่ละครั้ง (เทียบกับ SPA)
- ไม่เหมาะสำหรับ UI แบบโต้ตอบ
- อิมเมจคอนเทนเนอร์ JVM มีน้ำหนักมาก
เลือกเวลา:
- หน้าจอระบบธุรกิจ/การจัดการ (สำหรับใช้ภายใน)
- ฉันไม่ต้องการเปิดเผยคีย์ API ให้กับลูกค้าเนื่องจากข้อกำหนดด้านความปลอดภัย
- องค์กรที่มีวิศวกร Java จำนวนมากได้นำ Spring Boot มาใช้แล้ว
- แทนที่จะเป็น UI ที่สมบูรณ์ “ส่งแบบฟอร์มและแสดงผลลัพธ์” ก็เพียงพอแล้ว
กระแสการตัดสินใจ
ไตรมาสที่ 1 ปฏิกิริยา UI จำเป็นหรือไม่?
├─ ไม่ (ส่งแบบฟอร์มและแสดงผลก็เพียงพอแล้ว)
│ ├─ ฉันต้องการรวมทางฝั่งเซิร์ฟเวอร์ → Thymeleaf
│ └─ ฉันต้องการทำให้มันมีน้ำหนักเบา → วานิลลา HTML
└─ ใช่ (ต้องใช้ UI แบบโต้ตอบ)
├─ มุ่งเน้นไปที่ระบบนิเวศและการสรรหาบุคลากร → ตอบสนอง
└─ ต้องการลดจำนวนโค้ด/ต้นทุนการเรียนรู้ต่ำ → Vue
13. สรุป/การเรียนรู้- 4 สแต็กไม่ใช่ “ถูกต้องและไม่ถูกต้อง” แต่ ข้อดีและข้อเสียจะเปลี่ยนแปลงขึ้นอยู่กับข้อกำหนดของโครงการ ความแตกต่างจะปรากฏชัดเจนเมื่อวางข้อมูลจำเพาะเดียวกันไว้เคียงข้างกัน
- ถ้าคุณดูแค่จำนวนบรรทัดของโค้ด Vanilla/Vue มีน้อยกว่า และ React มีมากกว่า อย่างไรก็ตาม React มากกว่าชดเชยในตลาดการสรรหาบุคลากรและระบบนิเวศ
- Thymeleaf ยังไม่ล้าสมัย — มักจะตรงตามข้อกำหนดด้านความปลอดภัยและการปฏิบัติงานของระบบธุรกิจ
- “เลือก Framework โดยทำงานย้อนหลังจากข้อกำหนด” เป็นข้อสรุป หากคุณเลือกบางอย่างตามกระแส คุณจะพบกับช่วงเวลาที่ยากลำบากในอีกไม่กี่ปีต่อมา
- หลังจากเรียกใช้การใช้งาน 4 แบบพร้อมกัน ฉันพบว่าความแตกต่างในเครื่องมือสร้าง (Vite กับ Maven เทียบกับไม่มี) สร้างความแตกต่างอย่างมากในประสบการณ์ในวันแรกของการเปิดตัวโครงการ ในขั้นตอนต้นแบบ ระบบ Vanilla/Vite มีความเร็วอย่างท่วมท้น
เราหวังว่าการเปรียบเทียบนี้จะเป็นประโยชน์เมื่อทำการเลือก