ปลดล็อกการตรวจสอบความถูกต้องแบบก้าวหน้าสำหรับฟอร์มหลายขั้นตอนใน React เรียนรู้วิธีใช้ useFormState เพื่อประสบการณ์ผู้ใช้ที่ราบรื่นและผสานรวมกับเซิร์ฟเวอร์
React useFormState: กลไกการตรวจสอบความถูกต้องสำหรับฟอร์มหลายขั้นตอนเจาะลึก
ในโลกของการพัฒนาเว็บสมัยใหม่ การสร้างประสบการณ์ผู้ใช้ที่ใช้งานง่ายและแข็งแกร่งเป็นสิ่งสำคัญยิ่ง ไม่มีที่ไหนจะสำคัญไปกว่าฟอร์มซึ่งเป็นประตูหลักสำหรับการโต้ตอบของผู้ใช้ แม้ว่าฟอร์มติดต่อแบบง่ายๆ จะตรงไปตรงมา แต่ความซับซ้อนจะเพิ่มขึ้นอย่างมากกับฟอร์มหลายขั้นตอน เช่น วิซาร์ดการลงทะเบียนผู้ใช้ การชำระเงินอีคอมเมิร์ซ หรือแผงการกำหนดค่าโดยละเอียด กระบวนการหลายขั้นตอนเหล่านี้ก่อให้เกิดความท้าทายที่สำคัญในการจัดการสถานะ การตรวจสอบความถูกต้อง และการรักษาโฟลว์ผู้ใช้ที่ราบรื่น ในอดีต นักพัฒนาได้จัดการสถานะฝั่งไคลเอ็นต์ที่ซับซ้อน ผู้ให้บริการบริบท และไลบรารีของบุคคลที่สามเพื่อควบคุมความซับซ้อนนี้
ขอแนะนำ hook `useFormState` ของ React hook อันทรงพลังนี้ซึ่งเปิดตัวในฐานะส่วนหนึ่งของการพัฒนา React สู่คอมโพเนนต์ที่ผสานรวมกับเซิร์ฟเวอร์ นำเสนอโซลูชันที่คล่องตัวและสง่างามสำหรับการจัดการสถานะฟอร์มและการตรวจสอบความถูกต้อง โดยเฉพาะอย่างยิ่งในบริบทของฟอร์มหลายขั้นตอน ด้วยการรวมเข้ากับ Server Actions โดยตรง `useFormState` จะสร้างกลไกการตรวจสอบความถูกต้องที่แข็งแกร่งซึ่งทำให้โค้ดง่ายขึ้น เพิ่มประสิทธิภาพ และส่งเสริม progressive enhancement บทความนี้ให้คำแนะนำที่ครอบคลุมสำหรับนักพัฒนาทั่วโลกเกี่ยวกับวิธีการออกแบบกลไกการตรวจสอบความถูกต้องหลายขั้นตอนที่ซับซ้อนโดยใช้ `useFormState` เพื่อแปลงงานที่ซับซ้อนให้เป็นกระบวนการที่จัดการได้และปรับขนาดได้
ความท้าทายที่ยั่งยืนของฟอร์มหลายขั้นตอน
ก่อนที่จะเจาะลึกโซลูชัน เป็นสิ่งสำคัญที่จะต้องเข้าใจจุดเจ็บปวดทั่วไปที่นักพัฒนาประสบกับฟอร์มหลายขั้นตอน ความท้าทายเหล่านี้ไม่ใช่เรื่องเล็กน้อยและสามารถส่งผลกระทบต่อทุกสิ่งตั้งแต่เวลาในการพัฒนาไปจนถึงประสบการณ์ของผู้ใช้
- ความซับซ้อนของการจัดการสถานะ: คุณจะคงข้อมูลไว้ได้อย่างไรขณะที่ผู้ใช้ไปยังขั้นตอนต่างๆ สถานะควรอยู่ที่คอมโพเนนต์ระดับบนสุด บริบทส่วนกลาง หรือที่จัดเก็บในเครื่องหรือไม่? แต่ละวิธีมีข้อดีข้อเสีย ซึ่งมักนำไปสู่การส่ง prop ที่ซับซ้อนหรือตรรกะการซิงโครไนซ์สถานะที่ซับซ้อน
- การแยกตรรกะการตรวจสอบความถูกต้อง: การตรวจสอบความถูกต้องควรเกิดขึ้นที่ไหน? การตรวจสอบทุกอย่างในตอนท้ายจะให้ประสบการณ์ผู้ใช้ที่ไม่ดี การตรวจสอบในแต่ละขั้นตอนจะดีกว่า แต่บ่อยครั้งนี้ต้องการการเขียนตรรกะการตรวจสอบความถูกต้องที่แยกจากกัน ทั้งในฝั่งไคลเอ็นต์ (เพื่อการตอบสนองทันที) และในฝั่งเซิร์ฟเวอร์ (เพื่อความปลอดภัยและความสมบูรณ์ของข้อมูล)
- อุปสรรคด้านประสบการณ์ผู้ใช้: ผู้ใช้คาดหวังว่าจะสามารถย้ายไปมาระหว่างขั้นตอนต่างๆ ได้โดยไม่สูญเสียข้อมูล พวกเขายังคาดหวังข้อความแสดงข้อผิดพลาดที่ชัดเจนและเหมาะสมกับบริบท และการตอบสนองทันที การสร้างประสบการณ์ที่ลื่นไหลนี้อาจเกี่ยวข้องกับโค้ด boilerplate จำนวนมาก
- การซิงโครไนซ์สถานะเซิร์ฟเวอร์-ไคลเอ็นต์: แหล่งความจริงสูงสุดมักจะเป็นเซิร์ฟเวอร์ การรักษาให้สถานะฝั่งไคลเอ็นต์ซิงโครไนซ์อย่างสมบูรณ์กับกฎการตรวจสอบความถูกต้องและตรรกะทางธุรกิจฝั่งเซิร์ฟเวอร์เป็นการต่อสู้ที่ต่อเนื่อง บ่อยครั้งนำไปสู่การคัดลอกโค้ดและความไม่สอดคล้องกันที่เป็นไปได้
ความท้าทายเหล่านี้เน้นย้ำถึงความต้องการแนวทางที่ผสานรวมและสอดคล้องกันมากขึ้น ซึ่งเชื่อมช่องว่างระหว่างฝั่งไคลเอ็นต์และฝั่งเซิร์ฟเวอร์ นี่คือจุดที่ `useFormState` โดดเด่น
รู้จักกับ `useFormState`: แนวทางที่ทันสมัยในการจัดการฟอร์ม
hook `useFormState` ถูกออกแบบมาเพื่อจัดการสถานะฟอร์มที่อัปเดตตามผลลัพธ์ของการดำเนินการฟอร์ม เป็นเสาหลักของวิสัยทัศน์ของ React สำหรับแอปพลิเคชันที่ได้รับการปรับปรุงแบบก้าวหน้าซึ่งทำงานได้อย่างราบรื่นทั้งที่มีและไม่มี JavaScript เปิดใช้งานบนฝั่งไคลเอ็นต์
`useFormState` คืออะไร?
โดยพื้นฐานแล้ว `useFormState` คือ React Hook ที่รับอาร์กิวเมนต์สองตัว: ฟังก์ชัน server action และสถานะเริ่มต้น จะคืนค่าอาร์เรย์ที่มีสองค่า: สถานะปัจจุบันของฟอร์ม และฟังก์ชัน action ใหม่ที่จะส่งไปยัง element `
);
}
ขั้นตอนที่ 1: การจับและการตรวจสอบข้อมูลส่วนบุคคล
ในขั้นตอนนี้ เราต้องการตรวจสอบเฉพาะช่อง `name` และ `email` เท่านั้น เราจะใช้ input ที่ซ่อนอยู่ `_step` เพื่อบอก server action ของเราว่าจะต้องใช้ตรรกะการตรวจสอบความถูกต้องใด
// Step1.jsx component
{state.errors.name} {state.errors.email}
export function Step1({ state }) {
return (
ขั้นตอนที่ 1: ข้อมูลส่วนบุคคล
{state.errors?.name &&
{state.errors?.email &&
);
}
ตอนนี้ มาอัปเดต server action ของเราเพื่อจัดการการตรวจสอบความถูกต้องสำหรับขั้นตอนที่ 1
// actions.js (updated)
// ... (imports and schema definition)
export async function onbordingAction(prevState, formData) {
// ... (get form data)
const step = Number(formData.get('_step'));
if (step === 1) {
const validatedFields = schema.pick({ name: true, email: true }).safeParse({ name, email });
if (!validatedFields.success) {
return {
...currentState,
step: 1,
errors: validatedFields.error.flatten().fieldErrors,
};
}
// Success, move to next step
return {
...currentState,
step: 2,
errors: {},
};
}
// ... (logic for other steps)
}
เมื่อผู้ใช้คลิก "ถัดไป" ฟอร์มจะถูกส่ง Server action จะตรวจสอบว่ามันคือขั้นตอนที่ 1 ตรวจสอบ เฉพาะ ช่อง `name` และ `email` โดยใช้เมธอด `pick` ของ Zod และคืนค่าสถานะใหม่ หากการตรวจสอบความถูกต้องล้มเหลว จะคืนค่าข้อผิดพลาดและยังคงอยู่ที่ขั้นตอนที่ 1 หากสำเร็จ จะล้างข้อผิดพลาดและอัปเดต `step` เป็น 2 ทำให้คอมโพเนนต์ `OnboardingForm` หลักของเรา render คอมโพเนนต์ `Step2`
ขั้นตอนที่ 2: การตรวจสอบความถูกต้องแบบก้าวหน้าสำหรับรายละเอียดบริษัท
ความสวยงามของแนวทางนี้คือสถานะจากขั้นตอนที่ 1 จะถูกส่งต่อโดยอัตโนมัติ เราเพียงแค่ต้อง render ในช่องที่ซ่อนอยู่เพื่อให้รวมอยู่ในการส่งฟอร์มครั้งต่อไป
// Step2.jsx component
{state.errors.companyName} {state.errors.role}
export function Step2({ state }) {
return (
ขั้นตอนที่ 2: รายละเอียดบริษัท
{/* Persist data from previous step */}
{state.errors?.companyName &&
{state.errors?.role &&
);
}
และเราอัปเดต server action เพื่อจัดการขั้นตอนที่ 2
// actions.js (updated)
// ...
if (step === 2) {
const validatedFields = schema.pick({ companyName: true, role: true }).safeParse({ companyName, role });
if (!validatedFields.success) {
return {
...currentState,
step: 2,
errors: validatedFields.error.flatten().fieldErrors,
};
}
// Success, move to final review
return {
...currentState,
step: 3,
errors: {},
};
}
// ...
ตรรกะจะเหมือนกับขั้นตอนที่ 1 แต่จะกำหนดเป้าหมายช่องสำหรับขั้นตอนที่ 2 hook `useFormState` จัดการการเปลี่ยนผ่านได้อย่างราบรื่น โดยคงข้อมูลทั้งหมดไว้และมอบโฟลว์การตรวจสอบความถูกต้องแบบก้าวหน้าและชัดเจน
ขั้นตอนที่ 3: การตรวจสอบขั้นสุดท้ายและการส่ง
ในขั้นตอนสุดท้าย เราจะแสดงข้อมูลที่รวบรวมทั้งหมดเพื่อให้ผู้ใช้ตรวจสอบ การส่งขั้นสุดท้ายจะเรียกใช้การตรวจสอบความถูกต้องที่ครอบคลุมของทุกช่องก่อนที่เราจะบันทึกข้อมูลไปยังฐานข้อมูล
// Step3.jsx component
{state.message} {state.message}
export function Step3({ state }) {
return (
ขั้นตอนที่ 3: ยืนยันรายละเอียด
{state.message && state.message.startsWith('Success') &&
{state.message && state.message.startsWith('Error') &&
);
}
ตรรกะ server action สุดท้ายจะดำเนินการตรวจสอบความถูกต้องทั้งหมดและตรรกะทางธุรกิจขั้นสุดท้าย
// actions.js (final version)
// ...
if (step === 3) {
// Final, full validation
const validatedFields = schema.safeParse({ name, email, companyName, role });
if (!validatedFields.success) {
// Should not happen if step-by-step validation is correct, but a good safeguard
return {
...currentState,
step: 1, // Send user back to the first step with errors
errors: validatedFields.error.flatten().fieldErrors,
message: 'Error: Invalid data found. Please review.'
};
}
try {
// console.log('Submitting to database:', validatedFields.data);
// await saveToDatabase(validatedFields.data);
return { message: 'Success! Your onboarding is complete.', step: 4 }; // A final success step
} catch (dbError) {
return { ...currentState, step: 3, message: 'Error: Could not save data.' };
}
}
// ...
ด้วยสิ่งนี้ เรามีฟอร์มหลายขั้นตอนที่สมบูรณ์ แข็งแกร่ง พร้อมการตรวจสอบความถูกต้องแบบก้าวหน้าที่มีอำนาจจากเซิร์ฟเวอร์ ทั้งหมดถูกจัดการอย่างสะอาดโดย hook `useFormState`
กลยุทธ์ขั้นสูงสำหรับประสบการณ์ผู้ใช้ระดับโลก
การสร้างฟอร์มที่ใช้งานได้เป็นสิ่งหนึ่ง การทำให้การใช้งานเป็นเรื่องน่าพอใจเป็นอีกสิ่งหนึ่ง นี่คือเทคนิคขั้นสูงเพื่อยกระดับฟอร์มหลายขั้นตอนของคุณ
การจัดการการนำทาง: การเลื่อนไปมา
ตรรกะปัจจุบันของเราเคลื่อนไปข้างหน้าเท่านั้น ในการอนุญาตให้ผู้ใช้ย้อนกลับ เราไม่สามารถใช้ปุ่ม `type="submit"` แบบง่ายๆ ได้ แทนที่จะทำเช่นนั้น เราจะจัดการขั้นตอนในสถานะของคอมโพเนนต์ฝั่งไคลเอ็นต์ และใช้ server action สำหรับการดำเนินการไปข้างหน้าเท่านั้น อย่างไรก็ตาม วิธีที่ง่ายกว่าที่ยังคงรูปแบบที่เน้นเซิร์ฟเวอร์เป็นศูนย์กลางคือการมีปุ่ม "ย้อนกลับ" ซึ่งยังคงส่งฟอร์ม แต่มีความตั้งใจที่แตกต่างออกไป
// In a step component...
// In the server action...
const intent = formData.get('intent');
if (intent === 'back') {
return { ...currentState, step: step - 1, errors: {} };
}
การให้การตอบสนองทันทีด้วย `useFormStatus`
hook `useFormStatus` ให้สถานะที่กำลังดำเนินการของการส่งฟอร์มภายใน `
// SubmitButton.jsx
'use client';
import { useFormStatus } from 'react-dom';
export function SubmitButton({ text }) {
const { pending } = useFormStatus();
return (
{pending ? 'กำลังส่ง...' : text}
);
}
คุณสามารถใช้ `
การจัดโครงสร้าง Server Action ของคุณเพื่อความสามารถในการปรับขนาด
เมื่อฟอร์มของคุณใหญ่ขึ้น โซ่ `if/else if` ใน server action อาจจะยุ่งเหยิง การใช้ `switch` statement หรือรูปแบบที่เป็นโมดูลาร์มากขึ้นแนะนำสำหรับการจัดระเบียบที่ดีขึ้น
// actions.js with a switch statement
switch (step) {
case 1:
// Handle Step 1 validation
break;
case 2:
// Handle Step 2 validation
break;
// ... etc
}
ความสามารถในการเข้าถึง (a11y) เป็นสิ่งที่ขาดไม่ได้
สำหรับผู้ชมทั่วโลก ความสามารถในการเข้าถึงเป็นสิ่งที่ต้องมี ตรวจสอบให้แน่ใจว่าฟอร์มของคุณสามารถเข้าถึงได้โดย:
- การใช้ `aria-invalid="true"` กับช่อง input ที่มีข้อผิดพลาด
- การเชื่อมโยงข้อความแสดงข้อผิดพลาดกับ input โดยใช้ `aria-describedby`
- การจัดการโฟกัสอย่างเหมาะสมหลังจากการส่ง โดยเฉพาะอย่างยิ่งเมื่อข้อผิดพลาดปรากฏขึ้น
- การตรวจสอบให้แน่ใจว่าการควบคุมฟอร์มทั้งหมดสามารถนำทางด้วยคีย์บอร์ดได้
มุมมองทั่วโลก: การแปลเป็นภาษาต่างๆ และ `useFormState`
ข้อได้เปรียบที่สำคัญอย่างหนึ่งของการตรวจสอบความถูกต้องที่ขับเคลื่อนโดยเซิร์ฟเวอร์คือความสะดวกในการแปลเป็นภาษาต่างๆ (i18n) ข้อความการตรวจสอบความถูกต้องไม่จำเป็นต้องถูก hardcode ในฝั่งไคลเอ็นต์อีกต่อไป Server action สามารถตรวจจับภาษาที่ผู้ใช้ต้องการ (จากส่วนหัวเช่น `Accept-Language`, พารามิเตอร์ URL หรือการตั้งค่าโปรไฟล์ผู้ใช้) และคืนค่าข้อผิดพลาดในภาษาแม่ของพวกเขา
ตัวอย่างเช่น การใช้ไลบรารีเช่น `i18next` บนเซิร์ฟเวอร์:
// Server action with i18n
import { i18n } from 'your-i18n-config';
// ...
const t = await i18n.getFixedT(userLocale); // e.g., 'es' for Spanish
const schema = z.object({
email: z.string().email(t('errors.invalid_email')),
});
แนวทางนี้ช่วยให้แน่ใจว่าผู้ใช้ทั่วโลกได้รับข้อเสนอแนะที่ชัดเจนและเข้าใจได้ ซึ่งจะช่วยเพิ่มการรวมและการใช้งานแอปพลิเคชันของคุณอย่างมาก
`useFormState` เทียบกับไลบรารีฝั่งไคลเอ็นต์: การเปรียบเทียบ
รูปแบบนี้เปรียบเทียบกับไลบรารีที่จัดตั้งขึ้น เช่น Formik หรือ React Hook Form อย่างไร? ไม่ใช่เรื่องของว่าอะไรดีกว่า แต่เป็นเรื่องของว่าอะไรเหมาะสมกับงาน
- ไลบรารีฝั่งไคลเอ็นต์ (Formik, React Hook Form): สิ่งเหล่านี้ยอดเยี่ยมสำหรับฟอร์มที่ซับซ้อน มีการโต้ตอบสูง ซึ่งการตอบสนองทันทีจากฝั่งไคลเอ็นต์เป็นสิ่งสำคัญสูงสุด พวกเขานำเสนอชุดเครื่องมือที่ครอบคลุมสำหรับการจัดการสถานะฟอร์ม การตรวจสอบความถูกต้อง และการส่งทั้งหมดภายในเบราว์เซอร์ ความท้าทายหลักของพวกเขาสามารถเป็นเรื่องของการคัดลอกตรรกะการตรวจสอบความถูกต้องระหว่างฝั่งไคลเอ็นต์และเซิร์ฟเวอร์
- `useFormState` กับ Server Actions: แนวทางนี้จะโดดเด่นในกรณีที่เซิร์ฟเวอร์เป็นแหล่งความจริงสูงสุด มันทำให้สถาปัตยกรรมโดยรวมง่ายขึ้นโดยการรวมตรรกะไว้ที่ส่วนกลาง รับประกันความถูกต้องของข้อมูล และทำงานได้อย่างราบรื่นกับการปรับปรุงแบบก้าวหน้า ข้อแลกเปลี่ยนคือการเดินทางไปกลับเครือข่ายสำหรับการตรวจสอบความถูกต้อง แม้ว่าด้วยโครงสร้างพื้นฐานที่ทันสมัย สิ่งนี้มักจะไม่มีนัยสำคัญ
สำหรับฟอร์มหลายขั้นตอนที่เกี่ยวข้องกับตรรกะทางธุรกิจที่สำคัญหรือข้อมูลที่ต้องได้รับการตรวจสอบกับฐานข้อมูล (เช่น การตรวจสอบว่าชื่อผู้ใช้ถูกนำไปใช้แล้วหรือไม่) รูปแบบ `useFormState` นำเสนอสถาปัตยกรรมที่ตรงไปตรงมาและมีข้อผิดพลาดน้อยกว่า
บทสรุป: อนาคตของฟอร์มใน React
hook `useFormState` เป็นมากกว่า API ใหม่ มันแสดงถึงการเปลี่ยนแปลงทางปรัชญาในวิธีที่เราสร้างฟอร์มใน React ด้วยการยอมรับรูปแบบที่เน้นเซิร์ฟเวอร์เป็นศูนย์กลาง เราสามารถสร้างฟอร์มหลายขั้นตอนที่แข็งแกร่ง ปลอดภัย เข้าถึงได้ และบำรุงรักษาได้ง่ายขึ้น รูปแบบนี้จะขจัดหมวดหมู่ข้อบกพร่องทั้งหมดที่เกี่ยวข้องกับการซิงโครไนซ์สถานะ และมอบโครงสร้างที่ชัดเจนและปรับขนาดได้สำหรับการจัดการโฟลว์ผู้ใช้ที่ซับซ้อน
ด้วยการสร้างกลไกการตรวจสอบความถูกต้องด้วย `useFormState` คุณไม่ได้เพียงแค่จัดการสถานะ แต่คุณกำลังออกแบบกระบวนการรวบรวมข้อมูลที่ยืดหยุ่น ใช้งานง่าย ซึ่งยึดตามหลักการของการพัฒนาเว็บสมัยใหม่ สำหรับนักพัฒนาที่สร้างแอปพลิเคชันสำหรับผู้ชมทั่วโลก hook อันทรงพลังนี้จะมอบรากฐานสำหรับการสร้างประสบการณ์ผู้ใช้ระดับโลกอย่างแท้จริง