ทำไมต้องรู้จัก Generic Components และการทำ Performance
เวลาเราเขียนโปรแกรม เรามักจะเจอปัญหาว่าต้องเขียนโค้ดซ้ำๆ เพื่อจัดการข้อมูลที่หน้าตาคล้ายกัน Generic Components (คอมโพเนนต์ที่รองรับข้อมูลได้หลายประเภท) คือทางแก้ที่ช่วยให้เราเขียนโค้ดครั้งเดียวแต่ใช้ได้กับทุกงาน เปรียบเหมือนกล่องอเนกประสงค์ที่ใส่ของได้ทุกอย่าง ไม่ว่าจะเป็นหนังสือ เสื้อผ้า หรืออุปกรณ์ไฟฟ้า
การเขียนโปรแกรมให้ Performance (ความเร็วและประสิทธิภาพของแอป) ดีขึ้นก็สำคัญไม่แพ้กัน ถ้าเราปล่อยให้คอมโพเนนต์คำนวณงานหนักทุกครั้งที่หน้าจอขยับ แอปก็จะช้าและกระตุก การเลือกใช้เครื่องมืออย่าง useMemo (คำสั่งจำผลลัพธ์การคำนวณ) และ useCallback (คำสั่งจำฟังก์ชันเพื่อไม่ให้สร้างใหม่) จะช่วยให้แอปเราลื่นไหลเหมือนรถที่เปลี่ยนน้ำมันเครื่องใหม่ตลอดเวลา
มือใหม่หลายคนมักมองข้ามเรื่องนี้เพราะแอปยังมีขนาดเล็ก แต่ถ้าฝึกไว้ตั้งแต่ตอนนี้ จะช่วยให้เราเขียนโค้ดที่ Scale (ขยายขนาดรองรับผู้ใช้ได้มากขึ้น) ได้ง่ายในอนาคต การเข้าใจเรื่อง Type Definition (การกำหนดชนิดข้อมูลให้ชัดเจน) จะช่วยให้เราหาบั๊ก (ข้อผิดพลาดของโปรแกรม) ได้เร็วกว่าเดิมมาก เพราะโปรแกรมจะเตือนเราทันทีถ้าใส่ข้อมูลผิดประเภท
สร้าง Generic Components แบบมือโปร
ลองจินตนาการว่าเราต้องทำรายการแสดงรายชื่อคน และรายการแสดงรายชื่อสินค้า ถ้าเราเขียนคอมโพเนนต์แยกกัน เราก็ต้องนั่งแก้โค้ดสองที่เสมอเมื่อต้องการเปลี่ยนดีไซน์ แต่ถ้าเราใช้ Generics (การสร้างโครงสร้างโค้ดที่ยืดหยุ่น) เราจะทำคอมโพเนนต์เดียวที่รับข้อมูลอะไรก็ได้เข้ามาแสดงผล
เราจะใช้สัญลักษณ์ <T> เพื่อบอกว่า T คือตัวแทนของชนิดข้อมูลที่จะส่งเข้ามาในภายหลัง วิธีนี้ทำให้เราได้ประโยชน์จาก Type Safety (ความปลอดภัยในการกำหนดชนิดข้อมูล) คือถ้าเราส่งข้อมูลผิดประเภทไป คอมไพเลอร์ (ตัวตรวจสอบโค้ดก่อนรัน) จะฟ้องทันที ทำให้เราไม่ต้องเดาว่าข้อมูลข้างในคืออะไร
นี่คือตัวอย่างการเขียน List ที่รับข้อมูลแบบ Generic เข้ามา:
// รายการที่รองรับข้อมูลทุกชนิด
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
export const List = <T,>({ items, renderItem }: ListProps<T>) => {
return <ul>{items.map((item, index) => renderItem(item))}</ul>;
};
โค้ดด้านบนสร้างตัวแปร List ที่ยอมรับข้อมูล items แบบใดก็ได้ โดยมีเงื่อนไขว่าต้องบอกวิธีแสดงผลผ่าน renderItem ข้อดีคือเราสามารถนำไปใช้กับหน้าจอโปรไฟล์หรือหน้าตะกร้าสินค้าได้ทันทีโดยไม่ต้องเขียนโค้ดใหม่
เพิ่มความเร็วด้วย useMemo
บางครั้งเราต้องคำนวณข้อมูลชุดใหญ่ เช่น การเรียงลำดับรายชื่อผู้ใช้งาน 1,000 คน ถ้าเราไม่ใช้ useMemo (คำสั่งจำผลลัพธ์) ทุกครั้งที่คอมโพเนนต์มีการอัปเดต คอมพิวเตอร์จะคำนวณใหม่ทั้งหมดทันที ซึ่งเสียเวลาและทำให้หน้าจอค้างได้
การใช้ useMemo<T> ช่วยให้เราบอก React ว่า "เก็บผลลัพธ์นี้ไว้นะ ถ้าข้อมูลต้นทางไม่เปลี่ยน ไม่ต้องคำนวณใหม่" เปรียบเหมือนการจดสูตรคูณไว้ในกระดาษ ถ้ามีคนถามเลขเดิม เราก็แค่หยิบกระดาษขึ้นมาดู ไม่ต้องเสียเวลาคิดเลขใหม่ในหัวอีกรอบ
ลองดูตัวอย่างการใช้งานเพื่อประหยัดพลังงานคอมพิวเตอร์:
// ใช้ useMemo เก็บผลลัพธ์ที่คำนวณแล้ว
const sortedItems = useMemo<User[]>(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]); // คำนวณใหม่เฉพาะเมื่อ items เปลี่ยนเท่านั้น
ในตัวอย่างนี้ sortedItems จะไม่ถูกเรียงลำดับใหม่หากเราไม่ได้เปลี่ยนข้อมูลใน items วิธีนี้ช่วยให้แอปตอบสนองไวขึ้นมาก โดยเฉพาะในโปรเจกต์ที่มีการแสดงผลข้อมูลจำนวนมากในหน้าเดียว
จัดการฟังก์ชันด้วย useCallback
ปัญหาที่พบบ่อยใน React คือฟังก์ชันถูกสร้างใหม่ทุกครั้งที่คอมโพเนนต์รัน ซึ่งทำให้คอมโพเนนต์ลูกเข้าใจผิดว่าฟังก์ชันเปลี่ยนไปแล้ว จึงต้องวาดหน้าจอใหม่โดยไม่จำเป็น การใช้ useCallback (คำสั่งจำฟังก์ชัน) จึงเข้ามาช่วยแก้ปัญหานี้
เมื่อเราใช้ useCallback เราจะได้ฟังก์ชันเดิมกลับมาเสมอ ตราบใดที่ค่าที่ต้องใช้ในฟังก์ชันนั้นยังเหมือนเดิม มันช่วยลดภาระการทำงานของเบราว์เซอร์ได้มหาศาล มือใหม่มักพลาดด้วยการใส่ทุกอย่างใน Dependency Array (รายการตัวแปรที่คอยเฝ้าระวัง) จนทำให้มันทำงานบ่อยเกินไป
มาดูวิธีเขียนที่ถูกต้องเพื่อไม่ให้ฟังก์ชันถูกสร้างใหม่พร่ำเพรื่อ:
// จำฟังก์ชันคลิกไว้ไม่ให้สร้างใหม่
const handleClick = useCallback((id: number) => {
console.log("คลิกที่ไอดี:", id);
}, []); // ใส่ค่าว่างเพื่อให้ฟังก์ชันนี้คงที่ตลอดไป
การใช้ useCallback คู่กับ memo (เครื่องมือช่วยหยุดการวาดหน้าจอซ้ำ) จะทำให้คอมโพเนนต์ของคุณทำงานได้อย่างมีประสิทธิภาพสูงสุด จุดที่ต้องระวังคืออย่าใช้กับทุกฟังก์ชัน เพราะการจำฟังก์ชันก็กินหน่วยความจำเหมือนกัน ให้ใช้เฉพาะกับฟังก์ชันที่ส่งต่อให้คอมโพเนนต์ลูกเท่านั้น
จุดที่มือใหม่มักพลาดและวิธีแก้ไข
ข้อผิดพลาดอันดับหนึ่งคือการกำหนด Type เป็น any (ชนิดข้อมูลอะไรก็ได้) เพราะขี้เกียจพิมพ์ การทำแบบนี้จะทำให้ TypeScript (เครื่องมือช่วยเช็คชนิดข้อมูล) กลายเป็นไร้ความหมายทันที เหมือนเรามีเข็มทิศแต่ไม่ยอมมองมันก่อนเดินเข้าป่า
อีกจุดคือการลืมใส่ Dependency ให้ครบใน useMemo หรือ useCallback ซึ่งจะทำให้ข้อมูลที่แสดงผลเก่ากว่าความจริง ทำให้ผู้ใช้สับสนว่าทำไมกดปุ่มแล้วค่าไม่เปลี่ยน วิธีแก้คือใช้ ESLint (เครื่องมือช่วยตรวจหาจุดผิดในโค้ด) ที่จะคอยเตือนเราเสมอถ้าเราลืมใส่ค่าที่ต้องเฝ้าระวัง
สุดท้ายคือการใช้ Generics ซับซ้อนเกินจำเป็นจนอ่านโค้ดไม่รู้เรื่อง ถ้าคุณมีคอมโพเนนต์ที่ต้องรับข้อมูลแค่ชนิดเดียว ไม่จำเป็นต้องทำเป็น <T> ให้ปวดหัว เขียนให้ง่ายและตรงไปตรงมาคือหัวใจของโปรแกรมเมอร์ที่เก่งครับ
สรุป: การนำไปใช้จริงในงานเขียนโค้ด
การทำ Generic Components ช่วยให้คุณลดการเขียนโค้ดซ้ำซ้อนได้ดีมาก และการใช้ useMemo กับ useCallback คือกุญแจสำคัญที่ทำให้แอปของคุณดูเป็นมืออาชีพและทำงานได้รวดเร็ว ลองเริ่มจากโปรเจกต์เล็กๆ ของคุณก่อน อย่าเพิ่งรีบเอาไปใช้กับทุกส่วนในแอปขนาดใหญ่
ตัวอย่างการนำไปใช้จริง: ถ้าคุณต้องทำตาราง (Table) ที่แสดงข้อมูลสินค้า พนักงาน และลูกค้า ให้คุณลองเขียน <Table<T> /> ขึ้นมาเพียงตัวเดียว แล้วใช้ renderItem เพื่อกำหนดว่าจะแสดงหัวข้ออะไรในแต่ละคอลัมน์ คุณจะพบว่าโค้ดของคุณดูสะอาดขึ้นและดูแลรักษาง่ายกว่าเดิมมาก
การเป็นโปรแกรมเมอร์ไม่ใช่แค่การเขียนให้โค้ดรันได้ แต่คือการเขียนให้คนอื่น (และตัวคุณเองในอนาคต) อ่านเข้าใจและทำงานได้เร็ว ฝึกใช้เครื่องมือเหล่านี้ให้เป็นนิสัย แล้วคุณจะก้าวข้ามจากมือใหม่ไปสู่ระดับมืออาชีพได้เร็วกว่าคนอื่นแน่นอนครับ