ใช้ประโยชน์จากการเขียนโปรแกรม Declarative เพื่อสร้างเว็บแอปที่บำรุงรักษาได้

เผยแพร่แล้ว: 2022-03-11

ในบทความนี้ ผมจะแสดงวิธีที่การนำเทคนิคการเขียนโปรแกรมรูปแบบการประกาศมาใช้อย่างรอบคอบสามารถช่วยให้ทีมสร้างเว็บแอปพลิเคชันที่ขยายและบำรุงรักษาได้ง่ายขึ้น

“…การเขียนโปรแกรมเชิงประกาศเป็นกระบวนทัศน์การเขียนโปรแกรมที่แสดงตรรกะของการคำนวณโดยไม่อธิบายโฟลว์การควบคุมของมัน” —Remo H. Jansen การเขียนโปรแกรมเชิงปฏิบัติด้วย TypeScript

เช่นเดียวกับปัญหาอื่นๆ ในซอฟต์แวร์ การตัดสินใจใช้เทคนิคการเขียนโปรแกรมที่เปิดเผยในแอปพลิเคชันของคุณจำเป็นต้องมีการประเมินข้อดีข้อเสียอย่างรอบคอบ ตรวจสอบหนึ่งในบทความก่อนหน้าของเราสำหรับการอภิปรายเชิงลึกเกี่ยวกับสิ่งเหล่านี้

ในที่นี้ จุดเน้นอยู่ที่วิธีที่รูปแบบการเขียนโปรแกรมที่เปิดเผยออกมาค่อยๆ นำไปใช้สำหรับแอปพลิเคชันทั้งใหม่และที่มีอยู่ซึ่งเขียนด้วย JavaScript ซึ่งเป็นภาษาที่รองรับกระบวนทัศน์หลายแบบ

ขั้นแรก เราจะพูดถึงวิธีใช้ TypeScript ทั้งในส่วนหลังและส่วนหน้า เพื่อทำให้โค้ดของคุณมีความชัดเจนและยืดหยุ่นในการเปลี่ยนแปลงมากขึ้น จากนั้นเราจะสำรวจเครื่องจักรที่มีสถานะจำกัด (FSM) เพื่อปรับปรุงการพัฒนาส่วนหน้าและเพิ่มการมีส่วนร่วมของผู้มีส่วนได้ส่วนเสียในกระบวนการพัฒนา

FSM ไม่ใช่เทคโนโลยีใหม่ พวกเขาถูกค้นพบเมื่อเกือบ 50 ปีที่แล้วและเป็นที่นิยมในอุตสาหกรรมต่างๆ เช่น การประมวลผลสัญญาณ วิชาการบิน และการเงิน ซึ่งความถูกต้องของซอฟต์แวร์เป็นสิ่งสำคัญ นอกจากนี้ยังเหมาะสมอย่างยิ่งกับปัญหาการสร้างแบบจำลองที่มักเกิดขึ้นในการพัฒนาเว็บสมัยใหม่ เช่น การประสานงานการอัปเดตสถานะอะซิงโครนัสที่ซับซ้อนและแอนิเมชั่น

ผลประโยชน์นี้เกิดขึ้นเนื่องจากข้อจำกัดเกี่ยวกับวิธีการจัดการของรัฐ เครื่องแสดงสถานะสามารถอยู่ในสถานะเดียวเท่านั้นพร้อมกันและมีสถานะใกล้เคียงที่จำกัด ซึ่งสามารถเปลี่ยนเป็นการตอบสนองต่อเหตุการณ์ภายนอกได้ (เช่น การคลิกเมาส์หรือการตอบสนองการดึงข้อมูล) ผลที่ได้คืออัตราข้อบกพร่องที่ลดลงอย่างมาก อย่างไรก็ตาม วิธีการของ FSM อาจเป็นเรื่องยากที่จะขยายขนาดเพื่อให้ทำงานได้ดีในแอปพลิเคชันขนาดใหญ่ ส่วนขยายล่าสุดของ FSM ที่เรียกว่า statechart ช่วยให้ FSM ที่ซับซ้อนสามารถแสดงภาพและปรับขนาดเป็นแอปพลิเคชันขนาดใหญ่กว่าได้ ซึ่งเป็นรสชาติของเครื่องจักรที่มีสถานะจำกัดที่บทความนี้เน้น สำหรับการสาธิตของเรา เราจะใช้ไลบรารี XState ซึ่งเป็นหนึ่งในโซลูชันที่ดีที่สุดสำหรับ FSM และ statechart ใน JavaScript

ประกาศที่ส่วนหลังด้วย Node.js

การเขียนโปรแกรมเว็บเซิร์ฟเวอร์แบ็กเอนด์โดยใช้วิธีการเปิดเผยเป็นหัวข้อขนาดใหญ่ และโดยทั่วไปอาจเริ่มต้นด้วยการประเมินภาษาโปรแกรมที่ใช้งานได้ฝั่งเซิร์ฟเวอร์ที่เหมาะสม สมมติว่าคุณกำลังอ่านข้อความนี้ในเวลาที่คุณเลือก (หรือกำลังพิจารณา) Node.js สำหรับส่วนหลังของคุณ

ส่วนนี้ให้รายละเอียดวิธีการในการสร้างแบบจำลองเอนทิตีที่ส่วนหลังที่มีประโยชน์ดังต่อไปนี้:

  • ปรับปรุงความสามารถในการอ่านโค้ด
  • การปรับโครงสร้างใหม่อย่างปลอดภัย
  • ศักยภาพในการปรับปรุงประสิทธิภาพเนื่องจากการให้แบบจำลองประเภทการรับประกัน

การรับประกันพฤติกรรมผ่านการสร้างแบบจำลองประเภท

JavaScript

พิจารณางานในการค้นหาผู้ใช้ที่ระบุผ่านที่อยู่อีเมลใน JavaScript:

 function validateEmail(email) { if (typeof email !== "string") return false; return isWellFormedEmailAddress(email); } function lookupUser(validatedEmail) { // Assume a valid email is passed in. // Safe to pass this down to the database for a user lookup.. }

ฟังก์ชันนี้ยอมรับที่อยู่อีเมลเป็นสตริงและส่งคืนผู้ใช้ที่เกี่ยวข้องจากฐานข้อมูลเมื่อมีข้อมูลที่ตรงกัน

สมมติฐานคือ lookupUser() จะถูกเรียกเมื่อทำการตรวจสอบความถูกต้องพื้นฐานแล้วเท่านั้น นี่เป็นข้อสันนิษฐานที่สำคัญ จะเกิดอะไรขึ้นหากผ่านไปหลายสัปดาห์ มีการจัดโครงสร้างใหม่บางส่วนและข้อสันนิษฐานนี้ไม่มีอยู่อีกต่อไป เห็นด้วยอย่างยิ่งว่าการทดสอบหน่วยตรวจพบจุดบกพร่อง หรือเราอาจส่งข้อความที่ไม่มีการกรองไปยังฐานข้อมูล!

TypeScript (ความพยายามครั้งแรก)

ลองพิจารณาว่า TypeScript เทียบเท่ากับฟังก์ชันการตรวจสอบความถูกต้อง:

 function validateEmail(email: string) { // No longer needed the type check (typeof email === "string"). return isWellFormedEmailAddress(email); }

นี่เป็นการปรับปรุงเล็กน้อย โดยคอมไพเลอร์ TypeScript ช่วยเราจากการเพิ่มขั้นตอนการตรวจสอบรันไทม์เพิ่มเติม

ความปลอดภัยรับประกันว่าการพิมพ์ที่รัดกุมนั้นยังไม่ได้ถูกเอาเปรียบจริงๆ ลองดูว่า

TypeScript (พยายามครั้งที่สอง)

มาปรับปรุงความปลอดภัยของประเภทและไม่อนุญาตให้ส่งสตริงที่ยังไม่ได้ประมวลผลเป็นอินพุตไปยัง looukupUser :

 type ValidEmail = { value: string }; function validateEmail(input: string): Email | null { if (!isWellFormedEmailAddress(input)) return null; return { value: email }; } function lookupUser(email: ValidEmail): User { // No need to perform validation. Compiler has already ensured only valid emails have been passed in. return lookupUserInDatabase(email.value); }

จะดีกว่านี้ แต่ยุ่งยาก การใช้ ValidEmail ทั้งหมดจะเข้าถึงที่อยู่จริงผ่าน email.value TypeScript ใช้การพิมพ์ แบบโครงสร้าง มากกว่าการพิมพ์แบบ ระบุชื่อที่ ใช้โดยภาษาต่างๆ เช่น Java และ C#

แม้ว่าจะมีประสิทธิภาพ แต่ก็หมายความว่าประเภทอื่นใดที่ยึดตามลายเซ็นนี้ถือว่าเทียบเท่า ตัวอย่างเช่น รหัสผ่านประเภทต่อไปนี้สามารถส่งต่อไปยัง lookupUser() โดยไม่มีข้อร้องเรียนจากคอมไพเลอร์:

 type ValidPassword = { value: string }; const password = { value: "password" }; lookupUser(password); // No error.

TypeScript (พยายามครั้งที่สาม)

เราสามารถพิมพ์เล็กน้อยใน TypeScript โดยใช้จุดตัด:

 type ValidEmail = string & { _: "ValidEmail" }; function validateEmail(input: string): ValidEmail { // Perform email validation checks.. return input as ValidEmail; } type ValidPassword = string & { _: "ValidPassword" }; function validatePassword(input: string): ValidPassword { ... } lookupUser("[email protected]"); // Error: expected type ValidEmail. lookupUser(validatePassword("MyPassword"); // Error: expected type ValidEmail. lookupUser(validateEmail("[email protected]")); // Ok.

ตอนนี้เราบรรลุเป้าหมายที่ส่งผ่านเฉพาะสตริงอีเมลที่ตรวจสอบแล้วไปยัง lookupUser() ได้

เคล็ดลับแบบมือโปร: ใช้รูปแบบนี้อย่างง่ายดายโดยใช้ประเภทตัวช่วยต่อไปนี้:

 type Opaque<K, T> = T & { __TYPE__: K }; type Email = Opaque<"Email", string>; type Password = Opaque<"Password", string>; type UserId = Opaque<"UserId", number>;

ข้อดี

ด้วยการพิมพ์เอนทิตีในโดเมนของคุณอย่างจริงจัง เราสามารถ:

  1. ลดจำนวนการตรวจสอบที่ต้องทำขณะรันไทม์ ซึ่งใช้รอบ CPU ของเซิร์ฟเวอร์อันมีค่า (แม้ว่าจะมีจำนวนเล็กน้อย แต่สิ่งเหล่านี้จะเพิ่มขึ้นเมื่อให้บริการคำขอนับพันต่อนาที)
  2. รักษาการทดสอบพื้นฐานให้น้อยลงเนื่องจากการค้ำประกันที่คอมไพเลอร์ TypeScript มีให้
  3. ใช้ประโยชน์จากการปรับโครงสร้างใหม่โดยใช้เครื่องมือแก้ไขและคอมไพเลอร์
  4. ปรับปรุงความสามารถในการอ่านโค้ดผ่านอัตราส่วนสัญญาณต่อสัญญาณรบกวนที่ได้รับการปรับปรุง

ข้อเสีย

การสร้างแบบจำลองประเภทมาพร้อมกับข้อเสียบางประการที่ต้องพิจารณา:

  1. โดยปกติแล้ว การแนะนำ TypeScript จะทำให้ toolchain ซับซ้อน ส่งผลให้เวลาในการสร้างและทดสอบชุดทำงานยาวนานขึ้น
  2. หากเป้าหมายของคุณคือการสร้างต้นแบบคุณลักษณะและเข้าถึงผู้ใช้โดยเร็วที่สุด ความพยายามพิเศษที่จำเป็นในการสร้างแบบจำลองประเภทอย่างชัดเจนและเผยแพร่ผ่าน codebase อาจไม่คุ้มค่า

เราได้แสดงให้เห็นว่าโค้ด JavaScript ที่มีอยู่บนเซิร์ฟเวอร์หรือแบ็กเอนด์/ส่วนตรวจสอบส่วนหน้าที่ใช้ร่วมกันสามารถขยายได้ด้วยประเภทต่างๆ ได้อย่างไร เพื่อปรับปรุงความสามารถในการอ่านโค้ด และอนุญาตให้มีการจัดโครงสร้างใหม่ได้อย่างปลอดภัย ซึ่งเป็นข้อกำหนดที่สำคัญสำหรับทีม

ส่วนต่อประสานผู้ใช้ที่ประกาศ

ส่วนต่อประสานผู้ใช้ที่พัฒนาโดยใช้เทคนิคการเขียนโปรแกรมแบบเปิดเผยเน้นความพยายามในการอธิบาย "อะไร" มากกว่า "อย่างไร" ส่วนประกอบพื้นฐานหลักสองในสามของเว็บ ได้แก่ CSS และ HTML เป็นภาษาการเขียนโปรแกรมเชิงประกาศที่ผ่านการทดสอบของเวลาและเว็บไซต์มากกว่า 1 พันล้านแห่ง

ภาษาหลักที่ขับเคลื่อนเว็บ
ภาษาหลักที่ขับเคลื่อนเว็บ

React เป็นโอเพ่นซอร์สโดย Facebook ในปี 2013 และได้เปลี่ยนแปลงแนวทางการพัฒนาส่วนหน้าอย่างมีนัยสำคัญ เมื่อใช้งานครั้งแรก ฉันชอบที่ฉันสามารถ ประกาศ GUI เป็นฟังก์ชันของสถานะของแอปพลิเคชันได้ ตอนนี้ฉันสามารถเขียน UI ขนาดใหญ่และซับซ้อนจากหน่วยการสร้างขนาดเล็กโดยไม่ต้องจัดการกับรายละเอียดยุ่ง ๆ ของการจัดการ DOM และการติดตามว่าส่วนใดของแอปจำเป็นต้องอัปเดตเพื่อตอบสนองต่อการกระทำของผู้ใช้ ฉันสามารถเพิกเฉยต่อด้าน เวลา เป็นส่วนใหญ่เมื่อกำหนด UI และมุ่งเน้นไปที่การตรวจสอบให้แน่ใจว่าแอปพลิเคชันของฉันเปลี่ยนจากสถานะหนึ่งไปอีกสถานะหนึ่งอย่างถูกต้อง

วิวัฒนาการของ front-end JavaScript จาก how to what
วิวัฒนาการของ front-end JavaScript จาก how to what .

เพื่อให้บรรลุวิธีที่ง่ายกว่าในการพัฒนา UIs React ได้แทรกเลเยอร์ที่เป็นนามธรรมระหว่างผู้พัฒนากับเครื่อง/เบราว์เซอร์: DOM เสมือน

เฟรมเวิร์กเว็บ UI สมัยใหม่อื่น ๆ ได้เชื่อมช่องว่างนี้ด้วย แม้ว่าจะมีวิธีที่ต่างกัน ตัวอย่างเช่น Vue ใช้การตอบสนองต่อการทำงานผ่านตัวรับ/เซ็ตเตอร์ JavaScript (Vue 2) หรือพร็อกซี่ (Vue 3) Svelte นำการเกิดปฏิกิริยาผ่านขั้นตอนการคอมไพล์ซอร์สโค้ดพิเศษ (Svelte)

ตัวอย่างเหล่านี้ดูเหมือนจะแสดงให้เห็นถึงความต้องการอย่างมากในอุตสาหกรรมของเราในการจัดหาเครื่องมือที่ดีกว่าและง่ายกว่าสำหรับนักพัฒนาในการแสดงพฤติกรรมของแอปพลิเคชันผ่านแนวทางการประกาศ

สถานะแอปพลิเคชันประกาศและลอจิก

ในขณะที่ชั้นการนำเสนอยังคงหมุนรอบรูปแบบของ HTML บางรูปแบบ (เช่น JSX ใน React, แม่แบบที่ใช้ HTML ที่พบใน Vue, Angular และ Svelte) ฉันสันนิษฐานว่าปัญหาของการสร้างแบบจำลองสถานะของแอปพลิเคชันในลักษณะที่เป็น นักพัฒนารายอื่นเข้าใจได้ง่ายและสามารถบำรุงรักษาได้เนื่องจากแอปพลิเคชันเติบโตขึ้นยังไม่ได้รับการแก้ไข เราเห็นหลักฐานของสิ่งนี้ผ่านการขยายตัวของห้องสมุดและแนวทางการจัดการของรัฐที่ดำเนินต่อไปจนถึงทุกวันนี้

สถานการณ์มีความซับซ้อนโดยความคาดหวังที่เพิ่มขึ้นของเว็บแอปสมัยใหม่ ความท้าทายที่เกิดขึ้นใหม่ซึ่งแนวทางการจัดการของรัฐสมัยใหม่ต้องสนับสนุน:

  • แอปพลิเคชั่นออฟไลน์ครั้งแรกโดยใช้เทคนิคการสมัครสมาชิกขั้นสูงและแคช
  • โค้ดที่กระชับและการใช้โค้ดซ้ำสำหรับข้อกำหนดขนาดบันเดิลที่ลดขนาดลงเรื่อยๆ
  • ความต้องการประสบการณ์ผู้ใช้ที่ซับซ้อนมากขึ้นผ่านแอนิเมชั่นที่มีความเที่ยงตรงสูงและการอัปเดตตามเวลาจริง

(Re) การเกิดขึ้นของ Finite-state Machines และ Statecharts

มีการใช้เครื่องจักรแบบไฟไนต์สเตทอย่างกว้างขวางสำหรับการพัฒนาซอฟต์แวร์ในอุตสาหกรรมบางประเภทที่ความทนทานของแอปพลิเคชันเป็นสิ่งสำคัญ เช่น การบินและการเงิน นอกจากนี้ยังได้รับความนิยมอย่างต่อเนื่องสำหรับการพัฒนาเว็บแอปส่วนหน้าผ่าน ตัวอย่างเช่น ไลบรารี XState ที่ยอดเยี่ยม

Wikipedia กำหนดเครื่องสถานะ จำกัด เป็น:

เครื่องนามธรรมที่สามารถอยู่ในสถานะใดสถานะหนึ่งในช่วงเวลาหนึ่ง FSM สามารถเปลี่ยนจากสถานะหนึ่งไปอีกสถานะหนึ่งเพื่อตอบสนองต่ออินพุตภายนอกบางอย่าง การเปลี่ยนแปลงจากสถานะหนึ่งไปอีกสถานะหนึ่งเรียกว่าการเปลี่ยนแปลง FSM ถูกกำหนดโดยรายการสถานะ สถานะเริ่มต้น และเงื่อนไขสำหรับการเปลี่ยนแต่ละครั้ง

และต่อไป:

สถานะคือคำอธิบายสถานะของระบบที่กำลังรอดำเนินการเปลี่ยน

FSM ในรูปแบบพื้นฐานไม่สามารถปรับขนาดได้ดีกับระบบขนาดใหญ่เนื่องจากปัญหาสถานะการระเบิด เมื่อเร็ว ๆ นี้ UML statechart ถูกสร้างขึ้นเพื่อขยาย FSM ด้วยลำดับชั้นและการทำงานพร้อมกัน ซึ่งเป็นตัวเปิดใช้งานสำหรับการใช้ FSM ในวงกว้างในแอปพลิเคชันเชิงพาณิชย์

ประกาศลอจิกการสมัครของคุณ

ประการแรก FSM มีลักษณะเป็นโค้ดอย่างไร มีหลายวิธีในการปรับใช้เครื่องที่มีสถานะจำกัดใน JavaScript

  • เครื่องไฟไนต์สเตทเป็นคำสั่งสวิตช์

นี่คือเครื่องที่อธิบายสถานะที่เป็นไปได้ที่ JavaScript สามารถนำไปใช้ได้ โดยใช้คำสั่ง switch:

 const initialState = { type: 'idle', error: undefined, result: undefined }; function transition(state = initialState, action) { switch (action) { case 'invoke': return { type: 'pending' }; case 'resolve': return { type: 'completed', result: action.value }; case 'error': return { type: 'completed', error: action.error ; default: return state; } }

โค้ดรูปแบบนี้จะคุ้นเคยสำหรับนักพัฒนาที่เคยใช้ไลบรารีการจัดการสถานะ Redux ยอดนิยม

  • เครื่องจำกัดสถานะเป็นวัตถุ JavaScript

นี่คือเครื่องเดียวกับที่ใช้งานเป็นวัตถุ JavaScript โดยใช้ไลบรารี JavaScript XSstate:

 const promiseMachine = Machine({ id: "promise", initial: "idle", context: { result: undefined, error: undefined, }, states: { idle: { on: { INVOKE: "pending", }, }, pending: { on: { RESOLVE: "success", REJECT: "failure", }, }, success: { type: "final", actions: assign({ result: (context, event) => event.data, }), }, failure: { type: "final", actions: assign({ error: (context, event) => event.data, }), }, }, });

แม้ว่าเวอร์ชัน XSstate จะมีขนาดกะทัดรัดน้อยกว่า การแสดงวัตถุก็มีข้อดีหลายประการ:

  1. เครื่องของรัฐนั้นเป็น JSON แบบง่าย ซึ่งสามารถคงอยู่ได้อย่างง่ายดาย
  2. เนื่องจากเป็นการประกาศเครื่องจึงสามารถเห็นภาพได้
  3. หากใช้ TypeScript คอมไพเลอร์จะตรวจสอบว่ามีการดำเนินการเฉพาะการเปลี่ยนสถานะที่ถูกต้องเท่านั้น

XState รองรับ statecharts และใช้ข้อกำหนด SCXML ซึ่งทำให้เหมาะสำหรับการใช้งานในแอปพลิเคชันขนาดใหญ่มาก

การแสดงภาพ Statecharts ของสัญญา:

เครื่องแห่งคำมั่นสัญญา
เครื่องจักรแห่งคำมั่นสัญญา

แนวทางปฏิบัติที่ดีที่สุด XSstate

ต่อไปนี้คือแนวทางปฏิบัติที่ดีที่สุดบางประการที่ควรใช้เมื่อใช้ XSstate เพื่อช่วยให้โครงการสามารถบำรุงรักษาได้

แยกผลข้างเคียงจาก Logic

XState อนุญาตให้ระบุผลข้างเคียง (ซึ่งรวมถึงกิจกรรม เช่น การบันทึกหรือคำขอ API) แยกจากตรรกะของเครื่องสถานะ

สิ่งนี้มีประโยชน์ดังต่อไปนี้:

  1. ช่วยตรวจจับข้อผิดพลาดทางตรรกะโดยรักษาสถานะรหัสเครื่องให้สะอาดและเรียบง่ายที่สุด
  2. แสดงภาพสถานะเครื่องได้อย่างง่ายดายโดยไม่จำเป็นต้องถอดต้นแบบเพิ่มเติมก่อน
  3. ทดสอบเครื่องของรัฐได้ง่ายขึ้นโดยการฉีดบริการจำลอง
 const fetchUsersMachine = Machine({ id: "fetchUsers", initial: "idle", context: { users: undefined, error: undefined, nextPage: 0, }, states: { idle: { on: { FETCH: "fetching", }, }, fetching: { invoke: { src: (context) => fetch(`url/to/users?page=${context.nextPage}`).then((response) => response.json() ), onDone: { target: "success", actions: assign({ users: (context, event) => [...context.users, ...event.data], // Data holds the newly fetched users nextPage: (context) => context.nextPage + 1, }), }, onError: { target: "failure", error: (_, event) => event.data, // Data holds the error }, }, }, // success state.. // failure state.. }, });

แม้ว่าจะเป็นเรื่องน่าดึงดูดใจที่จะเขียนเครื่องของรัฐในลักษณะนี้ในขณะที่คุณยังคงทำงานต่างๆ ได้ การแยกข้อกังวลที่ดีกว่าทำได้โดยการส่งต่อผลข้างเคียงเป็นตัวเลือก:

 const services = { getUsers: (context) => fetch( `url/to/users?page=${context.nextPage}` ).then((response) => response.json()) } const fetchUsersMachine = Machine({ ... states: { ... fetching: { invoke: { // Invoke the side effect at key: 'getUsers' in the supplied services object. src: 'getUsers', } on: { RESOLVE: "success", REJECT: "failure", }, }, ... }, // Supply the side effects to be executed on state transitions. { services } });

นอกจากนี้ยังช่วยให้ทำการทดสอบหน่วยของเครื่องสถานะได้ง่าย ทำให้สามารถจำลองการดึงข้อมูลของผู้ใช้ได้อย่างชัดเจน:

 async function testFetchUsers() { return [{ name: "Peter", location: "New Zealand" }]; } const machine = fetchUsersMachine.withConfig({ services: { getUsers: (context) => testFetchUsers(), }, });

แยกเครื่องจักรขนาดใหญ่

ไม่ชัดเจนในทันทีว่าจะจัดโครงสร้างโดเมนของปัญหาให้อยู่ในลำดับชั้นของเครื่องจักรที่มีสถานะจำกัดที่ดีได้อย่างไรเมื่อเริ่มต้น

เคล็ดลับ: ใช้ลำดับชั้นของส่วนประกอบ UI ของคุณเพื่อช่วยแนะนำกระบวนการนี้ ดูส่วนถัดไปเกี่ยวกับวิธีการแมปเครื่องสถานะกับส่วนประกอบ UI

ประโยชน์หลักของการใช้เครื่องของรัฐคือการสร้างแบบจำลองสถานะและช่วงการเปลี่ยนภาพทั้งหมดอย่างชัดเจนระหว่างสถานะในแอปพลิเคชันของคุณ เพื่อให้เข้าใจพฤติกรรมที่เป็นผลลัพธ์ได้อย่างชัดเจน ทำให้มองเห็นข้อผิดพลาดหรือช่องว่างทางตรรกะได้ง่าย

เพื่อให้มันทำงานได้ดี เครื่องจักรจะต้องมีขนาดเล็กและรัดกุม โชคดีที่การเขียนเครื่องของรัฐตามลำดับชั้นนั้นง่าย ในตัวอย่างผังบัญญัติของระบบสัญญาณไฟจราจร สถานะ "สีแดง" จะกลายเป็นเครื่องแสดงสถานะย่อย เครื่องหลัก "ไฟ" ไม่ทราบสถานะภายในของ "สีแดง" แต่ตัดสินใจว่าเมื่อใดจึงจะเข้าสู่ "สีแดง" และลักษณะการทำงานที่ตั้งใจไว้เมื่อออกจากระบบ:

ตัวอย่างสัญญาณไฟจราจรโดยใช้ statecharts
ตัวอย่างสัญญาณไฟจราจรโดยใช้ statecharts

1-1 การแมปเครื่องของรัฐกับส่วนประกอบ UI แบบเก็บสถานะ

ตัวอย่างเช่น ไซต์อีคอมเมิร์ซสมมติที่เรียบง่ายซึ่งมีมุมมอง React ต่อไปนี้:

 <App> <SigninForm /> <RegistrationForm /> <Products /> <Cart /> <Admin> <Users /> <Products /> </Admin> </App>

ขั้นตอนการสร้างเครื่องของรัฐที่สอดคล้องกับมุมมองข้างต้นอาจคุ้นเคยสำหรับผู้ที่ใช้ไลบรารีการจัดการสถานะ Redux:

  1. คอมโพเนนต์มีสถานะที่ต้องสร้างโมเดลหรือไม่ ตัวอย่างเช่น ผู้ดูแลระบบ/ผลิตภัณฑ์ต้องไม่ การดึงเพจไปยังเซิร์ฟเวอร์บวกกับโซลูชันแคช (เช่น SWR) อาจเพียงพอ ในทางกลับกัน ส่วนประกอบ เช่น SignInForm หรือ Cart มักจะมีสถานะที่จำเป็นต้องได้รับการจัดการ เช่น ข้อมูลที่ป้อนลงในฟิลด์หรือเนื้อหาในรถเข็นปัจจุบัน
  2. เทคนิคท้องถิ่น (เช่น setState() / useState() ของ React เพียงพอที่จะจับปัญหาหรือไม่ การติดตามว่าโมดอลแบบป๊อปอัปของรถเข็นเปิดอยู่ในปัจจุบันแทบไม่ต้องใช้เครื่องที่มีสถานะจำกัด
  3. เครื่องสถานะที่เป็นผลลัพธ์มีแนวโน้มที่จะซับซ้อนเกินไปหรือไม่? ถ้าใช่ ให้แยกเครื่องออกเป็นชิ้นเล็กๆ หลายๆ ชิ้น เพื่อระบุโอกาสในการสร้างเครื่องย่อยที่สามารถนำกลับมาใช้ใหม่ได้ในที่อื่น ตัวอย่างเช่น เครื่อง SignInForm และ RegistrationForm อาจเรียกใช้อินสแตนซ์ของ textFieldMachine ลูกเพื่อตรวจสอบความถูกต้องของแบบจำลองและสถานะสำหรับฟิลด์อีเมล ชื่อ และรหัสผ่านของผู้ใช้

เมื่อใดจึงควรใช้แบบจำลองเครื่องจักรที่มีสถานะจำกัด

ในขณะที่ statechart และ FSM สามารถแก้ปัญหาที่ท้าทายบางอย่างได้อย่างสวยงาม การตัดสินใจเลือกเครื่องมือและแนวทางที่ดีที่สุดสำหรับแอปพลิเคชันนั้น ๆ มักจะขึ้นอยู่กับปัจจัยหลายประการ

บางสถานการณ์ที่ใช้เครื่องจักรที่มีสถานะ จำกัด ส่องแสง:

  • แอปพลิเคชันของคุณมีองค์ประกอบการป้อนข้อมูลจำนวนมาก ที่การเข้าถึงหรือการมองเห็นฟิลด์ถูกควบคุมโดยกฎที่ซับซ้อน: ตัวอย่างเช่น การป้อนแบบฟอร์มในแอปเคลมประกัน ที่นี่ FSM ช่วยให้แน่ใจว่ากฎเกณฑ์ทางธุรกิจได้รับการปฏิบัติอย่างแข็งแกร่ง นอกจากนี้ สามารถใช้คุณลักษณะการแสดงภาพของ statechart เพื่อช่วยเพิ่มความร่วมมือกับผู้มีส่วนได้ส่วนเสียที่ไม่ใช่ด้านเทคนิค และระบุข้อกำหนดทางธุรกิจโดยละเอียดในช่วงต้นของการพัฒนา
  • เพื่อให้ทำงานได้ดีขึ้นในการเชื่อมต่อที่ช้ากว่าและมอบประสบการณ์ที่มีความเที่ยงตรงสูงแก่ผู้ใช้ เว็บแอปต้องจัดการโฟลว์ข้อมูล async ที่ซับซ้อนมากขึ้น FSM จำลองสถานะทั้งหมดที่แอปพลิเคชันสามารถอยู่ได้อย่างชัดเจน และแผนภูมิสถานะสามารถมองเห็นได้เพื่อช่วยในการวินิจฉัยและแก้ปัญหาข้อมูลแบบอะซิงโครนัส
  • แอปพลิเคชันที่ต้องใช้แอนิเมชั่นแบบ state-based ที่ซับซ้อนมาก สำหรับแอนิเมชั่นที่ซับซ้อน เทคนิคสำหรับการสร้างโมเดลแอนิเมชั่นเป็นกระแสเหตุการณ์ตลอดเวลาด้วย RxJS นั้นเป็นที่นิยม สำหรับหลาย ๆ สถานการณ์ วิธีนี้ใช้ได้ผลดี แต่เมื่อแอนิเมชั่นที่มีเนื้อหาสมบูรณ์ถูกรวมเข้ากับชุดสถานะที่รู้จักที่ซับซ้อน FSM จะให้ "จุดพัก" ที่กำหนดไว้อย่างดีซึ่งแอนิเมชันจะไหลไปมา FSM ที่รวมกับ RxJS ดูเหมือนจะเป็นการผสมผสานที่ลงตัวเพื่อช่วยส่งมอบประสบการณ์ผู้ใช้ที่มีความเที่ยงตรงสูงและแสดงออกถึงความเที่ยงตรงสูง
  • แอปพลิเคชันไคลเอนต์ ที่หลากหลาย เช่น การตัดต่อรูปภาพหรือวิดีโอ เครื่องมือสร้างไดอะแกรม หรือเกมที่ตรรกะทางธุรกิจส่วนใหญ่อยู่ในฝั่งไคลเอ็นต์ FSM นั้นแยกจากเฟรมเวิร์ก UI หรือไลบรารีโดยเนื้อแท้และง่ายต่อการเขียนการทดสอบเพื่อให้สามารถทำซ้ำแอปพลิเคชันคุณภาพสูงได้อย่างรวดเร็วและจัดส่งด้วยความมั่นใจ

คำเตือนเครื่องไฟไนต์สเตท

  • แนวทางทั่วไป แนวทางปฏิบัติที่ดีที่สุด และ API สำหรับไลบรารี statechart เช่น XState นั้นแปลกใหม่สำหรับนักพัฒนาฟรอนต์เอนด์ส่วนใหญ่ ซึ่งต้องใช้เวลาและทรัพยากรในการทุ่มเทเวลาเพื่อให้เกิดประสิทธิผล โดยเฉพาะสำหรับทีมที่มีประสบการณ์น้อย
  • คล้ายกับข้อแม้ก่อนหน้านี้ ในขณะที่ความนิยมของ XState ยังคงเติบโตและมีการบันทึกเป็นอย่างดี ไลบรารีการจัดการสถานะที่มีอยู่ เช่น Redux, MobX หรือ React Context มีการติดตามจำนวนมากที่ให้ข้อมูลออนไลน์มากมายที่ XState ยังไม่ตรงกัน
  • สำหรับแอปพลิเคชันที่ทำตามโมเดล CRUD ที่ง่ายกว่า เทคนิคการจัดการสถานะที่มีอยู่รวมกับไลบรารีแคชทรัพยากรที่ดี เช่น SWR หรือ React Query ก็เพียงพอแล้ว ในที่นี้ ข้อจำกัดเพิ่มเติมที่ FSM มีให้ แม้ว่าแอปที่ซับซ้อนจะมีประโยชน์อย่างเหลือเชื่อ แต่ก็อาจทำให้การพัฒนาช้าลงได้
  • เครื่องมือนี้มีความสมบูรณ์น้อยกว่าไลบรารีการจัดการสถานะอื่น ๆ โดยที่งานยังคงดำเนินการเกี่ยวกับการสนับสนุน TypeScript ที่ได้รับการปรับปรุงและส่วนขยาย devtools ของเบราว์เซอร์

ห่อ

ความนิยมและการยอมรับของโปรแกรมการประกาศในชุมชนการพัฒนาเว็บยังคงเพิ่มขึ้นอย่างต่อเนื่อง

ในขณะที่การพัฒนาเว็บสมัยใหม่ยังคงมีความซับซ้อนมากขึ้น ไลบรารีและเฟรมเวิร์กที่นำแนวทางการเขียนโปรแกรมแบบเปิดเผยมาใช้นั้นมีความถี่เพิ่มขึ้น เหตุผลดูเหมือนชัดเจน—ต้องสร้างแนวทางที่ง่ายกว่าและอธิบายได้มากกว่าในการเขียนซอฟต์แวร์

การใช้ภาษาที่พิมพ์อย่างเข้มงวด เช่น TypeScript ช่วยให้สามารถกำหนดโมเดลเอนทิตีในโดเมนแอปพลิเคชันได้กระชับและชัดเจน ซึ่งช่วยลดโอกาสของข้อผิดพลาดและจำนวนโค้ดตรวจสอบที่มีแนวโน้มผิดพลาดที่ต้องจัดการ การนำเครื่อง finite-state และ statecharts มาใช้ในส่วนหน้าช่วยให้นักพัฒนาสามารถประกาศตรรกะทางธุรกิจของแอปพลิเคชันผ่านการเปลี่ยนสถานะ ทำให้สามารถพัฒนาเครื่องมือสร้างภาพข้อมูลที่สมบูรณ์ และเพิ่มโอกาสในการทำงานร่วมกันอย่างใกล้ชิดกับผู้ที่ไม่ใช่นักพัฒนา

เมื่อเราทำเช่นนี้ เราจะเปลี่ยนโฟกัสจากวิธีการทำงานของแอปพลิเคชันไปเป็นมุมมองในระดับที่สูงขึ้น ซึ่งช่วยให้เรามุ่งความสนใจไปที่ความต้องการของลูกค้าได้มากยิ่งขึ้นและสร้างมูลค่าที่ยั่งยืน