เจาะลึกกระบวนการเรนเดอร์ของ React สำรวจวงจรชีวิตของคอมโพเนนต์ เทคนิคการเพิ่มประสิทธิภาพ และแนวทางปฏิบัติที่ดีที่สุดสำหรับการสร้างแอปพลิเคชันที่มีประสิทธิภาพสูง
React Render: การเรนเดอร์คอมโพเนนต์และการจัดการวงจรชีวิต
React ซึ่งเป็นไลบรารี JavaScript ยอดนิยมสำหรับสร้างส่วนต่อประสานกับผู้ใช้ (UI) อาศัยกระบวนการเรนเดอร์ที่มีประสิทธิภาพเพื่อแสดงและอัปเดตคอมโพเนนต์ การทำความเข้าใจว่า React เรนเดอร์คอมโพเนนต์ จัดการวงจรชีวิต และเพิ่มประสิทธิภาพการทำงานอย่างไรนั้นเป็นสิ่งสำคัญอย่างยิ่งสำหรับการสร้างแอปพลิเคชันที่แข็งแกร่งและขยายขนาดได้ คู่มือฉบับสมบูรณ์นี้จะสำรวจแนวคิดเหล่านี้อย่างละเอียด พร้อมทั้งตัวอย่างที่นำไปใช้ได้จริงและแนวทางปฏิบัติที่ดีที่สุดสำหรับนักพัฒนาทั่วโลก
ทำความเข้าใจกระบวนการเรนเดอร์ของ React
หัวใจหลักของการทำงานของ React อยู่ที่สถาปัตยกรรมแบบคอมโพเนนต์และ Virtual DOM เมื่อ state หรือ props ของคอมโพเนนต์เปลี่ยนแปลง React จะไม่จัดการกับ DOM จริงโดยตรง แต่จะสร้างตัวแทนเสมือนของ DOM ที่เรียกว่า Virtual DOM ขึ้นมา จากนั้น React จะเปรียบเทียบ Virtual DOM กับเวอร์ชันก่อนหน้า และระบุชุดการเปลี่ยนแปลงที่น้อยที่สุดที่จำเป็นในการอัปเดต DOM จริง กระบวนการนี้เรียกว่า reconciliation ซึ่งช่วยปรับปรุงประสิทธิภาพได้อย่างมาก
Virtual DOM และ Reconciliation
Virtual DOM คือตัวแทนของ DOM จริงที่อยู่ในหน่วยความจำซึ่งมีขนาดเล็ก การจัดการ Virtual DOM นั้นเร็วกว่าและมีประสิทธิภาพมากกว่า DOM จริงมาก เมื่อคอมโพเนนต์อัปเดต React จะสร้างแผนผัง Virtual DOM ใหม่และเปรียบเทียบกับแผนผังเดิม การเปรียบเทียบนี้ช่วยให้ React สามารถกำหนดได้ว่าโหนดใดใน DOM จริงที่ต้องได้รับการอัปเดต จากนั้น React จะนำการอัปเดตที่น้อยที่สุดเหล่านี้ไปใช้กับ DOM จริง ส่งผลให้กระบวนการเรนเดอร์รวดเร็วและมีประสิทธิภาพมากขึ้น
พิจารณาตัวอย่างง่ายๆ นี้:
สถานการณ์: การคลิกปุ่มจะอัปเดตตัวนับที่แสดงบนหน้าจอ
หากไม่มี React: การคลิกแต่ละครั้งอาจกระตุ้นการอัปเดต DOM ทั้งหมด ทำให้ต้องเรนเดอร์ทั้งหน้าหรือส่วนใหญ่ของหน้าใหม่ ซึ่งนำไปสู่ประสิทธิภาพที่ช้า
เมื่อใช้ React: มีเพียงค่าตัวนับใน Virtual DOM เท่านั้นที่ถูกอัปเดต กระบวนการ reconciliation จะตรวจจับการเปลี่ยนแปลงนี้และนำไปใช้กับโหนดที่เกี่ยวข้องใน DOM จริง ส่วนที่เหลือของหน้ายังคงไม่เปลี่ยนแปลง ส่งผลให้ผู้ใช้ได้รับประสบการณ์ที่ราบรื่นและตอบสนองได้ดี
React ตรวจจับการเปลี่ยนแปลงได้อย่างไร: อัลกอริทึม Diffing
อัลกอริทึม diffing ของ React เป็นหัวใจของกระบวนการ reconciliation โดยจะเปรียบเทียบแผนผัง Virtual DOM ใหม่และเก่าเพื่อระบุความแตกต่าง อัลกอริทึมนี้ตั้งสมมติฐานหลายอย่างเพื่อเพิ่มประสิทธิภาพการเปรียบเทียบ:
- องค์ประกอบสองชนิดที่แตกต่างกันจะสร้างแผนผังที่แตกต่างกัน หากองค์ประกอบรากมีชนิดต่างกัน (เช่น เปลี่ยนจาก <div> เป็น <span>) React จะ unmount แผนผังเก่าและสร้างแผนผังใหม่ขึ้นมาทั้งหมด
- เมื่อเปรียบเทียบองค์ประกอบสองชนิดที่เหมือนกัน React จะดูที่แอตทริบิวต์เพื่อพิจารณาว่ามีการเปลี่ยนแปลงหรือไม่ หากมีเพียงแอตทริบิวต์ที่เปลี่ยนแปลง React จะอัปเดตแอตทริบิวต์ของโหนด DOM ที่มีอยู่
- React ใช้ key prop เพื่อระบุไอเท็มในลิสต์ที่ไม่ซ้ำกัน การให้ key prop ช่วยให้ React สามารถอัปเดตลิสต์ได้อย่างมีประสิทธิภาพโดยไม่ต้องเรนเดอร์ทั้งลิสต์ใหม่
การทำความเข้าใจสมมติฐานเหล่านี้ช่วยให้นักพัฒนาเขียนคอมโพเนนต์ React ที่มีประสิทธิภาพมากขึ้น ตัวอย่างเช่น การใช้ key เมื่อเรนเดอร์ลิสต์เป็นสิ่งสำคัญอย่างยิ่งต่อประสิทธิภาพ
วงจรชีวิตของคอมโพเนนต์ React (React Component Lifecycle)
คอมโพเนนต์ของ React มีวงจรชีวิตที่กำหนดไว้อย่างชัดเจน ซึ่งประกอบด้วยชุดของเมธอดที่จะถูกเรียก ณ จุดต่างๆ ในการดำรงอยู่ของคอมโพเนนต์ การทำความเข้าใจเมธอดวงจรชีวิตเหล่านี้ช่วยให้นักพัฒนาสามารถควบคุมวิธีที่คอมโพเนนต์ถูกเรนเดอร์ อัปเดต และ unmount ได้ แม้จะมีการนำ Hooks เข้ามาใช้ เมธอดวงจรชีวิตก็ยังคงมีความสำคัญ และการทำความเข้าใจหลักการพื้นฐานของมันก็มีประโยชน์
เมธอดวงจรชีวิตใน Class Components
ในคอมโพเนนต์แบบคลาส เมธอดวงจรชีวิตจะถูกใช้เพื่อรันโค้ดในขั้นตอนต่างๆ ของชีวิตคอมโพเนนต์ นี่คือภาพรวมของเมธอดวงจรชีวิตที่สำคัญ:
constructor(props): ถูกเรียกก่อนที่คอมโพเนนต์จะถูก mount ใช้สำหรับกำหนดค่าเริ่มต้นของ state และ bind event handlersstatic getDerivedStateFromProps(props, state): ถูกเรียกก่อนการเรนเดอร์ ทั้งในการ mount ครั้งแรกและการอัปเดตครั้งต่อๆ ไป ควรคืนค่าอ็อบเจกต์เพื่ออัปเดต state หรือnullเพื่อบ่งชี้ว่า props ใหม่ไม่ต้องการการอัปเดต state ใดๆ เมธอดนี้ส่งเสริมการอัปเดต state ที่คาดเดาได้ตามการเปลี่ยนแปลงของ propsrender(): เมธอดที่จำเป็นซึ่งคืนค่า JSX ที่จะเรนเดอร์ ควรเป็นฟังก์ชันบริสุทธิ์ของ props และ statecomponentDidMount(): ถูกเรียกทันทีหลังจากคอมโพเนนต์ถูก mount (ถูกแทรกลงในแผนผัง) เป็นที่ที่ดีในการทำ side effects เช่น การดึงข้อมูลหรือการตั้งค่า subscriptionsshouldComponentUpdate(nextProps, nextState): ถูกเรียกก่อนการเรนเดอร์เมื่อได้รับ props หรือ state ใหม่ ช่วยให้คุณสามารถเพิ่มประสิทธิภาพโดยการป้องกันการ re-render ที่ไม่จำเป็น ควรคืนค่าtrueหากคอมโพเนนต์ควรอัปเดต หรือfalseหากไม่ควรgetSnapshotBeforeUpdate(prevProps, prevState): ถูกเรียกก่อนที่ DOM จะถูกอัปเดต มีประโยชน์สำหรับการจับข้อมูลจาก DOM (เช่น ตำแหน่งการเลื่อน) ก่อนที่มันจะเปลี่ยนแปลง ค่าที่คืนกลับมาจะถูกส่งเป็นพารามิเตอร์ไปยังcomponentDidUpdate()componentDidUpdate(prevProps, prevState, snapshot): ถูกเรียกทันทีหลังจากการอัปเดตเกิดขึ้น เป็นที่ที่ดีในการดำเนินการกับ DOM หลังจากคอมโพเนนต์ได้รับการอัปเดตcomponentWillUnmount(): ถูกเรียกทันทีที่คอมโพเนนต์กำลังจะถูก unmount และทำลาย เป็นที่ที่ดีในการล้างทรัพยากรต่างๆ เช่น การลบ event listeners หรือการยกเลิก network requestsstatic getDerivedStateFromError(error): ถูกเรียกหลังจากเกิดข้อผิดพลาดระหว่างการเรนเดอร์ จะได้รับข้อผิดพลาดเป็นอาร์กิวเมนต์และควรคืนค่าเพื่ออัปเดต state ช่วยให้คอมโพเนนต์สามารถแสดง UI สำรองได้componentDidCatch(error, info): ถูกเรียกหลังจากเกิดข้อผิดพลาดระหว่างการเรนเดอร์ในคอมโพเนนต์ลูกหลาน จะได้รับข้อมูลข้อผิดพลาดและ component stack เป็นอาร์กิวเมนต์ เป็นที่ที่ดีในการบันทึกข้อผิดพลาดไปยังบริการรายงานข้อผิดพลาด
ตัวอย่างการทำงานของเมธอดวงจรชีวิต
พิจารณาคอมโพเนนต์ที่ดึงข้อมูลจาก API เมื่อมัน mount และอัปเดตข้อมูลเมื่อ props ของมันเปลี่ยนแปลง:
class DataFetcher extends React.Component {
constructor(props) {
super(props);
this.state = { data: null };
}
componentDidMount() {
this.fetchData();
}
componentDidUpdate(prevProps) {
if (this.props.url !== prevProps.url) {
this.fetchData();
}
}
fetchData = async () => {
try {
const response = await fetch(this.props.url);
const data = await response.json();
this.setState({ data });
} catch (error) {
console.error('Error fetching data:', error);
}
};
render() {
if (!this.state.data) {
return <p>Loading...</p>;
}
return <div>{this.state.data.message}</div>;
}
}
ในตัวอย่างนี้:
componentDidMount()ดึงข้อมูลเมื่อคอมโพเนนต์ถูก mount เป็นครั้งแรกcomponentDidUpdate()ดึงข้อมูลอีกครั้งหากurlprop เปลี่ยนแปลง- เมธอด
render()จะแสดงข้อความกำลังโหลดในขณะที่กำลังดึงข้อมูล แล้วจึงเรนเดอร์ข้อมูลเมื่อพร้อมใช้งาน
เมธอดวงจรชีวิตและการจัดการข้อผิดพลาด
React ยังมีเมธอดวงจรชีวิตสำหรับการจัดการข้อผิดพลาดที่เกิดขึ้นระหว่างการเรนเดอร์:
static getDerivedStateFromError(error): ถูกเรียกหลังจากเกิดข้อผิดพลาดระหว่างการเรนเดอร์ จะได้รับข้อผิดพลาดเป็นอาร์กิวเมนต์และควรคืนค่าเพื่ออัปเดต state ซึ่งช่วยให้คอมโพเนนต์สามารถแสดง UI สำรองได้componentDidCatch(error, info): ถูกเรียกหลังจากเกิดข้อผิดพลาดระหว่างการเรนเดอร์ในคอมโพเนนต์ลูกหลาน จะได้รับข้อมูลข้อผิดพลาดและ component stack เป็นอาร์กิวเมนต์ นี่เป็นที่ที่ดีในการบันทึกข้อผิดพลาดไปยังบริการรายงานข้อผิดพลาด
เมธอดเหล่านี้ช่วยให้คุณจัดการข้อผิดพลาดได้อย่างราบรื่นและป้องกันไม่ให้แอปพลิเคชันของคุณล่ม ตัวอย่างเช่น คุณสามารถใช้ getDerivedStateFromError() เพื่อแสดงข้อความข้อผิดพลาดแก่ผู้ใช้ และ componentDidCatch() เพื่อบันทึกข้อผิดพลาดไปยังเซิร์ฟเวอร์
Hooks และ Functional Components
React Hooks ซึ่งเปิดตัวใน React 16.8 เป็นวิธีที่ช่วยให้สามารถใช้ state และฟีเจอร์อื่นๆ ของ React ใน functional components ได้ แม้ว่า functional components จะไม่มีเมธอดวงจรชีวิตเหมือนกับ class components แต่ Hooks ก็ให้ฟังก์ชันการทำงานที่เทียบเท่ากัน
useState(): ช่วยให้คุณสามารถเพิ่ม state ให้กับ functional componentsuseEffect(): ช่วยให้คุณสามารถทำ side effects ใน functional components ซึ่งคล้ายกับcomponentDidMount(),componentDidUpdate()และcomponentWillUnmount()useContext(): ช่วยให้คุณสามารถเข้าถึง React contextuseReducer(): ช่วยให้คุณสามารถจัดการ state ที่ซับซ้อนโดยใช้ reducer functionuseCallback(): คืนค่าฟังก์ชันเวอร์ชัน memoized ซึ่งจะเปลี่ยนแปลงก็ต่อเมื่อ dependencies ตัวใดตัวหนึ่งมีการเปลี่ยนแปลงuseMemo(): คืนค่าที่ผ่านการ memoized ซึ่งจะคำนวณใหม่ก็ต่อเมื่อ dependencies ตัวใดตัวหนึ่งมีการเปลี่ยนแปลงuseRef(): ช่วยให้คุณสามารถเก็บค่าไว้ระหว่างการเรนเดอร์useImperativeHandle(): ปรับแต่งค่าอินสแตนซ์ที่เปิดเผยให้คอมโพเนนต์แม่เมื่อใช้refuseLayoutEffect(): เป็นเวอร์ชันของuseEffectที่ทำงานแบบซิงโครนัสหลังจาก DOM mutations ทั้งหมดuseDebugValue(): ใช้เพื่อแสดงค่าสำหรับ custom hooks ใน React DevTools
ตัวอย่างของ useEffect Hook
นี่คือวิธีที่คุณสามารถใช้ useEffect() Hook เพื่อดึงข้อมูลใน functional component:
import React, { useState, useEffect } from 'react';
function DataFetcher({ url }) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const json = await response.json();
setData(json);
} catch (error) {
console.error('Error fetching data:', error);
}
}
fetchData();
}, [url]); // รัน effect ใหม่ก็ต่อเมื่อ URL เปลี่ยนแปลง
if (!data) {
return <p>Loading...</p>;
}
return <div>{data.message}</div>;
}
ในตัวอย่างนี้:
useEffect()ดึงข้อมูลเมื่อคอมโพเนนต์ถูกเรนเดอร์ครั้งแรกและทุกครั้งที่urlprop เปลี่ยนแปลง- อาร์กิวเมนต์ที่สองของ
useEffect()คืออาร์เรย์ของ dependencies หาก dependencies ใดๆ เปลี่ยนแปลง effect จะถูกรันใหม่ useState()Hook ถูกใช้เพื่อจัดการ state ของคอมโพเนนต์
การเพิ่มประสิทธิภาพการเรนเดอร์ของ React
การเรนเดอร์ที่มีประสิทธิภาพเป็นสิ่งสำคัญสำหรับการสร้างแอปพลิเคชัน React ที่มีประสิทธิภาพสูง นี่คือเทคนิคบางอย่างสำหรับการเพิ่มประสิทธิภาพการเรนเดอร์:
1. การป้องกันการ Re-render ที่ไม่จำเป็น
หนึ่งในวิธีที่มีประสิทธิภาพที่สุดในการเพิ่มประสิทธิภาพการเรนเดอร์คือการป้องกันการ re-render ที่ไม่จำเป็น นี่คือเทคนิคบางอย่างสำหรับการป้องกันการ re-render:
- การใช้
React.memo():React.memo()เป็น higher-order component ที่ทำ memoize ให้กับ functional component มันจะ re-render คอมโพเนนต์ก็ต่อเมื่อ props ของมันมีการเปลี่ยนแปลง - การใช้
shouldComponentUpdate(): ใน class components คุณสามารถใช้เมธอดวงจรชีวิตshouldComponentUpdate()เพื่อป้องกันการ re-render ตามการเปลี่ยนแปลงของ prop หรือ state - การใช้
useMemo()และuseCallback(): Hooks เหล่านี้สามารถใช้เพื่อ memoize ค่าและฟังก์ชัน ป้องกันการ re-render ที่ไม่จำเป็น - การใช้โครงสร้างข้อมูลแบบ Immutable: โครงสร้างข้อมูลแบบ Immutable ช่วยให้แน่ใจว่าการเปลี่ยนแปลงข้อมูลจะสร้างอ็อบเจกต์ใหม่แทนที่จะแก้ไขของเดิม ทำให้ง่ายต่อการตรวจจับการเปลี่ยนแปลงและป้องกันการ re-render ที่ไม่จำเป็น
2. การแบ่งโค้ด (Code-Splitting)
Code-splitting คือกระบวนการแบ่งแอปพลิเคชันของคุณออกเป็นส่วนเล็กๆ (chunks) ที่สามารถโหลดได้ตามต้องการ ซึ่งสามารถลดเวลาในการโหลดเริ่มต้นของแอปพลิเคชันของคุณได้อย่างมาก
React มีหลายวิธีในการทำ code-splitting:
- การใช้
React.lazy()และSuspense: ฟีเจอร์เหล่านี้ช่วยให้คุณสามารถ import คอมโพเนนต์แบบไดนามิก โดยจะโหลดเมื่อจำเป็นเท่านั้น - การใช้ dynamic imports: คุณสามารถใช้ dynamic imports เพื่อโหลดโมดูลตามต้องการได้
3. การทำ List Virtualization
เมื่อเรนเดอร์ลิสต์ขนาดใหญ่ การเรนเดอร์ทุกรายการพร้อมกันอาจทำให้ช้า เทคนิค List virtualization ช่วยให้คุณเรนเดอร์เฉพาะรายการที่มองเห็นบนหน้าจอในปัจจุบัน เมื่อผู้ใช้เลื่อนหน้าจอ รายการใหม่จะถูกเรนเดอร์และรายการเก่าจะถูก unmount
มีไลบรารีหลายตัวที่ให้บริการคอมโพเนนต์สำหรับ list virtualization เช่น:
react-windowreact-virtualized
4. การเพิ่มประสิทธิภาพรูปภาพ
รูปภาพมักเป็นสาเหตุสำคัญของปัญหาด้านประสิทธิภาพ นี่คือเคล็ดลับบางประการสำหรับการเพิ่มประสิทธิภาพรูปภาพ:
- ใช้รูปแบบรูปภาพที่เหมาะสม: ใช้รูปแบบเช่น WebP เพื่อการบีบอัดและคุณภาพที่ดีขึ้น
- ปรับขนาดรูปภาพ: ปรับขนาดรูปภาพให้มีขนาดที่เหมาะสมกับขนาดที่แสดงผล
- Lazy load รูปภาพ: โหลดรูปภาพเฉพาะเมื่อมองเห็นบนหน้าจอเท่านั้น
- ใช้ CDN: ใช้ Content Delivery Network (CDN) เพื่อให้บริการรูปภาพจากเซิร์ฟเวอร์ที่อยู่ใกล้กับผู้ใช้ของคุณตามภูมิศาสตร์
5. การทำ Profiling และ Debugging
React มีเครื่องมือสำหรับการทำ profiling และ debugging ประสิทธิภาพการเรนเดอร์ React Profiler ช่วยให้คุณสามารถบันทึกและวิเคราะห์ประสิทธิภาพการเรนเดอร์ เพื่อระบุคอมโพเนนต์ที่ทำให้เกิดปัญหาคอขวดด้านประสิทธิภาพ
ส่วนขยายเบราว์เซอร์ React DevTools มีเครื่องมือสำหรับตรวจสอบคอมโพเนนต์, state, และ props ของ React
ตัวอย่างที่ใช้งานได้จริงและแนวทางปฏิบัติที่ดีที่สุด
ตัวอย่าง: การทำ Memoize ให้กับ Functional Component
พิจารณา functional component ง่ายๆ ที่แสดงชื่อผู้ใช้:
function UserProfile({ user }) {
console.log('Rendering UserProfile');
return <div>{user.name}</div>;
}
เพื่อป้องกันไม่ให้คอมโพเนนต์นี้ re-render โดยไม่จำเป็น คุณสามารถใช้ React.memo():
import React from 'react';
const UserProfile = React.memo(({ user }) => {
console.log('Rendering UserProfile');
return <div>{user.name}</div>;
});
ตอนนี้ UserProfile จะ re-render ก็ต่อเมื่อ user prop เปลี่ยนแปลงเท่านั้น
ตัวอย่าง: การใช้ useCallback()
พิจารณาคอมโพเนนต์ที่ส่งฟังก์ชัน callback ไปยังคอมโพเนนต์ลูก:
import React, { useState } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<ChildComponent onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
}
function ChildComponent({ onClick }) {
console.log('Rendering ChildComponent');
return <button onClick={onClick}>Click me</button>;
}
ในตัวอย่างนี้ ฟังก์ชัน handleClick จะถูกสร้างขึ้นใหม่ทุกครั้งที่ ParentComponent เรนเดอร์ ซึ่งทำให้ ChildComponent re-render โดยไม่จำเป็น แม้ว่า props ของมันจะไม่ได้เปลี่ยนแปลงก็ตาม
เพื่อป้องกันปัญหานี้ คุณสามารถใช้ useCallback() เพื่อ memoize ฟังก์ชัน handleClick:
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]); // สร้างฟังก์ชันใหม่ก็ต่อเมื่อ count เปลี่ยนแปลง
return (
<div>
<ChildComponent onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
}
function ChildComponent({ onClick }) {
console.log('Rendering ChildComponent');
return <button onClick={onClick}>Click me</button>;
}
ตอนนี้ ฟังก์ชัน handleClick จะถูกสร้างขึ้นใหม่ก็ต่อเมื่อ count state เปลี่ยนแปลงเท่านั้น
ตัวอย่าง: การใช้ useMemo()
พิจารณาคอมโพเนนต์ที่คำนวณค่าที่ได้มาจาก props ของมัน:
import React, { useState } from 'react';
function MyComponent({ items }) {
const [filter, setFilter] = useState('');
const filteredItems = items.filter(item => item.name.includes(filter));
return (
<div>
<input type="text" value={filter} onChange={e => setFilter(e.target.value)} />
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
ในตัวอย่างนี้ อาร์เรย์ filteredItems จะถูกคำนวณใหม่ทุกครั้งที่ MyComponent เรนเดอร์ แม้ว่า items prop จะไม่ได้เปลี่ยนแปลงก็ตาม ซึ่งอาจไม่มีประสิทธิภาพหากอาร์เรย์ items มีขนาดใหญ่
เพื่อป้องกันปัญหานี้ คุณสามารถใช้ useMemo() เพื่อ memoize อาร์เรย์ filteredItems:
import React, { useState, useMemo } from 'react';
function MyComponent({ items }) {
const [filter, setFilter] = useState('');
const filteredItems = useMemo(() => {
return items.filter(item => item.name.includes(filter));
}, [items, filter]); // คำนวณใหม่ก็ต่อเมื่อ items หรือ filter เปลี่ยนแปลง
return (
<div>
<input type="text" value={filter} onChange={e => setFilter(e.target.value)} />
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
ตอนนี้ อาร์เรย์ filteredItems จะถูกคำนวณใหม่ก็ต่อเมื่อ items prop หรือ filter state เปลี่ยนแปลงเท่านั้น
สรุป
การทำความเข้าใจกระบวนการเรนเดอร์และวงจรชีวิตของคอมโพเนนต์ใน React เป็นสิ่งจำเป็นสำหรับการสร้างแอปพลิเคชันที่มีประสิทธิภาพและบำรุงรักษาง่าย ด้วยการใช้เทคนิคต่างๆ เช่น memoization, code-splitting และ list virtualization นักพัฒนาสามารถเพิ่มประสิทธิภาพการเรนเดอร์และสร้างประสบการณ์ผู้ใช้ที่ราบรื่นและตอบสนองได้ดี ด้วยการมาถึงของ Hooks การจัดการ state และ side effects ใน functional components กลายเป็นเรื่องง่ายขึ้น ซึ่งช่วยเพิ่มความยืดหยุ่นและพลังของการพัฒนา React ให้มากยิ่งขึ้น ไม่ว่าคุณจะสร้างเว็บแอปพลิเคชันขนาดเล็กหรือระบบองค์กรขนาดใหญ่ การเรียนรู้แนวคิดการเรนเดอร์ของ React อย่างเชี่ยวชาญจะช่วยปรับปรุงความสามารถในการสร้างส่วนต่อประสานผู้ใช้คุณภาพสูงได้อย่างมาก