ทำความเข้าใจ Props ใน React ด้วย TypeScript
เวลาเราเขียนโปรแกรมด้วย React เรามักจะแบ่งหน้าจอเป็นส่วนย่อย ๆ ที่เรียกว่า Component (ชิ้นส่วนของหน้าจอที่นำไปใช้ซ้ำได้) เพื่อให้โค้ดเป็นระเบียบ แต่ละชิ้นส่วนมักต้องส่งข้อมูลหากันผ่านสิ่งที่เรียกว่า Props (คุณสมบัติที่ส่งจากตัวแม่ไปให้ตัวลูก) เปรียบเหมือนเราส่งใบสั่งซื้อไปให้ร้านอาหาร เพื่อบอกว่าอยากกินอะไรบ้าง
เมื่อเราใช้ TypeScript (ภาษาที่ช่วยตรวจเช็คความผิดพลาดของข้อมูลก่อนรันโปรแกรม) เราต้องบอกให้ชัดเจนว่า Props ที่ส่งมานั้นมีหน้าตาเป็นอย่างไร การกำหนดค่าเหล่านี้ช่วยให้เราไม่เผลอส่งข้อมูลผิดประเภทเข้าไปใน Component ถ้าเราลืมส่งข้อมูลที่จำเป็น โปรแกรมจะแจ้งเตือนทันที ทำให้เราแก้บั๊กได้ตั้งแต่ก่อนเริ่มรันงานจริง
การกำหนดรูปแบบข้อมูลทำได้สองวิธีหลัก คือ Interface (โครงสร้างที่กำหนดว่าข้อมูลต้องมีอะไรบ้าง) และ Type (การตั้งชื่อให้รูปแบบข้อมูล) ทั้งสองแบบใช้งานได้ใกล้เคียงกัน แต่ Interface มักถูกเลือกใช้บ่อยกว่าในการทำ Component เพราะอ่านง่ายและขยายต่อได้สะดวกในอนาคต
// กำหนดหน้าตาข้อมูลที่จะส่งให้ Component
interface UserProps {
name: string; // ชื่อต้องเป็นข้อความ
age: number; // อายุต้องเป็นตัวเลข
}
// นำไปใช้ใน Component
const UserCard = ({ name, age }: UserProps) => {
return <div>{name} อายุ {age} ปี</div>;
};
การจัดการ Props ที่มีค่าไม่แน่นอน
ในชีวิตจริง ข้อมูลบางอย่างอาจไม่ได้มีมาให้ทุกครั้ง เช่น เรามีรูปโปรไฟล์ แต่บางคนอาจไม่มีรูป ถ้าเราบังคับว่าต้องมีรูปเสมอ โปรแกรมจะพังทันทีเมื่อเจอคนไม่มีรูป เราจึงต้องใช้ Optional Props (คุณสมบัติที่ใส่หรือไม่ใส่ก็ได้) ซึ่งเขียนง่าย ๆ ด้วยเครื่องหมายคำถามหลังชื่อตัวแปร
นอกจากนี้ บางครั้งเราอยากกำหนดค่าเริ่มต้นให้ข้อมูลในกรณีที่คนเรียกใช้ไม่ได้ส่งค่ามาให้ เราเรียกว่า Default Values (ค่าตั้งต้นที่โปรแกรมกำหนดไว้ให้) วิธีนี้ช่วยให้ Component ของเราทำงานได้ราบรื่นแม้ข้อมูลบางส่วนจะขาดหายไป ไม่ต้องมานั่งเขียนโค้ดเช็คค่าว่างซ้ำซ้อน
การใช้ Destructuring Props (การแกะข้อมูลออกจากวัตถุมาใช้งานโดยตรง) จะช่วยให้โค้ดดูสะอาดตาขึ้นมาก แทนที่จะต้องเขียน props.name ซ้ำ ๆ เราสามารถดึงค่าออกมาใช้ได้เลยตั้งแต่ตอนรับค่าเข้าฟังก์ชัน ทำให้การเขียนโค้ดเป็นเรื่องสนุกและไม่รกสายตา
// ใช้เครื่องหมาย ? เพื่อบอกว่า job ไม่จำเป็นต้องมี
interface UserProps {
name: string;
job?: string;
}
// กำหนดค่าเริ่มต้นถ้าไม่มีใครส่ง job มา ให้ใช้คำว่า "ว่างงาน"
const UserCard = ({ name, job = "ว่างงาน" }: UserProps) => {
return <div>{name} ทำงานเป็น {job}</div>;
};
การส่ง Component อื่นเป็นลูกด้วย React.ReactNode
บางครั้งเราต้องการสร้างกล่องครอบ Component อื่น ๆ เพื่อทำพวกกรอบหรือเลย์เอาต์ สิ่งที่อยู่ข้างในกล่องนั้นเราจะเรียกว่า children (ลูกที่ถูกส่งเข้ามาซ้อนข้างใน) เพื่อบอกว่าข้อมูลนี้เป็นสิ่งที่ React ยอมรับได้ เราจึงต้องใช้ประเภทข้อมูลที่ชื่อว่า React.ReactNode (รูปแบบข้อมูลทุกอย่างที่ React นำไปแสดงผลบนหน้าจอได้)
ลองนึกภาพว่า Component ของคุณคือกล่องพัสดุ ถ้าคุณไม่กำหนดประเภทให้ children คุณจะไม่สามารถใส่อะไรลงไปในกล่องได้เลย แต่ถ้าคุณใช้ React.ReactNode คุณจะใส่อะไรก็ได้ ไม่ว่าจะเป็นข้อความ รูปภาพ หรือแม้แต่ Component อื่น ๆ ซ้อนลงไปอีกชั้นหนึ่ง
จุดที่มือใหม่มักพลาดคือการลืมกำหนดประเภทให้ children หรือเลือกใช้ประเภทที่เจาะจงเกินไปจนยืดหยุ่นไม่ได้ การใช้ React.ReactNode คือทางเลือกที่ครอบคลุมและปลอดภัยที่สุดสำหรับงานส่วนใหญ่ในโปรเจกต์ของคุณ
// กำหนดให้ children รับข้อมูลอะไรก็ได้ที่ React แสดงผลได้
interface LayoutProps {
children: React.ReactNode;
}
const Wrapper = ({ children }: LayoutProps) => {
return <div className="box">{children}</div>;
};
สรุป: นำไปใช้จริงให้โปรเจกต์แข็งแกร่ง
การใช้ TypeScript กับ Props อาจดูเหมือนเพิ่มงานในตอนแรก แต่เชื่อเถอะว่ามันช่วยให้คุณประหยัดเวลาแก้บั๊กได้มหาศาล ยิ่งโปรเจกต์ใหญ่ขึ้นเท่าไหร่ ระบบนี้จะกลายเป็นเกราะป้องกันชั้นดีที่คอยบอกคุณทันทีว่าลืมส่งข้อมูลอะไร หรือส่งข้อมูลผิดประเภทไปตรงไหน
ลองนำไปฝึกใช้ในโปรเจกต์เล็ก ๆ ของคุณดู เริ่มจากการสร้าง Component แสดงผลข้อมูลผู้ใช้งาน (User Profile) โดยกำหนด Interface ให้ชัดเจน ใช้เครื่องหมาย ? กับข้อมูลที่ไม่มีก็ได้ และลองใช้ React.ReactNode ในการสร้าง Component ที่ทำหน้าที่เป็นกรอบล้อมรอบเนื้อหาข้างใน
หัวใจสำคัญคือการทำโค้ดให้คนอื่นอ่านรู้เรื่องและคอมไพเลอร์ (ตัวตรวจสอบโค้ด) เข้าใจ หากคุณฝึกจนชิน คุณจะพบว่าตัวเองเขียนโค้ดได้มั่นใจขึ้น ไม่ต้องเดาว่าตัวแปรนี้มีค่าอะไร และการทำงานร่วมกับทีมจะง่ายขึ้นมาก เพราะทุกคนจะรู้ทันทีว่าต้องส่งข้อมูลแบบไหนเข้ามาใน Component ของคุณ