# สร้างแอปธนาคาร ตอนที่ 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. **ทุกคนเข้าใจตรงกันหรือไม่?**: การทำให้สิ่งที่ผู้ใช้เห็นตรงกับสิ่งที่เกิดขึ้นจริง **แผนการของเรา:** แทนที่จะไล่ตามปัญหา เราจะสร้างระบบ **การจัดการสถานะที่เป็นศูนย์กลาง** คิดว่ามันเหมือนกับการมีคนที่จัดระเบียบได้ดีคนหนึ่งที่ดูแลเรื่องสำคัญทั้งหมด: ![แผนภาพแสดงการไหลของข้อมูลระหว่าง HTML, การกระทำของผู้ใช้ และสถานะ](../../../../translated_images/data-flow.fa2354e0908fecc89b488010dedf4871418a992edffa17e73441d257add18da4.th.png) **การทำความเข้าใจการไหลของข้อมูลนี้:** - **รวมศูนย์**สถานะของแอปพลิเคชันทั้งหมดในที่เดียว - **กำหนดเส้นทาง**การเปลี่ยนแปลงสถานะทั้งหมดผ่านฟังก์ชันที่ควบคุม - **รับรอง**ว่า 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) นี่คือตัวอย่างผลลัพธ์หลังจากทำงานที่ได้รับมอบหมายเสร็จ: ![ภาพหน้าจอแสดงตัวอย่างหน้าต่าง "เพิ่มธุรกรรม"](../../../../translated_images/dialog.93bba104afeb79f12f65ebf8f521c5d64e179c40b791c49c242cf15f7e7fab15.th.png) --- **ข้อจำกัดความรับผิดชอบ**: เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI [Co-op Translator](https://github.com/Azure/co-op-translator) แม้ว่าเราจะพยายามให้การแปลมีความถูกต้อง แต่โปรดทราบว่าการแปลอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลสำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้