ภาพรวม (Overview)
การเขียน TypeScript ใน React ไม่ใช่แค่การใส่ : string หรือ : number ไปเรื่อยๆ แต่มันคือการออกแบบ "โครงสร้างข้อมูล" ที่ทำให้เราใช้ Component ได้อย่างมั่นใจและลด Bug ได้มหาศาล วันนี้มาดูท่าที่ Pro เค้าใช้กันครับ
1. Discriminated Unions for Props
ปัญหาคลาสสิก: "ถ้ามี prop variant='success' ต้องมี prop onSuccess แต่ถ้า variant='error' ต้องมี prop errorMessage"
ถ้าเขียนแบบ Optional หมด (onSuccess?: ..., errorMessage?: ...) คนเรียกใช้จะงงว่าตกลงต้องใส่อันไหน
✅ แก้ด้วย Discriminated Unions:
type StatusProps =
| { status: 'loading' } // ถ้า loading ห้ามมี prop อื่น
| { status: 'success'; data: User } // ถ้า success *ต้อง*มี data
| { status: 'error'; error: Error }; // ถ้า error *ต้อง*มี error
function StatusMessage(props: StatusProps) {
if (props.status === 'loading') {
return <Spinner />
}
if (props.status === 'success') {
return <div>Welcome {props.data.name}</div> // ✅ TS รู้ว่ามี data แน่นอน
}
return <div>Error: {props.error.message}</div> // ✅ TS รู้ว่ามี error แน่นอน
}
แบบนี้คนเรียกใช้จะใส่ Props ผิดไม่ได้เลย!
2. Generic Components
สมมติเราทำ Component List ที่รับ array อะไรก็ได้ และ render item ออกมา
❌ แบบ Any (ไม่ปลอดภัย):
interface ListProps {
items: any[];
renderItem: (item: any) => ReactNode;
}
✅ แบบ Generic (Type Safe):
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
// เวลาใช้ TS จะรู้เองว่า item คือ User!
<List
items={users}
renderItem={(user) => <div>{user.name}</div>} // ✅ user มี type auto!
/>
3. "as const" และ Derived Types
อย่าเขียน Type ซ้ำซ้อนกับ Value ให้ใช้ Value สร้าง Type แทน
❌ เขียนซ้ำ:
const MENU = ["Home", "About", "Contact"];
type MenuType = "Home" | "About" | "Contact"; // ถ้าแก้ MENU ต้องมาแก้ตรงนี้ด้วย ลืมแก้ก็บั๊ก
✅ Derived Type (Single Source of Truth):
// บอก TS ว่า array นี้เป็น Read-only Tuple นะ (ค่าไม่เปลี่ยน)
const MENU = ["Home", "About", "Contact"] as const;
// ดึง Type ออกมาจากค่าจริง
type MenuType = (typeof MENU)[number]; // 'Home' | 'About' | 'Contact'
4. Omit & Pick for Cleanup
เวลาเรา extends HTML Props บางทีเราไม่อยากได้ทุกอย่าง หรืออยากทับบางตัว
// อยากได้ปุ่มธรรมดา แต่บังคับว่า onClick ห้าม return อะไร
interface ButtonProps extends Omit<React.ComponentProps<"button">, "onClick"> {
onClick: () => void;
isLoading?: boolean;
}
สรุป
TypeScript ยิ่ง "Strict" ยิ่งดีครับ:
- ใช้ Discriminated Unions แทน Optional Props เยอะๆ
- ใช้ Generics กับ Reusable Component
- ใช้ as const เพื่อผูก Type กับ Value
เขียน Type ให้แข็ง แล้วตอนแก้ Logic จะพริ้วครับ!
References
Mission Accomplished
You've reached the end of this module. Time to apply these senior mindsets to your real-world projects!