ทำไม React ถึงช้า และเราจะรู้ได้ยังไง
ปกติแล้ว React (ไลบรารีสำหรับสร้างหน้าจอเว็บ) ทำงานได้เร็วอยู่แล้วโดยธรรมชาติ แต่พอโปรเจกต์เริ่มใหญ่ขึ้น เรามักจะเจออาการเว็บหน่วงหรือกระตุก การที่เว็บช้าไม่ได้แปลว่า React ไม่ดี แต่มักเกิดจากเราสั่งให้มันทำงานซ้ำซ้อนโดยไม่จำเป็น เปรียบเหมือนการสั่งให้คนล้างจานใบเดิมซ้ำๆ ทั้งที่มันสะอาดอยู่แล้ว
กฎเหล็กของการปรับจูนประสิทธิภาพคือ ห้ามเดา อย่าเพิ่งรีบแก้โค้ดถ้ายังไม่รู้ว่าจุดไหนคือต้นเหตุ ให้เริ่มจากการใช้เครื่องมือวัดผลก่อน เสมอ เครื่องมือที่ช่วยได้ดีที่สุดคือ React Profiler (เครื่องมือวัดความเร็วการทำงานของหน้าจอ) ที่ติดมากับ Chrome DevTools ซึ่งจะบอกเราว่าส่วนไหนของหน้าเว็บกำลังทำงานหนักเกินความจำเป็น
การวัดผลช่วยให้เราประหยัดเวลา ไม่ต้องไปเสียเวลาแก้ส่วนที่ไม่จำเป็น การเห็นตัวเลขชัดเจนว่าฟังก์ชันไหนใช้เวลานานกี่มิลลิวินาที จะช่วยให้เราโฟกัสถูกจุด การแก้ปัญหาที่ไม่ได้เป็นต้นเหตุจริงๆ จะทำให้โค้ดซับซ้อนขึ้นโดยเปล่าประโยชน์ และอาจทำให้เว็บช้าลงกว่าเดิมด้วยซ้ำ
อย่าเพิ่งรีบปรับจูน (Premature Optimization)
มือใหม่หลายคนมักพลาดด้วยการพยายามทำให้โค้ดเร็วตั้งแต่บรรทัดแรก เช่น การห่อทุกอย่างด้วยคำสั่ง memo (ฟังก์ชันช่วยจำผลลัพธ์เก่าไว้เพื่อลดการคำนวณใหม่) การทำแบบนี้เหมือนคนแบกเครื่องมือช่างเต็มตัวเพื่อไปเปลี่ยนหลอดไฟหนึ่งดวง มันทำให้โค้ดอ่านยากและเพิ่มภาระให้คอมพิวเตอร์โดยไม่จำเป็น
การปรับแต่งที่เกินความจำเป็นจะทำให้โปรเจกต์บวมและบำรุงรักษายาก ให้เน้นแก้เฉพาะจุดที่วัดผลแล้วพบว่าช้าจริงๆ หรือที่เรียกว่า Hot Paths (เส้นทางที่มีการเรียกใช้งานบ่อยและหนัก) การเขียนโค้ดให้เรียบง่ายและอ่านง่ายคือหัวใจสำคัญที่สุด ก่อนจะคิดถึงเรื่องความเร็ว ให้เน้นที่ความถูกต้องของตรรกะก่อนเสมอ
ถ้าคุณยังไม่เจอปัญหาความหน่วง ก็ไม่จำเป็นต้องใส่เทคนิคปรับจูนอะไรเข้าไปเลย โค้ดที่เรียบง่ายคือโค้ดที่เร็วที่สุด การปรับแต่งควรเกิดขึ้นเมื่อมีเหตุผลรองรับ ไม่ใช่ทำเพราะความกังวลล่วงหน้า
หยุดการ Render ซ้ำโดยไม่จำเป็น
การ Re-render (การวาดหน้าจอใหม่เมื่อข้อมูลเปลี่ยน) ไม่ใช่เรื่องแย่เสมอไป แต่มันจะกลายเป็นปัญหาเมื่อเราปล่อยให้มันเกิดขึ้นเป็นลูกโซ่ในส่วนประกอบที่ซับซ้อน ตัวการที่พบบ่อยที่สุดคือการสร้างออบเจกต์หรือฟังก์ชันใหม่ขึ้นมาทุกครั้งที่หน้าจอถูกวาดใหม่ ทำให้ React เข้าใจผิดว่าข้อมูลเปลี่ยนไปแล้ว
ลองดูตัวอย่างการแก้ปัญหาเมื่อมีการส่งข้อมูลใหม่เข้าไปในส่วนประกอบลูก:
// แบบที่ผิด: สร้างฟังก์ชันใหม่ทุกครั้งที่ Render
function ProductList({ products }) {
return <List items={products} onSelect={(id) => track(id)} />;
}
// แบบที่ถูก: ใช้ useCallback เพื่อคงค่าฟังก์ชันเดิมไว้
function ProductList({ products }) {
const handleSelect = useCallback((id) => track(id), []);
return <List items={products} onSelect={handleSelect} />;
}
ในตัวอย่างแรก ฟังก์ชัน onSelect จะถูกสร้างใหม่ทุกครั้งที่ ProductList ทำงาน ทำให้ List ต้องวาดใหม่เสมอแม้ข้อมูลไม่ได้เปลี่ยน ส่วนตัวอย่างที่สองเราใช้ useCallback (คำสั่งที่ช่วยล็อกฟังก์ชันไว้ไม่ให้สร้างใหม่) เพื่อให้ List รู้ว่าไม่ต้องวาดใหม่ถ้าไม่มีอะไรเปลี่ยน ผลลัพธ์คือโปรแกรมจะทำงานได้ลื่นไหลขึ้นทันที
เลือกสิ่งที่ควรจำ (Memoization)
เครื่องมืออย่าง React.memo, useMemo และ useCallback มีไว้เพื่อช่วยรักษา Referential Identity (การคงค่าอ้างอิงเดิมของตัวแปรไว้) เราควรใช้เครื่องมือเหล่านี้เมื่อส่วนประกอบลูกมีการคำนวณที่หนักมาก หรือได้รับข้อมูลที่เปลี่ยนค่าตลอดเวลาจนทำให้เกิดการวาดใหม่แบบไม่หยุดหย่อน
ถ้าส่วนประกอบของคุณเป็นแค่ข้อความธรรมดา หรือมีตรรกะไม่ซับซ้อน การไม่ใช้เทคนิคเหล่านี้จะดีกว่า การเก็บค่าไว้ในหน่วยความจำก็มีต้นทุนเช่นกัน หากเก็บทุกอย่างไว้ในหน่วยความจำจนเต็ม มันจะทำให้เว็บทำงานช้าลงกว่าเดิมเสียอีก จำไว้ว่าการเลือกใช้เครื่องมือให้ถูกที่สำคัญกว่าการใช้ทุกอย่างที่มี
ข่าวดีในปัจจุบันคือเรามี React Compiler (เครื่องมือที่จะเข้ามาช่วยจัดการเรื่องการจำค่าให้โดยอัตโนมัติ) ซึ่งจะช่วยลดภาระของนักพัฒนาได้มหาศาล เพียงแค่ติดตั้งเพิ่มเข้าไปในโปรเจกต์ โค้ดของคุณก็จะได้รับการปรับจูนความเร็วให้ในระดับหนึ่งโดยไม่ต้องเขียนคำสั่งเพิ่มเอง
ลดขนาดของ JavaScript ที่ต้องส่งให้ผู้ใช้งาน
กฎทองคือ "โค้ดที่เร็วที่สุด คือโค้ดที่เราไม่ต้องส่งไปให้ผู้ใช้" การโหลดไฟล์ขนาดใหญ่ทั้งหมดในครั้งเดียวจะทำให้ผู้ใช้งานที่เน็ตช้าต้องรอหน้าจอนาน การทำ Code-splitting (การแบ่งไฟล์โค้ดเป็นส่วนย่อยๆ) จะช่วยให้เบราว์เซอร์โหลดเฉพาะสิ่งที่จำเป็นต้องใช้ก่อนเท่านั้น
เราสามารถใช้คำสั่ง lazy และ Suspense เพื่อโหลดหน้าเว็บเฉพาะส่วนที่ผู้ใช้กำลังดูอยู่ได้:
import { lazy, Suspense } from 'react';
// โหลดคอมโพเนนต์เฉพาะตอนที่จำเป็นต้องใช้
const Editor = lazy(() => import('./Editor'));
export function Panel() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Editor />
</Suspense>
);
}
โค้ดด้านบนใช้ lazy เพื่อบอกว่าอย่าเพิ่งโหลด Editor จนกว่าผู้ใช้จะกดเปิดมันขึ้นมา ส่วน Suspense ทำหน้าที่แสดงข้อความรอระหว่างที่ไฟล์กำลังดาวน์โหลด วิธีนี้จะช่วยลดขนาดของไฟล์ที่เบราว์เซอร์ต้องโหลดในตอนเริ่มต้นได้มาก ทำให้เว็บแสดงผลไวขึ้นอย่างเห็นได้ชัด
ย้ายงานไปไว้ที่ Server
การใช้ React Server Components (เทคนิคที่ให้การประมวลผลข้อมูลเกิดขึ้นที่เซิร์ฟเวอร์ก่อนส่งมา) ช่วยให้เราลดการส่งข้อมูลไปประมวลผลที่เครื่องผู้ใช้ได้มาก ข้อมูลที่ต้องดึงจากฐานข้อมูลหรือการคำนวณหนักๆ ควรทำที่เซิร์ฟเวอร์ แล้วส่งออกมาเป็น HTML ที่พร้อมแสดงผลทันที
หลักการง่ายๆ คือถ้าเป็นเรื่องการดึงข้อมูล ให้ทำที่เซิร์ฟเวอร์ ถ้าเป็นเรื่องการโต้ตอบกับผู้ใช้หรือการกดปุ่ม ให้ทำที่ Client Component (ส่วนประกอบที่รันบนเบราว์เซอร์ของผู้ใช้) การแบ่งหน้าที่ให้ชัดเจนแบบนี้จะช่วยให้แอปพลิเคชันของคุณเบาและเร็วขึ้นอย่างมหาศาล เพราะเบราว์เซอร์ไม่ต้องโหลดตรรกะการคำนวณหนักๆ มาไว้ที่เครื่อง
นอกจากนี้ อย่าลืมเรื่องรูปภาพและฟอนต์ เพราะเป็นไฟล์ที่มีขนาดใหญ่ที่สุดในเว็บ ให้เลือกใช้ขนาดรูปที่พอดีกับการแสดงผล และโหลดเฉพาะฟอนต์ที่จำเป็นจริงๆ การทำตามขั้นตอนเหล่านี้เป็นพื้นฐานที่นักพัฒนาทุกคนควรทำให้เป็นนิสัยเพื่อให้เว็บมีประสิทธิภาพสูงสุด
สรุป: ขั้นตอนสู่ความเร็วที่ทำได้จริง
การทำให้เว็บเร็วไม่ใช่เรื่องของเวทมนตร์ แต่เป็นเรื่องของการมีวินัยในการเขียนโค้ด เริ่มต้นด้วยการ 1. วัดผลก่อนเสมอ 2. แก้เฉพาะจุดที่ช้าจริง 3. ลดการวาดหน้าจอซ้ำ 4. ลดขนาดไฟล์ที่โหลด และ 5. ใช้เซิร์ฟเวอร์ช่วยประมวลผล เพียงเท่านี้คุณก็จัดการปัญหาความเร็วได้เกือบ 90% แล้ว
สมมติว่าคุณกำลังทำเว็บแสดงรายการสินค้า สิ่งแรกที่ควรทำคือเปิด React Profiler ดูว่าตอนกดเลือกสินค้า หน้าจอมีการวาดใหม่ทั้งหน้าหรือไม่ ถ้าใช่ ให้ลองใช้ useMemo ครอบการกรองข้อมูลสินค้า และใช้ useCallback กับปุ่มกดเลือก เพียงเท่านี้เว็บของคุณก็จะตอบสนองไวขึ้นจนผู้ใช้รู้สึกได้
การเป็นโปรแกรมเมอร์ที่เก่งไม่ได้วัดกันที่การใช้เทคนิคยากๆ แต่คือการรู้วิธีแก้ปัญหาให้ตรงจุดและสร้างประสบการณ์ที่ดีที่สุดให้ผู้ใช้ เริ่มฝึกจากโปรเจกต์เล็กๆ ของคุณวันนี้ แล้วคุณจะพบว่าการปรับจูนประสิทธิภาพเป็นทักษะที่สนุกและคุ้มค่าที่จะเรียนรู้ไปตลอดอาชีพการทำงาน
ที่มา: A Practical Guide to React Performance — DEV Community