ทำไมแอป React ถึงช้า และ Re-render คืออะไร
เวลาเราเขียนแอปด้วย React (ไลบรารีสำหรับสร้างหน้าจอเว็บไซต์) บางครั้งแอปอาจจะรู้สึกหน่วงหรือกระตุกโดยไม่รู้สาเหตุ สิ่งที่เกิดขึ้นเบื้องหลังคือสิ่งที่เรียกว่า Re-render (การสั่งให้หน้าจอวาดใหม่) ซึ่ง React จะพยายามอัปเดตหน้าจอทุกครั้งที่ข้อมูลเปลี่ยน
ลองนึกภาพว่าคุณกำลังจดบันทึกในสมุดเล่มหนึ่ง ทุกครั้งที่มีคนพูดชื่อคุณ คุณจะลบสิ่งที่เขียนไว้ทั้งหมดแล้วเขียนใหม่ตั้งแต่ต้น แม้ว่าเนื้อหาเดิมจะไม่ได้เปลี่ยนไปเลยก็ตาม นี่คือสิ่งที่ React ทำถ้าเราไม่ได้สั่งให้มันหยุดคิดบ้าง การทำงานซ้ำแบบไม่จำเป็นนี้เองที่ทำให้แอปของคุณทำงานช้าลงเมื่อมีข้อมูลเยอะขึ้น
การเรียนรู้วิธีลดการทำงานที่ไม่จำเป็นนี้ คือทักษะสำคัญที่จะแยกมือใหม่กับมืออาชีพออกจากกัน เมื่อคุณเริ่มเข้าใจว่าตอนไหนควรวาดใหม่และตอนไหนควรหยุด คุณจะสามารถสร้างแอปที่ลื่นไหลเหมือนแอปดังๆ ที่ทุกคนใช้กันได้
ป้องกันการวาดใหม่ด้วย React.memo
React.memo (ฟังก์ชันช่วยจำส่วนประกอบ) คือเครื่องมือแรกที่เราต้องรู้จัก มันทำหน้าที่เหมือนการบอกส่วนประกอบหนึ่งว่า ถ้าข้อมูลที่ส่งเข้ามาไม่เปลี่ยน ก็ไม่ต้องวาดใหม่นะ ให้ใช้ผลลัพธ์เดิมที่เคยจำไว้ได้เลย
ถ้าคุณมีปุ่มกดที่แสดงชื่อคน ถ้าชื่อคนเดิมถูกส่งเข้ามาเรื่อยๆ ปุ่มนั้นก็ไม่จำเป็นต้องประมวลผลใหม่ วิธีนี้ช่วยลดภาระของเบราว์เซอร์ได้อย่างมากโดยเฉพาะในหน้าเว็บที่มีรายการข้อมูลยาวๆ
import React from 'react';
// ใช้ React.memo หุ้ม Component เอาไว้
const UserButton = React.memo(({ name }) => {
console.log("กำลังวาดปุ่ม...");
return <button>{name}</button>;
});
ในโค้ดด้านบน เราใช้ React.memo ครอบตัว UserButton ไว้ ถ้า name ที่ส่งเข้ามาใหม่เหมือนเดิมเป๊ะๆ React จะไม่รันโค้ดข้างในนั้นซ้ำให้เสียเวลา
ข้อควรระวัง คืออย่าใช้กับทุกอย่างในแอป เพราะการเช็คว่าข้อมูลเปลี่ยนไหมก็ใช้พลังงานเครื่องเหมือนกัน ให้ใช้เฉพาะส่วนที่ซับซ้อนหรือมีการวาดใหม่บ่อยๆ เท่านั้น
จัดการการคำนวณซ้ำด้วย useMemo
บางครั้งเราต้องคำนวณค่าอะไรบางอย่างที่ใช้เวลานาน เช่น การกรองรายชื่อผู้ใช้งานนับพันคน ถ้าเราใส่การคำนวณนี้ไว้ในฟังก์ชันปกติ มันจะรันใหม่ทุกครั้งที่หน้าจอขยับ ซึ่งไม่ดีแน่ๆ
useMemo (คำสั่งจดจำผลลัพธ์การคำนวณ) เข้ามาแก้ปัญหานี้ โดยมันจะจำ "ผลลัพธ์" ของการคำนวณเอาไว้ แล้วส่งค่าเดิมคืนให้ทันทีโดยไม่คำนวณใหม่ ถ้าตัวแปรที่ใช้คำนวณยังเหมือนเดิม
import { useMemo } from 'react';
// คำนวณรายชื่อเฉพาะตอนที่ users เปลี่ยนเท่านั้น
const filteredUsers = useMemo(() => {
return users.filter(user => user.isActive);
}, [users]);
จากตัวอย่าง useMemo จะรับฟังก์ชันคำนวณเป็นตัวแรก และรับรายการตัวแปรที่ต้องเฝ้าระวังเป็นตัวที่สอง ในที่นี้คือ [users] ถ้า users ไม่เปลี่ยน มันจะคืนค่า filteredUsers เดิมให้ทันที
มือใหม่มักพลาดโดยการใส่ useMemo ทุกจุดในโค้ด จงจำไว้ว่าใช้เมื่อการคำนวณนั้นหนักจริงๆ เท่านั้น ถ้าเป็นแค่การบวกเลขธรรมดา การเขียนโค้ดปกติจะเร็วกว่าและอ่านง่ายกว่ามาก
หยุดการสร้างฟังก์ชันใหม่ด้วย useCallback
ใน React ทุกครั้งที่หน้าจอวาดใหม่ ฟังก์ชันที่ประกาศไว้ในคอมโพเนนต์จะถูกสร้างใหม่ทั้งหมด แม้หน้าตาฟังก์ชันจะเหมือนเดิม แต่มันถือเป็น "คนละตัว" ในสายตาของ React ทำให้ส่วนประกอบลูกที่รับฟังก์ชันนี้ไปใช้เข้าใจผิดว่าข้อมูลเปลี่ยนและสั่งวาดใหม่ตามไปด้วย
useCallback (คำสั่งจดจำฟังก์ชัน) จะช่วยล็อคตัวฟังก์ชันเอาไว้ไม่ให้ถูกสร้างใหม่ซ้ำๆ จนกว่าสิ่งที่มันพึ่งพาจะเปลี่ยนไป นี่คือท่าไม้ตายที่ใช้คู่กับ React.memo เพื่อให้การทำซ้ำหยุดลงอย่างสมบูรณ์
import { useCallback } from 'react';
// ฟังก์ชันนี้จะไม่ถูกสร้างใหม่ถ้าไม่มีอะไรเปลี่ยน
const handleClick = useCallback(() => {
console.log("ปุ่มถูกกด");
}, []); // อาร์เรย์ว่างหมายถึงไม่ต้องเฝ้าตัวแปรอะไรเลย
การใช้ useCallback ช่วยให้คอมโพเนนต์ลูกที่ได้รับฟังก์ชันนี้ไป ไม่ต้องสั่ง Re-render เพราะมันเห็นว่าฟังก์ชันที่ได้รับคือตัวเดิมตลอดเวลา
จุดที่มือใหม่พลาดบ่อย คือการไม่ใส่รายการในอาร์เรย์ (รายการตัวแปรเฝ้าระวัง) ให้ครบ ทำให้ฟังก์ชันใช้ค่าเก่าตลอดเวลาแม้ข้อมูลจะเปลี่ยนไปแล้ว ดังนั้นต้องเช็คให้ดีว่าฟังก์ชันของคุณมีการใช้ตัวแปรภายนอกตัวไหนบ้าง
สรุป: การนำไปใช้จริงให้ได้ผล
การลดการทำงานที่ไม่จำเป็นไม่ใช่เรื่องของการเขียนโค้ดให้ดูซับซ้อน แต่คือการทำความเข้าใจว่า React ทำงานอย่างไรและเมื่อไหร่ที่มันทำงานหนักเกินไป เริ่มต้นด้วยการสังเกตว่าหน้าเว็บเริ่มมีอาการกระตุกหรือดีเลย์ตอนพิมพ์หรือคลิกหรือไม่
ขั้นตอนการปรับปรุงประสิทธิภาพที่แนะนำ:
- วัดผลก่อนเสมอ อย่าเพิ่งรีบใส่
memoถ้ายังไม่รู้ว่าจุดไหนช้า - ใช้ React DevTools (เครื่องมือเสริมในเบราว์เซอร์สำหรับดูการทำงานของ React) เพื่อดูว่าคอมโพเนนต์ไหนวาดใหม่บ่อยเกินไป
- เลือกปรับปรุงเฉพาะจุดที่เป็นคอขวด เช่น รายการข้อมูลขนาดใหญ่หรือการคำนวณที่ซับซ้อน
กฎเหล็กคือ อย่าทำ Optimization (การปรับปรุงให้ดีขึ้น) ก่อนที่ปัญหาจะเกิด การเขียนโค้ดที่อ่านง่ายและสะอาดสำคัญกว่าความเร็วในช่วงที่คุณกำลังฝึกฝน เมื่อถึงเวลาที่ต้องทำโปรเจกต์จริงหรือทำงานในบริษัท ค่อยนำเครื่องมือเหล่านี้ไปใช้เพื่อสร้างประสบการณ์ที่ดีที่สุดให้กับผู้ใช้งานครับ