ทำไมต้องคุม State และจัดการ Form ด้วย TypeScript
ถ้าเปรียบการเขียนเว็บเป็นเหมือนการทำร้านอาหาร State (ข้อมูลที่เปลี่ยนแปลงได้ในหน้าจอ) ก็คือออเดอร์ของลูกค้าที่เปลี่ยนไปมาตลอดเวลา ถ้าพนักงานจดออเดอร์ผิดหรือลืมแก้ไขเมนูตามที่ลูกค้าสั่ง ร้านก็จะวุ่นวายและส่งอาหารผิดโต๊ะ การใช้ TypeScript (ภาษาที่ช่วยตรวจสอบความถูกต้องของข้อมูลก่อนรันโค้ด) เข้ามาช่วย จะเหมือนการมีระบบเช็กบิลอัตโนมัติที่คอยเตือนทันทีถ้าเราจดข้อมูลผิดประเภท
มือใหม่หลายคนมักมองข้ามเรื่องการจัดการข้อมูลให้มีระเบียบ ทำให้พอโปรเจกต์เริ่มใหญ่ขึ้น โค้ดจะกลายเป็น Spaghetti Code (โค้ดที่พันกันยุ่งเหยิงจนแก้ไม่ได้) การกำหนด Type (รูปแบบข้อมูลที่ชัดเจน) ตั้งแต่ต้นจะช่วยให้เราทำงานได้เร็วขึ้น เพราะโปรแกรมจะคอยเตือนเราทันทีเมื่อใส่ข้อมูลไม่ตรงกับที่ตกลงกันไว้
การจัดการ Form (แบบฟอร์มรับข้อมูล) คือจุดที่มือใหม่มักจะพลาดบ่อยที่สุด เพราะต้องรับค่าจากผู้ใช้และตรวจสอบความถูกต้อง เราจะมาเรียนรู้วิธีทำให้มันปลอดภัยด้วยการใช้ Interface (แบบแปลนที่กำหนดโครงสร้างของข้อมูล) เพื่อให้มั่นใจว่าทุกฟิลด์ในฟอร์มมีค่าที่ถูกต้องและครบถ้วนเสมอ
สร้าง Form ที่ปลอดภัยด้วย React Hook Form
การเขียนฟอร์มแบบเดิมใน React (ไลบรารีสำหรับสร้างหน้าจอเว็บ) มักจะทำให้หน้าจอรีเฟรชบ่อยเกินความจำเป็น เราจึงต้องใช้เครื่องมือที่ชื่อว่า React Hook Form (เครื่องมือช่วยจัดการฟอร์มให้ทำงานไวขึ้น) ซึ่งจะเข้ามาช่วยลดภาระการเขียนโค้ดซ้ำซากและจัดการสถานะของฟอร์มได้อย่างเป็นระเบียบ
เราจะเริ่มจากการกำหนด Schema (โครงสร้างข้อมูลที่ต้องมี) เพื่อบอกว่าในฟอร์มของเราต้องมีช่องอะไรบ้าง เช่น ชื่อ หรือ อีเมล การทำแบบนี้ทำให้เราไม่ต้องคอยมานั่งไล่เช็กทีละช่องว่ามีค่าหรือยัง เพราะ TypeScript จะช่วยเราตรวจเช็กตั้งแต่ตอนพิมพ์โค้ดเลย
ลองดูตัวอย่างการสร้างฟอร์มรับข้อมูลผู้ใช้แบบง่ายๆ ที่มีการตรวจสอบความถูกต้องของข้อมูลกันครับ
import { useForm } from "react-hook-form";
// กำหนดรูปแบบข้อมูลที่ฟอร์มต้องมี
interface UserForm {
name: string;
email: string;
}
function MyForm() {
// เรียกใช้ hook สำหรับจัดการฟอร์ม
const { register, handleSubmit } = useForm<UserForm>();
// ฟังก์ชันเมื่อกดปุ่มบันทึก
const onSubmit = (data: UserForm) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")} placeholder="ชื่อของคุณ" />
<input {...register("email")} placeholder="อีเมลของคุณ" />
<button type="submit">บันทึก</button>
</form>
);
}
โค้ดด้านบนใช้ useForm เพื่อผูกค่าจากช่องกรอกข้อมูลเข้ากับตัวแปร ถ้าเราลืมพิมพ์ชื่อช่องให้ตรงกับ UserForm โปรแกรมจะแจ้งเตือนทันที ทำให้เราลดโอกาสเกิดบั๊ก (ข้อผิดพลาดของโปรแกรม) ได้มหาศาลครับ
เชื่อมต่อ API ให้แม่นยำด้วย TypeScript
เมื่อเราได้ข้อมูลจากฟอร์มแล้ว ขั้นตอนต่อไปคือการส่งข้อมูลไปหา API (ช่องทางสื่อสารระหว่างหน้าเว็บกับเซิร์ฟเวอร์) การดึงข้อมูลมาแสดงผลโดยไม่รู้ว่าข้อมูลหน้าตาเป็นอย่างไร คือสาเหตุหลักที่ทำให้เว็บพังเวลาข้อมูลมาไม่ครบหรือผิดรูปแบบ
เราจะใช้ Fetch API (คำสั่งมาตรฐานในการดึงข้อมูลจากอินเทอร์เน็ต) เพื่อสื่อสารกับเซิร์ฟเวอร์ และใช้ Generics (การสร้างโค้ดที่รองรับข้อมูลหลายประเภทแต่ยังคงความปลอดภัยไว้) เพื่อบอกให้โปรแกรมรู้ว่า ข้อมูลที่ได้รับกลับมาจะต้องมีหน้าตาเป็นแบบไหนเสมอ
นี่คือตัวอย่างการดึงข้อมูลรายชื่อผู้ใช้จากเซิร์ฟเวอร์แบบปลอดภัยครับ
interface User {
id: number;
name: string;
}
// ฟังก์ชันดึงข้อมูลแบบระบุ Type
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
return res.json(); // แปลงข้อมูลที่ได้เป็น JSON
}
การกำหนด Promise<User> ช่วยให้เรามั่นใจว่าถ้าเราเรียกใช้ฟังก์ชันนี้ เราจะได้ข้อมูลที่มี id และ name กลับมาแน่นอน ถ้าเซิร์ฟเวอร์ส่งข้อมูลมาผิดรูปแบบ โปรแกรมจะฟ้องทันที ช่วยให้เราแก้ปัญหาได้ก่อนที่ผู้ใช้จะเห็น
จัดการ Global State ด้วย Context API
ในแอปพลิเคชันที่มีหลายหน้าจอ บางครั้งเราต้องใช้ข้อมูลเดียวกันในหลายที่ เช่น ข้อมูลผู้ใช้ที่ล็อกอินอยู่ การส่งข้อมูลผ่าน Props (การส่งค่าจากพ่อไปลูก) ในทุกๆ ชั้นจะทำให้โค้ดอ่านยากมาก เราจึงต้องใช้ Context API (ตัวเก็บข้อมูลส่วนกลางที่เข้าถึงได้จากทุกที่)
การใช้ Global State (สถานะที่แชร์กันทั่วทั้งโปรเจกต์) ต้องระวังเรื่องความซับซ้อน อย่าพยายามเก็บทุกอย่างไว้ที่นี่ ให้เก็บเฉพาะข้อมูลที่จำเป็นต้องใช้ร่วมกันจริงๆ เท่านั้น เพื่อไม่ให้แอปฯ ของเราทำงานช้าลงโดยไม่จำเป็น
ข้อควรระวังคือการลืมใส่ Provider (ตัวห่อหุ้มที่ประกาศว่าใครบ้างที่เข้าถึงข้อมูลได้) ซึ่งถ้าลืมใส่จะทำให้เกิดข้อผิดพลาดเวลาเรียกใช้ข้อมูล แนะนำให้สร้าง Custom Hook (ฟังก์ชันพิเศษที่สร้างขึ้นเองเพื่อเรียกใช้ Context) เพื่อดึงข้อมูลมาใช้งานได้ง่ายและปลอดภัยขึ้นครับ
จัดการ Error ให้เป็นระบบแบบมือโปร
โปรแกรมเมอร์มือใหม่มักลืมเรื่อง Error Handling (การจัดการเมื่อเกิดข้อผิดพลาด) จนทำให้เว็บค้างหรือพังเวลาอินเทอร์เน็ตหลุดหรือเซิร์ฟเวอร์ตอบกลับมาไม่สำเร็จ เราต้องเตรียมตัวรับมือกับสถานการณ์เหล่านี้ไว้เสมอ
เราควรใช้ try...catch (คำสั่งดักจับความผิดพลาด) ทุกครั้งที่เรียก API เพื่อให้โปรแกรมยังทำงานต่อได้ แทนที่จะพังไปเลย นอกจากนี้การแสดง UI Feedback (ข้อความแจ้งเตือนที่หน้าจอ) ให้ผู้ใช้รู้ว่าเกิดอะไรขึ้น เป็นเรื่องสำคัญมากสำหรับประสบการณ์ใช้งานที่ดี
ตัวอย่างการดักจับข้อผิดพลาดเมื่อดึงข้อมูลครับ
async function loadData() {
try {
const data = await fetchUser(1);
console.log(data);
} catch (error) {
// แสดงข้อความเมื่อดึงข้อมูลไม่สำเร็จ
alert("เกิดข้อผิดพลาด กรุณาลองใหม่ภายหลัง");
}
}
การใส่ try...catch จะช่วยให้แอปฯ ของเราดูเป็นมืออาชีพ เพราะผู้ใช้จะไม่เจอหน้าจอขาวๆ ที่ค้างอยู่เฉยๆ แต่จะได้รับคำแนะนำว่าต้องทำอย่างไรต่อไปครับ
สรุป: เอาไปใช้จริงในโปรเจกต์แรก
ทั้งหมดที่พูดมาคือหัวใจสำคัญของการเขียนเว็บในปัจจุบัน ถ้าคุณกำลังทำ Portfolio (แฟ้มสะสมผลงาน) หรือโปรเจกต์แรก ให้เริ่มจากการทำฟอร์มง่ายๆ ก่อน แล้วค่อยๆ เชื่อมต่อ API โดยกำหนด Type ให้ครบทุกจุด
หัวใจสำคัญคือความสม่ำเสมอ อย่าพยายามทำทุกอย่างให้สมบูรณ์แบบในวันเดียว ให้เน้นเขียนโค้ดที่รันได้จริงและเข้าใจง่ายก่อน แล้วค่อยๆ ปรับปรุงให้มันปลอดภัยด้วยการเพิ่มการตรวจสอบข้อมูลเข้าไปทีละส่วน
ตัวอย่างการนำไปใช้จริง: เริ่มจากการทำหน้า "Login" (เข้าสู่ระบบ) ที่มีการรับค่าจากฟอร์ม ส่งข้อมูลไปเช็กที่เซิร์ฟเวอร์ และแสดงข้อความเตือนเมื่อรหัสผ่านผิด แค่โปรเจกต์เล็กๆ นี้ ถ้าทำครบตามขั้นตอนที่แนะนำ คุณจะเก่งขึ้นกว่าคนทั่วไปหลายเท่าตัวครับ