|
|
<!--
|
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
|
{
|
|
|
"original_hash": "32bd800759c3e943c38ad9ae6e1f51e0",
|
|
|
"translation_date": "2025-10-23T21:07:24+00:00",
|
|
|
"source_file": "7-bank-project/4-state-management/README.md",
|
|
|
"language_code": "th"
|
|
|
}
|
|
|
-->
|
|
|
# สร้างแอปธนาคาร ตอนที่ 4: แนวคิดการจัดการสถานะ
|
|
|
|
|
|
## แบบทดสอบก่อนเรียน
|
|
|
|
|
|
[แบบทดสอบก่อนเรียน](https://ff-quizzes.netlify.app/web/quiz/47)
|
|
|
|
|
|
## บทนำ
|
|
|
|
|
|
การจัดการสถานะเปรียบเสมือนระบบนำทางบนยาน Voyager – เมื่อทุกอย่างทำงานได้อย่างราบรื่น คุณแทบจะไม่รู้สึกถึงการมีอยู่ของมัน แต่เมื่อเกิดปัญหา มันจะกลายเป็นตัวแปรสำคัญที่ทำให้ยานไปถึงอวกาศระหว่างดวงดาวหรือหลงทางในอวกาศ ในการพัฒนาเว็บ สถานะหมายถึงทุกสิ่งที่แอปพลิเคชันของคุณต้องจดจำ เช่น สถานะการเข้าสู่ระบบของผู้ใช้ ข้อมูลในฟอร์ม ประวัติการนำทาง และสถานะชั่วคราวของอินเทอร์เฟซ
|
|
|
|
|
|
เมื่อแอปธนาคารของคุณพัฒนาจากฟอร์มเข้าสู่ระบบแบบง่าย ๆ ไปเป็นแอปพลิเคชันที่ซับซ้อนมากขึ้น คุณอาจพบกับความท้าทายทั่วไป เช่น การรีเฟรชหน้าแล้วผู้ใช้ถูกออกจากระบบโดยไม่คาดคิด การปิดเบราว์เซอร์แล้วความคืบหน้าทั้งหมดหายไป หรือการแก้ไขปัญหาโดยต้องค้นหาผ่านฟังก์ชันหลาย ๆ ตัวที่แก้ไขข้อมูลเดียวกันในวิธีที่ต่างกัน
|
|
|
|
|
|
นี่ไม่ใช่สัญญาณของการเขียนโค้ดที่ไม่ดี – แต่เป็นความเจ็บปวดที่เกิดขึ้นตามธรรมชาติเมื่อแอปพลิเคชันมีความซับซ้อนถึงระดับหนึ่ง นักพัฒนาทุกคนต้องเผชิญกับความท้าทายเหล่านี้เมื่อแอปพลิเคชันเปลี่ยนจาก "การพิสูจน์แนวคิด" ไปเป็น "พร้อมใช้งานจริง"
|
|
|
|
|
|
ในบทเรียนนี้ เราจะนำระบบการจัดการสถานะที่เป็นศูนย์กลางมาใช้เพื่อเปลี่ยนแอปธนาคารของคุณให้เป็นแอปพลิเคชันที่เชื่อถือได้และเป็นมืออาชีพ คุณจะได้เรียนรู้วิธีจัดการการไหลของข้อมูลอย่างคาดการณ์ได้ รักษาสถานะการเข้าสู่ระบบของผู้ใช้อย่างเหมาะสม และสร้างประสบการณ์ผู้ใช้ที่ราบรื่นซึ่งแอปพลิเคชันเว็บสมัยใหม่ต้องการ
|
|
|
|
|
|
## ความรู้พื้นฐานที่ต้องมี
|
|
|
|
|
|
ก่อนที่จะดำดิ่งสู่แนวคิดการจัดการสถานะ คุณต้องตั้งค่าพื้นฐานการพัฒนาและมีโครงสร้างแอปธนาคารของคุณพร้อม บทเรียนนี้สร้างขึ้นจากแนวคิดและโค้ดจากส่วนก่อนหน้าของซีรีส์นี้โดยตรง
|
|
|
|
|
|
**การตั้งค่าที่จำเป็น:**
|
|
|
- ทำบทเรียน [การดึงข้อมูล](../3-data/README.md) ให้เสร็จสมบูรณ์ - แอปของคุณควรโหลดและแสดงข้อมูลบัญชีได้สำเร็จ
|
|
|
- ติดตั้ง [Node.js](https://nodejs.org) บนระบบของคุณเพื่อรัน API ฝั่งเซิร์ฟเวอร์
|
|
|
- เริ่มต้น [API เซิร์ฟเวอร์](../api/README.md) ในเครื่องเพื่อจัดการการดำเนินการข้อมูลบัญชี
|
|
|
|
|
|
**การทดสอบสภาพแวดล้อมของคุณ:**
|
|
|
|
|
|
ตรวจสอบว่า API เซิร์ฟเวอร์ของคุณทำงานได้อย่างถูกต้องโดยการรันคำสั่งนี้ในเทอร์มินัล:
|
|
|
|
|
|
```sh
|
|
|
curl http://localhost:5000/api
|
|
|
# -> should return "Bank API v1.0.0" as a result
|
|
|
```
|
|
|
|
|
|
**สิ่งที่คำสั่งนี้ทำ:**
|
|
|
- **ส่ง**คำขอ GET ไปยัง API เซิร์ฟเวอร์ในเครื่องของคุณ
|
|
|
- **ทดสอบ**การเชื่อมต่อและตรวจสอบว่าเซิร์ฟเวอร์ตอบสนอง
|
|
|
- **ส่งคืน**ข้อมูลเวอร์ชัน API หากทุกอย่างทำงานได้อย่างถูกต้อง
|
|
|
|
|
|
---
|
|
|
|
|
|
## การวินิจฉัยปัญหาสถานะปัจจุบัน
|
|
|
|
|
|
เหมือนกับ Sherlock Holmes ที่ตรวจสอบสถานที่เกิดเหตุ เราต้องเข้าใจว่าเกิดอะไรขึ้นในโครงสร้างปัจจุบันของเราก่อนที่เราจะสามารถแก้ปัญหาการสูญเสียสถานะผู้ใช้ได้
|
|
|
|
|
|
มาทำการทดลองง่าย ๆ ที่เผยให้เห็นความท้าทายในการจัดการสถานะ:
|
|
|
|
|
|
**🧪 ลองทดสอบการวินิจฉัยนี้:**
|
|
|
1. เข้าสู่ระบบแอปธนาคารของคุณและไปที่แดชบอร์ด
|
|
|
2. รีเฟรชหน้าเบราว์เซอร์
|
|
|
3. สังเกตว่าเกิดอะไรขึ้นกับสถานะการเข้าสู่ระบบของคุณ
|
|
|
|
|
|
หากคุณถูกเปลี่ยนกลับไปที่หน้าจอเข้าสู่ระบบ คุณได้ค้นพบปัญหาคลาสสิกของการคงสถานะแล้ว พฤติกรรมนี้เกิดขึ้นเพราะการใช้งานปัจจุบันของเราจัดเก็บข้อมูลผู้ใช้ในตัวแปร JavaScript ที่รีเซ็ตทุกครั้งที่โหลดหน้าใหม่
|
|
|
|
|
|
**ปัญหาการใช้งานปัจจุบัน:**
|
|
|
|
|
|
ตัวแปร `account` แบบง่ายจาก [บทเรียนก่อนหน้า](../3-data/README.md) สร้างปัญหาสำคัญสามประการที่ส่งผลต่อทั้งประสบการณ์ผู้ใช้และการดูแลรักษาโค้ด:
|
|
|
|
|
|
| ปัญหา | สาเหตุทางเทคนิค | ผลกระทบต่อผู้ใช้ |
|
|
|
|---------|--------|----------------|
|
|
|
| **การสูญเสียเซสชัน** | การรีเฟรชหน้าล้างตัวแปร JavaScript | ผู้ใช้ต้องเข้าสู่ระบบใหม่บ่อยครั้ง |
|
|
|
| **การอัปเดตกระจัดกระจาย** | ฟังก์ชันหลายตัวแก้ไขสถานะโดยตรง | การแก้ไขข้อบกพร่องยากขึ้นเรื่อย ๆ |
|
|
|
| **การล้างข้อมูลไม่สมบูรณ์** | การออกจากระบบไม่ล้างการอ้างอิงสถานะทั้งหมด | ความกังวลด้านความปลอดภัยและความเป็นส่วนตัว |
|
|
|
|
|
|
**ความท้าทายด้านสถาปัตยกรรม:**
|
|
|
|
|
|
เหมือนกับการออกแบบห้องของ Titanic ที่ดูแข็งแกร่งจนกระทั่งห้องหลายห้องถูกน้ำท่วมพร้อมกัน การแก้ไขปัญหาเหล่านี้ทีละปัญหาไม่สามารถแก้ไขปัญหาสถาปัตยกรรมพื้นฐานได้ เราต้องการโซลูชันการจัดการสถานะที่ครอบคลุม
|
|
|
|
|
|
> 💡 **เรากำลังพยายามทำอะไรที่นี่?**
|
|
|
|
|
|
[การจัดการสถานะ](https://en.wikipedia.org/wiki/State_management) เป็นเรื่องของการแก้ปริศนาสองข้อพื้นฐาน:
|
|
|
|
|
|
1. **ข้อมูลของฉันอยู่ที่ไหน?**: การติดตามว่ามีข้อมูลอะไรและมาจากไหน
|
|
|
2. **ทุกคนเข้าใจตรงกันหรือไม่?**: การทำให้สิ่งที่ผู้ใช้เห็นตรงกับสิ่งที่เกิดขึ้นจริง
|
|
|
|
|
|
**แผนการของเรา:**
|
|
|
|
|
|
แทนที่จะไล่ตามปัญหา เราจะสร้างระบบ **การจัดการสถานะที่เป็นศูนย์กลาง** คิดว่ามันเหมือนกับการมีคนที่จัดระเบียบได้ดีคนหนึ่งที่ดูแลเรื่องสำคัญทั้งหมด:
|
|
|
|
|
|

|
|
|
|
|
|
**การทำความเข้าใจการไหลของข้อมูลนี้:**
|
|
|
- **รวมศูนย์**สถานะของแอปพลิเคชันทั้งหมดในที่เดียว
|
|
|
- **กำหนดเส้นทาง**การเปลี่ยนแปลงสถานะทั้งหมดผ่านฟังก์ชันที่ควบคุม
|
|
|
- **รับรอง**ว่า UI ยังคงสอดคล้องกับสถานะปัจจุบัน
|
|
|
- **ให้**รูปแบบที่ชัดเจนและคาดการณ์ได้สำหรับการจัดการข้อมูล
|
|
|
|
|
|
> 💡 **ข้อมูลเชิงลึกระดับมืออาชีพ**: บทเรียนนี้มุ่งเน้นไปที่แนวคิดพื้นฐาน สำหรับแอปพลิเคชันที่ซับซ้อนมากขึ้น ไลบรารีอย่าง [Redux](https://redux.js.org) มีฟีเจอร์การจัดการสถานะที่ก้าวหน้ามากขึ้น การเข้าใจหลักการพื้นฐานเหล่านี้จะช่วยให้คุณเชี่ยวชาญไลบรารีการจัดการสถานะใด ๆ
|
|
|
|
|
|
> ⚠️ **หัวข้อขั้นสูง**: เราจะไม่ครอบคลุมการอัปเดต UI อัตโนมัติที่เกิดจากการเปลี่ยนแปลงสถานะ เนื่องจากเกี่ยวข้องกับแนวคิด [การเขียนโปรแกรมเชิงปฏิกิริยา](https://en.wikipedia.org/wiki/Reactive_programming) ถือว่าเป็นขั้นตอนต่อไปที่ยอดเยี่ยมสำหรับการเรียนรู้ของคุณ!
|
|
|
|
|
|
### งาน: รวมโครงสร้างสถานะ
|
|
|
|
|
|
มาเริ่มเปลี่ยนการจัดการสถานะที่กระจัดกระจายของเราให้เป็นระบบรวมศูนย์กัน ขั้นตอนแรกนี้เป็นการสร้างรากฐานสำหรับการปรับปรุงทั้งหมดที่จะตามมา
|
|
|
|
|
|
**ขั้นตอนที่ 1: สร้างวัตถุสถานะรวมศูนย์**
|
|
|
|
|
|
แทนที่การประกาศ `account` แบบง่าย:
|
|
|
|
|
|
```js
|
|
|
let account = null;
|
|
|
```
|
|
|
|
|
|
ด้วยวัตถุสถานะที่มีโครงสร้าง:
|
|
|
|
|
|
```js
|
|
|
let state = {
|
|
|
account: null
|
|
|
};
|
|
|
```
|
|
|
|
|
|
**เหตุผลที่การเปลี่ยนแปลงนี้สำคัญ:**
|
|
|
- **รวมศูนย์**ข้อมูลแอปพลิเคชันทั้งหมดในที่เดียว
|
|
|
- **เตรียม**โครงสร้างสำหรับการเพิ่มคุณสมบัติสถานะเพิ่มเติมในภายหลัง
|
|
|
- **สร้าง**ขอบเขตที่ชัดเจนระหว่างสถานะและตัวแปรอื่น ๆ
|
|
|
- **สร้าง**รูปแบบที่สามารถขยายได้เมื่อแอปของคุณเติบโต
|
|
|
|
|
|
**ขั้นตอนที่ 2: อัปเดตรูปแบบการเข้าถึงสถานะ**
|
|
|
|
|
|
อัปเดตฟังก์ชันของคุณให้ใช้โครงสร้างสถานะใหม่:
|
|
|
|
|
|
**ในฟังก์ชัน `register()` และ `login()`**, แทนที่:
|
|
|
```js
|
|
|
account = ...
|
|
|
```
|
|
|
|
|
|
ด้วย:
|
|
|
```js
|
|
|
state.account = ...
|
|
|
```
|
|
|
|
|
|
**ในฟังก์ชัน `updateDashboard()`**, เพิ่มบรรทัดนี้ที่ด้านบน:
|
|
|
```js
|
|
|
const account = state.account;
|
|
|
```
|
|
|
|
|
|
**สิ่งที่การอัปเดตเหล่านี้ทำสำเร็จ:**
|
|
|
- **รักษา**ฟังก์ชันการทำงานที่มีอยู่ในขณะที่ปรับปรุงโครงสร้าง
|
|
|
- **เตรียม**โค้ดของคุณสำหรับการจัดการสถานะที่ซับซ้อนมากขึ้น
|
|
|
- **สร้าง**รูปแบบที่สอดคล้องกันสำหรับการเข้าถึงข้อมูลสถานะ
|
|
|
- **สร้าง**รากฐานสำหรับการอัปเดตสถานะรวมศูนย์
|
|
|
|
|
|
> 💡 **หมายเหตุ**: การปรับโครงสร้างนี้ไม่ได้แก้ปัญหาของเราในทันที แต่สร้างรากฐานที่จำเป็นสำหรับการปรับปรุงที่ทรงพลังที่จะตามมา!
|
|
|
|
|
|
## การดำเนินการอัปเดตสถานะที่ควบคุม
|
|
|
|
|
|
เมื่อสถานะของเรารวมศูนย์แล้ว ขั้นตอนต่อไปคือการสร้างกลไกที่ควบคุมสำหรับการแก้ไขข้อมูล วิธีนี้ช่วยให้การเปลี่ยนแปลงสถานะสามารถคาดการณ์ได้และแก้ไขข้อบกพร่องได้ง่ายขึ้น
|
|
|
|
|
|
หลักการสำคัญคล้ายกับการควบคุมการจราจรทางอากาศ: แทนที่จะอนุญาตให้ฟังก์ชันหลายตัวแก้ไขสถานะอย่างอิสระ เราจะส่งการเปลี่ยนแปลงทั้งหมดผ่านฟังก์ชันที่ควบคุมเดียว รูปแบบนี้ให้การดูแลที่ชัดเจนว่าเมื่อใดและอย่างไรที่ข้อมูลเปลี่ยนแปลง
|
|
|
|
|
|
**การจัดการสถานะที่ไม่เปลี่ยนแปลง:**
|
|
|
|
|
|
เราจะถือว่าวัตถุ `state` ของเราเป็น [*ไม่เปลี่ยนแปลง*](https://en.wikipedia.org/wiki/Immutable_object) หมายความว่าเราจะไม่แก้ไขมันโดยตรง แต่ละการเปลี่ยนแปลงจะสร้างวัตถุสถานะใหม่พร้อมข้อมูลที่อัปเดต
|
|
|
|
|
|
แม้ว่าวิธีนี้อาจดูเหมือนไม่มีประสิทธิภาพในตอนแรกเมื่อเทียบกับการแก้ไขโดยตรง แต่มันให้ข้อดีที่สำคัญสำหรับการแก้ไขข้อบกพร่อง การทดสอบ และการรักษาความสามารถในการคาดการณ์ของแอปพลิเคชัน
|
|
|
|
|
|
**ประโยชน์ของการจัดการสถานะที่ไม่เปลี่ยนแปลง:**
|
|
|
|
|
|
| ประโยชน์ | คำอธิบาย | ผลกระทบ |
|
|
|
|---------|-------------|--------|
|
|
|
| **ความสามารถในการคาดการณ์** | การเปลี่ยนแปลงเกิดขึ้นผ่านฟังก์ชันที่ควบคุมเท่านั้น | แก้ไขข้อบกพร่องและทดสอบได้ง่ายขึ้น |
|
|
|
| **การติดตามประวัติ** | การเปลี่ยนแปลงสถานะแต่ละครั้งสร้างวัตถุใหม่ | รองรับฟังก์ชันย้อนกลับ/ทำซ้ำ |
|
|
|
| **ป้องกันผลกระทบข้างเคียง** | ไม่มีการแก้ไขโดยไม่ได้ตั้งใจ | ป้องกันข้อบกพร่องที่ไม่คาดคิด |
|
|
|
| **การปรับปรุงประสิทธิภาพ** | ตรวจจับได้ง่ายเมื่อสถานะเปลี่ยนแปลงจริง | รองรับการอัปเดต UI อย่างมีประสิทธิภาพ |
|
|
|
|
|
|
**การจัดการสถานะไม่เปลี่ยนแปลงใน JavaScript ด้วย `Object.freeze()`:**
|
|
|
|
|
|
JavaScript มี [`Object.freeze()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) เพื่อป้องกันการแก้ไขวัตถุ:
|
|
|
|
|
|
```js
|
|
|
const immutableState = Object.freeze({ account: userData });
|
|
|
// Any attempt to modify immutableState will throw an error
|
|
|
```
|
|
|
|
|
|
**การแยกย่อยสิ่งที่เกิดขึ้นที่นี่:**
|
|
|
- **ป้องกัน**การกำหนดค่าหรือการลบคุณสมบัติโดยตรง
|
|
|
- **โยน**ข้อยกเว้นหากมีการพยายามแก้ไข
|
|
|
- **รับรอง**ว่าการเปลี่ยนแปลงสถานะต้องผ่านฟังก์ชันที่ควบคุม
|
|
|
- **สร้าง**สัญญาที่ชัดเจนสำหรับวิธีการอัปเดตสถานะ
|
|
|
|
|
|
> 💡 **เจาะลึก**: เรียนรู้เกี่ยวกับความแตกต่างระหว่างวัตถุไม่เปลี่ยนแปลงแบบ *ตื้น* และ *ลึก* ใน [เอกสาร MDN](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze) การเข้าใจความแตกต่างนี้เป็นสิ่งสำคัญสำหรับโครงสร้างสถานะที่ซับซ้อน
|
|
|
|
|
|
### งาน
|
|
|
|
|
|
มาสร้างฟังก์ชัน `updateState()` ใหม่:
|
|
|
|
|
|
```js
|
|
|
function updateState(property, newData) {
|
|
|
state = Object.freeze({
|
|
|
...state,
|
|
|
[property]: newData
|
|
|
});
|
|
|
}
|
|
|
```
|
|
|
|
|
|
ในฟังก์ชันนี้ เรากำลังสร้างวัตถุสถานะใหม่และคัดลอกข้อมูลจากสถานะก่อนหน้าด้วย [*ตัวดำเนินการกระจาย (`...`)*](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) จากนั้นเราจะเขียนทับคุณสมบัติเฉพาะของวัตถุสถานะด้วยข้อมูลใหม่โดยใช้ [สัญลักษณ์วงเล็บ](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]` สำหรับการกำหนดค่า สุดท้ายเราล็อควัตถุเพื่อป้องกันการแก้ไขโดยใช้ `Object.freeze()` ตอนนี้เรามีคุณสมบัติ `account` ที่จัดเก็บในสถานะเท่านั้น แต่ด้วยวิธีนี้คุณสามารถเพิ่มคุณสมบัติได้มากเท่าที่คุณต้องการในสถานะ
|
|
|
|
|
|
เราจะอัปเดตการเริ่มต้นสถานะเพื่อให้แน่ใจว่าสถานะเริ่มต้นถูกล็อคด้วย:
|
|
|
|
|
|
```js
|
|
|
let state = Object.freeze({
|
|
|
account: null
|
|
|
});
|
|
|
```
|
|
|
|
|
|
หลังจากนั้น อัปเดตฟังก์ชัน `register` โดยแทนที่การกำหนดค่า `state.account = result;` ด้วย:
|
|
|
|
|
|
```js
|
|
|
updateState('account', result);
|
|
|
```
|
|
|
|
|
|
ทำเช่นเดียวกันกับฟังก์ชัน `login` โดยแทนที่ `state.account = data;` ด้วย:
|
|
|
|
|
|
```js
|
|
|
updateState('account', data);
|
|
|
```
|
|
|
|
|
|
เราจะใช้โอกาสนี้แก้ไขปัญหาข้อมูลบัญชีที่ไม่ได้ถูกล้างเมื่อผู้ใช้คลิก *Logout*
|
|
|
|
|
|
สร้างฟังก์ชันใหม่ `logout()`:
|
|
|
|
|
|
```js
|
|
|
function logout() {
|
|
|
updateState('account', null);
|
|
|
navigate('/login');
|
|
|
}
|
|
|
```
|
|
|
|
|
|
ใน `updateDashboard()`, แทนที่การเปลี่ยนเส้นทาง `return navigate('/login');` ด้วย `return logout()`;
|
|
|
|
|
|
ลองลงทะเบียนบัญชีใหม่ ออกจากระบบ และเข้าสู่ระบบอีกครั้งเพื่อตรวจสอบว่าทุกอย่างยังทำงานได้อย่างถูกต้อง
|
|
|
|
|
|
> เคล็ดลับ: คุณสามารถดูการเปลี่ยนแปลงสถานะทั้งหมดได้โดยเพิ่ม `console.log(state)` ที่ด้านล่างของ `updateState()` และเปิดคอนโซลในเครื่องมือพัฒนาเบราว์เซอร์ของคุณ
|
|
|
|
|
|
## การดำเนินการคงข้อมูล
|
|
|
|
|
|
ปัญหาการสูญเสียเซสชันที่เราระบุไว้ก่อนหน้านี้ต้องการโซลูชันการคงข้อมูลที่รักษาสถานะผู้ใช้ในเซสชันเบราว์เซอร์ โซลูชันนี้เปลี่ยนแอปพลิเคชันของเราจากประสบการณ์ชั่วคราวไปเป็นเครื่องมือที่เชื่อถือได้และเป็นมืออาชีพ
|
|
|
|
|
|
ลองพิจารณาว่านาฬิกาอะตอมรักษาเวลาได้อย่างแม่นยำแม้ในช่วงที่ไฟฟ้าดับโดยการจัดเก็บสถานะสำคัญในหน่วยความจำที่ไม่ระเหย เช่นเดียวกัน แอปพลิเคชันเว็บต้องการกลไกการจัดเก็บที่คงอยู่เพื่อรักษาข้อมูลผู้ใช้ที่สำคัญในเซสชันเบราว์เซอร์และการรีเฟรชหน้า
|
|
|
|
|
|
**คำถามเชิงกลยุทธ์สำหรับการคงข้อมูล:**
|
|
|
|
|
|
ก่อนดำเนินการคงข้อมูล ให้พิจารณาปัจจัยสำคัญเหล่านี้:
|
|
|
|
|
|
| คำถาม | บริบทแ
|
|
|
> 💡 **ตัวเลือกขั้นสูง**: สำหรับแอปพลิเคชันออฟไลน์ที่ซับซ้อนและมีชุดข้อมูลขนาดใหญ่ ลองใช้ [`IndexedDB` API](https://developer.mozilla.org/docs/Web/API/IndexedDB_API) ซึ่งเป็นฐานข้อมูลฝั่งไคลเอนต์ที่สมบูรณ์ แต่ต้องการการพัฒนาโค้ดที่ซับซ้อนมากขึ้น
|
|
|
|
|
|
### งาน: การจัดการข้อมูลแบบถาวรด้วย localStorage
|
|
|
|
|
|
เราจะมาทำการจัดการข้อมูลแบบถาวรเพื่อให้ผู้ใช้ยังคงล็อกอินอยู่จนกว่าพวกเขาจะออกจากระบบอย่างชัดเจน โดยเราจะใช้ `localStorage` ในการเก็บข้อมูลบัญชีระหว่างการใช้งานเบราว์เซอร์
|
|
|
|
|
|
**ขั้นตอนที่ 1: กำหนดการตั้งค่าการจัดเก็บข้อมูล**
|
|
|
|
|
|
```js
|
|
|
const storageKey = 'savedAccount';
|
|
|
```
|
|
|
|
|
|
**สิ่งที่ค่าคงที่นี้ให้:**
|
|
|
- **สร้าง** ตัวระบุที่สอดคล้องกันสำหรับข้อมูลที่จัดเก็บ
|
|
|
- **ป้องกัน** การพิมพ์ผิดในการอ้างอิงคีย์การจัดเก็บ
|
|
|
- **ทำให้** การเปลี่ยนคีย์การจัดเก็บง่ายขึ้นเมื่อจำเป็น
|
|
|
- **ปฏิบัติตาม** หลักการที่ดีที่สุดสำหรับโค้ดที่ดูแลรักษาได้ง่าย
|
|
|
|
|
|
**ขั้นตอนที่ 2: เพิ่มการจัดการข้อมูลแบบถาวรอัตโนมัติ**
|
|
|
|
|
|
เพิ่มบรรทัดนี้ในตอนท้ายของฟังก์ชัน `updateState()`:
|
|
|
|
|
|
```js
|
|
|
localStorage.setItem(storageKey, JSON.stringify(state.account));
|
|
|
```
|
|
|
|
|
|
**การอธิบายสิ่งที่เกิดขึ้น:**
|
|
|
- **แปลง** ออบเจ็กต์บัญชีเป็นสตริง JSON เพื่อจัดเก็บ
|
|
|
- **บันทึก** ข้อมูลโดยใช้คีย์การจัดเก็บที่สอดคล้องกัน
|
|
|
- **ดำเนินการ** โดยอัตโนมัติเมื่อเกิดการเปลี่ยนแปลงสถานะ
|
|
|
- **รับรอง** ว่าข้อมูลที่จัดเก็บจะสอดคล้องกับสถานะปัจจุบันเสมอ
|
|
|
|
|
|
> 💡 **ประโยชน์ด้านสถาปัตยกรรม**: เนื่องจากเราได้รวมการอัปเดตสถานะทั้งหมดไว้ใน `updateState()` การเพิ่มการจัดการข้อมูลแบบถาวรจึงใช้เพียงบรรทัดเดียว ซึ่งแสดงให้เห็นถึงพลังของการตัดสินใจด้านสถาปัตยกรรมที่ดี!
|
|
|
|
|
|
**ขั้นตอนที่ 3: คืนค่าข้อมูลเมื่อแอปพลิเคชันโหลด**
|
|
|
|
|
|
สร้างฟังก์ชันเริ่มต้นเพื่อคืนค่าข้อมูลที่บันทึกไว้:
|
|
|
|
|
|
```js
|
|
|
function init() {
|
|
|
const savedAccount = localStorage.getItem(storageKey);
|
|
|
if (savedAccount) {
|
|
|
updateState('account', JSON.parse(savedAccount));
|
|
|
}
|
|
|
|
|
|
// Our previous initialization code
|
|
|
window.onpopstate = () => updateRoute();
|
|
|
updateRoute();
|
|
|
}
|
|
|
|
|
|
init();
|
|
|
```
|
|
|
|
|
|
**การทำความเข้าใจกระบวนการเริ่มต้น:**
|
|
|
- **ดึง** ข้อมูลบัญชีที่บันทึกไว้ก่อนหน้านี้จาก localStorage
|
|
|
- **แปลง** สตริง JSON กลับเป็นออบเจ็กต์ JavaScript
|
|
|
- **อัปเดต** สถานะโดยใช้ฟังก์ชันอัปเดตที่ควบคุม
|
|
|
- **คืนค่า** เซสชันของผู้ใช้อัตโนมัติเมื่อโหลดหน้า
|
|
|
- **ดำเนินการ** ก่อนการอัปเดตเส้นทางเพื่อให้แน่ใจว่าสถานะพร้อมใช้งาน
|
|
|
|
|
|
**ขั้นตอนที่ 4: ปรับปรุงเส้นทางเริ่มต้น**
|
|
|
|
|
|
อัปเดตเส้นทางเริ่มต้นเพื่อใช้ประโยชน์จากการจัดการข้อมูลแบบถาวร:
|
|
|
|
|
|
ใน `updateRoute()` ให้แทนที่:
|
|
|
```js
|
|
|
// Replace: return navigate('/login');
|
|
|
return navigate('/dashboard');
|
|
|
```
|
|
|
|
|
|
**เหตุผลที่การเปลี่ยนแปลงนี้สมเหตุสมผล:**
|
|
|
- **ใช้ประโยชน์** จากระบบการจัดการข้อมูลแบบถาวรใหม่อย่างมีประสิทธิภาพ
|
|
|
- **อนุญาต** ให้แดชบอร์ดจัดการการตรวจสอบสิทธิ์
|
|
|
- **เปลี่ยนเส้นทาง** ไปยังหน้าล็อกอินโดยอัตโนมัติหากไม่มีเซสชันที่บันทึกไว้
|
|
|
- **สร้าง** ประสบการณ์การใช้งานที่ราบรื่นยิ่งขึ้นสำหรับผู้ใช้
|
|
|
|
|
|
**การทดสอบการใช้งานของคุณ:**
|
|
|
|
|
|
1. ล็อกอินเข้าสู่แอปธนาคารของคุณ
|
|
|
2. รีเฟรชหน้าเบราว์เซอร์
|
|
|
3. ตรวจสอบว่าคุณยังคงล็อกอินอยู่และอยู่ในหน้าแดชบอร์ด
|
|
|
4. ปิดและเปิดเบราว์เซอร์ใหม่
|
|
|
5. กลับไปยังแอปของคุณและยืนยันว่าคุณยังคงล็อกอินอยู่
|
|
|
|
|
|
🎉 **ความสำเร็จปลดล็อก**: คุณได้ทำการจัดการสถานะแบบถาวรสำเร็จแล้ว! แอปของคุณตอนนี้ทำงานเหมือนแอปพลิเคชันเว็บระดับมืออาชีพ
|
|
|
|
|
|
## การปรับสมดุลระหว่างการจัดการข้อมูลแบบถาวรกับความสดใหม่ของข้อมูล
|
|
|
|
|
|
ระบบการจัดการข้อมูลแบบถาวรของเราสามารถรักษาเซสชันของผู้ใช้ได้สำเร็จ แต่ก็สร้างความท้าทายใหม่: ข้อมูลที่ล้าสมัย เมื่อผู้ใช้หรือแอปพลิเคชันหลายตัวแก้ไขข้อมูลเซิร์ฟเวอร์เดียวกัน ข้อมูลที่แคชไว้ในเครื่องอาจไม่ทันสมัย
|
|
|
|
|
|
สถานการณ์นี้คล้ายกับนักเดินเรือไวกิ้งที่ใช้ทั้งแผนที่ดาวที่บันทึกไว้และการสังเกตการณ์ดาวปัจจุบัน แผนที่ให้ความสม่ำเสมอ แต่พวกเขาต้องการการสังเกตการณ์ใหม่เพื่อปรับตัวให้เข้ากับสภาพที่เปลี่ยนแปลง เช่นเดียวกัน แอปพลิเคชันของเราต้องการทั้งสถานะผู้ใช้ที่ถาวรและข้อมูลเซิร์ฟเวอร์ที่สดใหม่
|
|
|
|
|
|
**🧪 การค้นพบปัญหาความสดใหม่ของข้อมูล:**
|
|
|
|
|
|
1. ล็อกอินเข้าสู่แดชบอร์ดโดยใช้บัญชี `test`
|
|
|
2. รันคำสั่งนี้ในเทอร์มินัลเพื่อจำลองการทำธุรกรรมจากแหล่งอื่น:
|
|
|
|
|
|
```sh
|
|
|
curl --request POST \
|
|
|
--header "Content-Type: application/json" \
|
|
|
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
|
|
|
http://localhost:5000/api/accounts/test/transactions
|
|
|
```
|
|
|
|
|
|
3. รีเฟรชหน้าแดชบอร์ดในเบราว์เซอร์
|
|
|
4. สังเกตว่าคุณเห็นธุรกรรมใหม่หรือไม่
|
|
|
|
|
|
**สิ่งที่การทดสอบนี้แสดงให้เห็น:**
|
|
|
- **แสดง** ว่า localStorage อาจกลายเป็น "ล้าสมัย" (ไม่ทันสมัย)
|
|
|
- **จำลอง** สถานการณ์จริงที่ข้อมูลเปลี่ยนแปลงนอกแอปของคุณ
|
|
|
- **เผย** ความตึงเครียดระหว่างการจัดการข้อมูลแบบถาวรและความสดใหม่ของข้อมูล
|
|
|
|
|
|
**ความท้าทายของข้อมูลล้าสมัย:**
|
|
|
|
|
|
| ปัญหา | สาเหตุ | ผลกระทบต่อผู้ใช้ |
|
|
|
|-------|--------|-------------------|
|
|
|
| **ข้อมูลล้าสมัย** | localStorage ไม่หมดอายุโดยอัตโนมัติ | ผู้ใช้เห็นข้อมูลที่ไม่ทันสมัย |
|
|
|
| **การเปลี่ยนแปลงเซิร์ฟเวอร์** | แอป/ผู้ใช้อื่นแก้ไขข้อมูลเดียวกัน | มุมมองที่ไม่สอดคล้องกันระหว่างแพลตฟอร์ม |
|
|
|
| **แคช vs. ความเป็นจริง** | แคชในเครื่องไม่ตรงกับสถานะเซิร์ฟเวอร์ | ประสบการณ์ผู้ใช้ที่ไม่ดีและความสับสน |
|
|
|
|
|
|
**กลยุทธ์การแก้ปัญหา:**
|
|
|
|
|
|
เราจะใช้รูปแบบ "รีเฟรชเมื่อโหลด" ที่สมดุลระหว่างข้อดีของการจัดการข้อมูลแบบถาวรกับความต้องการข้อมูลที่สดใหม่ วิธีนี้จะช่วยรักษาประสบการณ์การใช้งานที่ราบรื่นในขณะที่รับรองความถูกต้องของข้อมูล
|
|
|
|
|
|
### งาน: การสร้างระบบรีเฟรชข้อมูล
|
|
|
|
|
|
เราจะสร้างระบบที่ดึงข้อมูลใหม่จากเซิร์ฟเวอร์โดยอัตโนมัติในขณะที่ยังคงรักษาข้อดีของการจัดการสถานะแบบถาวร
|
|
|
|
|
|
**ขั้นตอนที่ 1: สร้างฟังก์ชันอัปเดตข้อมูลบัญชี**
|
|
|
|
|
|
```js
|
|
|
async function updateAccountData() {
|
|
|
const account = state.account;
|
|
|
if (!account) {
|
|
|
return logout();
|
|
|
}
|
|
|
|
|
|
const data = await getAccount(account.user);
|
|
|
if (data.error) {
|
|
|
return logout();
|
|
|
}
|
|
|
|
|
|
updateState('account', data);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**การทำความเข้าใจตรรกะของฟังก์ชันนี้:**
|
|
|
- **ตรวจสอบ** ว่าผู้ใช้ล็อกอินอยู่หรือไม่ (state.account มีอยู่)
|
|
|
- **เปลี่ยนเส้นทาง** ไปยังหน้าล็อกเอาต์หากไม่มีเซสชันที่ถูกต้อง
|
|
|
- **ดึง** ข้อมูลบัญชีใหม่จากเซิร์ฟเวอร์โดยใช้ฟังก์ชัน `getAccount()` ที่มีอยู่
|
|
|
- **จัดการ** ข้อผิดพลาดของเซิร์ฟเวอร์อย่างราบรื่นโดยการล็อกเอาต์เซสชันที่ไม่ถูกต้อง
|
|
|
- **อัปเดต** สถานะด้วยข้อมูลใหม่โดยใช้ระบบอัปเดตที่ควบคุม
|
|
|
- **กระตุ้น** การจัดการข้อมูลแบบถาวรใน localStorage โดยอัตโนมัติผ่านฟังก์ชัน `updateState()`
|
|
|
|
|
|
**ขั้นตอนที่ 2: สร้างฟังก์ชันรีเฟรชแดชบอร์ด**
|
|
|
|
|
|
```js
|
|
|
async function refresh() {
|
|
|
await updateAccountData();
|
|
|
updateDashboard();
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**สิ่งที่ฟังก์ชันรีเฟรชนี้ทำสำเร็จ:**
|
|
|
- **ประสานงาน** กระบวนการรีเฟรชข้อมูลและการอัปเดต UI
|
|
|
- **รอ** ให้ข้อมูลใหม่โหลดก่อนอัปเดตการแสดงผล
|
|
|
- **รับรอง** ว่าแดชบอร์ดแสดงข้อมูลที่ทันสมัยที่สุด
|
|
|
- **รักษา** การแยกที่ชัดเจนระหว่างการจัดการข้อมูลและการอัปเดต UI
|
|
|
|
|
|
**ขั้นตอนที่ 3: รวมเข้ากับระบบเส้นทาง**
|
|
|
|
|
|
อัปเดตการกำหนดค่าเส้นทางของคุณเพื่อกระตุ้นการรีเฟรชโดยอัตโนมัติ:
|
|
|
|
|
|
```js
|
|
|
const routes = {
|
|
|
'/login': { templateId: 'login' },
|
|
|
'/dashboard': { templateId: 'dashboard', init: refresh }
|
|
|
};
|
|
|
```
|
|
|
|
|
|
**วิธีการทำงานของการรวมนี้:**
|
|
|
- **ดำเนินการ** ฟังก์ชันรีเฟรชทุกครั้งที่โหลดเส้นทางแดชบอร์ด
|
|
|
- **รับรอง** ว่าข้อมูลใหม่จะแสดงเสมอเมื่อผู้ใช้ไปยังแดชบอร์ด
|
|
|
- **รักษา** โครงสร้างเส้นทางที่มีอยู่ในขณะที่เพิ่มความสดใหม่ของข้อมูล
|
|
|
- **ให้** รูปแบบที่สอดคล้องกันสำหรับการเริ่มต้นเฉพาะเส้นทาง
|
|
|
|
|
|
**การทดสอบระบบรีเฟรชข้อมูลของคุณ:**
|
|
|
|
|
|
1. ล็อกอินเข้าสู่แอปธนาคารของคุณ
|
|
|
2. รันคำสั่ง curl จากก่อนหน้านี้เพื่อสร้างธุรกรรมใหม่
|
|
|
3. รีเฟรชหน้าแดชบอร์ดหรือไปยังหน้าอื่นแล้วกลับมา
|
|
|
4. ตรวจสอบว่าธุรกรรมใหม่ปรากฏทันที
|
|
|
|
|
|
🎉 **สมดุลที่สมบูรณ์แบบ**: แอปของคุณตอนนี้รวมประสบการณ์การใช้งานที่ราบรื่นของสถานะถาวรเข้ากับความถูกต้องของข้อมูลเซิร์ฟเวอร์ที่สดใหม่!
|
|
|
|
|
|
## ความท้าทายของ GitHub Copilot Agent 🚀
|
|
|
|
|
|
ใช้โหมด Agent เพื่อทำความท้าทายต่อไปนี้:
|
|
|
|
|
|
**คำอธิบาย:** สร้างระบบการจัดการสถานะที่ครอบคลุมพร้อมฟังก์ชันย้อนกลับ/ทำซ้ำสำหรับแอปธนาคาร ความท้าทายนี้จะช่วยให้คุณฝึกฝนแนวคิดการจัดการสถานะขั้นสูง รวมถึงการติดตามประวัติสถานะ การอัปเดตแบบไม่เปลี่ยนแปลง และการซิงโครไนซ์กับส่วนติดต่อผู้ใช้
|
|
|
|
|
|
**คำสั่ง:** สร้างระบบการจัดการสถานะที่ปรับปรุงแล้วซึ่งรวมถึง: 1) อาร์เรย์ประวัติสถานะที่ติดตามสถานะก่อนหน้า, 2) ฟังก์ชันย้อนกลับและทำซ้ำที่สามารถย้อนกลับไปยังสถานะก่อนหน้า, 3) ปุ่ม UI สำหรับการย้อนกลับ/ทำซ้ำบนแดชบอร์ด, 4) ขีดจำกัดประวัติสูงสุด 10 สถานะเพื่อป้องกันปัญหาหน่วยความจำ, และ 5) การล้างประวัติอย่างเหมาะสมเมื่อผู้ใช้ออกจากระบบ รับรองว่าฟังก์ชันย้อนกลับ/ทำซ้ำทำงานร่วมกับการเปลี่ยนแปลงยอดคงเหลือบัญชีและยังคงอยู่หลังการรีเฟรชเบราว์เซอร์
|
|
|
|
|
|
เรียนรู้เพิ่มเติมเกี่ยวกับ [โหมด Agent](https://code.visualstudio.com/blogs/2025/02/24/introducing-copilot-agent-mode) ที่นี่
|
|
|
|
|
|
## 🚀 ความท้าทาย: การปรับปรุงการจัดเก็บข้อมูล
|
|
|
|
|
|
การใช้งานของคุณตอนนี้จัดการเซสชันผู้ใช้ การรีเฟรชข้อมูล และการจัดการสถานะได้อย่างมีประสิทธิภาพ อย่างไรก็ตาม ลองพิจารณาว่าวิธีการปัจจุบันของเราสมดุลระหว่างประสิทธิภาพการจัดเก็บข้อมูลกับฟังก์ชันการทำงานได้ดีที่สุดหรือไม่
|
|
|
|
|
|
เหมือนกับนักหมากรุกที่แยกแยะระหว่างตัวหมากที่สำคัญและตัวหมากที่สามารถเสียได้ การจัดการสถานะที่มีประสิทธิภาพต้องระบุว่าข้อมูลใดที่ต้องจัดเก็บถาวรและข้อมูลใดที่ควรดึงจากเซิร์ฟเวอร์เสมอ
|
|
|
|
|
|
**การวิเคราะห์การปรับปรุง:**
|
|
|
|
|
|
ประเมินการใช้งาน localStorage ปัจจุบันของคุณและพิจารณาคำถามเชิงกลยุทธ์เหล่านี้:
|
|
|
- ข้อมูลขั้นต่ำที่จำเป็นในการรักษาการตรวจสอบสิทธิ์ของผู้ใช้คืออะไร?
|
|
|
- ข้อมูลใดที่เปลี่ยนแปลงบ่อยจนการแคชในเครื่องไม่มีประโยชน์?
|
|
|
- การปรับปรุงการจัดเก็บข้อมูลสามารถเพิ่มประสิทธิภาพได้โดยไม่ลดทอนประสบการณ์ผู้ใช้ได้อย่างไร?
|
|
|
|
|
|
**กลยุทธ์การใช้งาน:**
|
|
|
- **ระบุ** ข้อมูลสำคัญที่ต้องจัดเก็บถาวร (อาจเป็นเพียงข้อมูลการระบุตัวตนของผู้ใช้)
|
|
|
- **ปรับเปลี่ยน** การใช้งาน localStorage ของคุณเพื่อจัดเก็บเฉพาะข้อมูลเซสชันที่สำคัญ
|
|
|
- **รับรอง** ว่าข้อมูลใหม่จะถูกโหลดจากเซิร์ฟเวอร์เสมอเมื่อเยี่ยมชมแดชบอร์ด
|
|
|
- **ทดสอบ** ว่าวิธีการที่ปรับปรุงแล้วของคุณยังคงรักษาประสบการณ์ผู้ใช้เดิม
|
|
|
|
|
|
**การพิจารณาขั้นสูง:**
|
|
|
- **เปรียบเทียบ** ข้อดีข้อเสียระหว่างการจัดเก็บข้อมูลบัญชีทั้งหมดกับการจัดเก็บเฉพาะโทเค็นการตรวจสอบสิทธิ์
|
|
|
- **บันทึก** การตัดสินใจและเหตุผลของคุณสำหรับสมาชิกทีมในอนาคต
|
|
|
|
|
|
ความท้าทายนี้จะช่วยให้คุณคิดเหมือนนักพัฒนามืออาชีพที่พิจารณาทั้งประสบการณ์ผู้ใช้และประสิทธิภาพของแอปพลิเคชัน ใช้เวลาในการทดลองกับวิธีการต่าง ๆ!
|
|
|
|
|
|
## แบบทดสอบหลังการบรรยาย
|
|
|
|
|
|
[แบบทดสอบหลังการบรรยาย](https://ff-quizzes.netlify.app/web/quiz/48)
|
|
|
|
|
|
## งานที่ได้รับมอบหมาย
|
|
|
|
|
|
[สร้างหน้าต่าง "เพิ่มธุรกรรม"](assignment.md)
|
|
|
|
|
|
นี่คือตัวอย่างผลลัพธ์หลังจากทำงานที่ได้รับมอบหมายเสร็จ:
|
|
|
|
|
|

|
|
|
|
|
|
---
|
|
|
|
|
|
**ข้อจำกัดความรับผิดชอบ**:
|
|
|
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลสำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้ |