เรียนรู้วิธีที่ Automatic Batching ของ React ช่วยเพิ่มประสิทธิภาพการอัปเดต State หลายรายการ ปรับปรุงประสิทธิภาพของแอปพลิเคชันและป้องกันการ re-render ที่ไม่จำเป็น พร้อมสำรวจตัวอย่างและแนวทางปฏิบัติที่ดีที่สุด
React Automatic Batching: การเพิ่มประสิทธิภาพการอัปเดต State เพื่อประสิทธิภาพสูงสุด
ประสิทธิภาพของ React เป็นสิ่งสำคัญอย่างยิ่งในการสร้าง User Interface ที่ราบรื่นและตอบสนองได้ดี หนึ่งในคุณสมบัติหลักที่ถูกนำมาใช้เพื่อปรับปรุงประสิทธิภาพคือ automatic batching เทคนิคการเพิ่มประสิทธิภาพนี้จะจัดกลุ่มการอัปเดต State หลายรายการให้เป็นการ re-render เพียงครั้งเดียวโดยอัตโนมัติ ซึ่งนำไปสู่การเพิ่มประสิทธิภาพอย่างมีนัยสำคัญ โดยเฉพาะอย่างยิ่งในแอปพลิเคชันที่ซับซ้อนและมีการเปลี่ยนแปลง State บ่อยครั้ง
React Automatic Batching คืออะไร?
Batching ในบริบทของ React คือกระบวนการจัดกลุ่มการอัปเดต State หลายรายการให้เป็นการอัปเดตเพียงครั้งเดียว ก่อนหน้า React 18, batching จะถูกใช้กับการอัปเดตที่เกิดขึ้นภายใน React event handler เท่านั้น การอัปเดตที่อยู่นอก event handler เช่น ภายใน setTimeout
, promises หรือ native event handlers จะไม่ถูกจัดกลุ่ม ซึ่งอาจนำไปสู่การ re-render ที่ไม่จำเป็นและปัญหาคอขวดด้านประสิทธิภาพ
React 18 ได้เปิดตัว automatic batching ซึ่งขยายการเพิ่มประสิทธิภาพนี้ไปยังการอัปเดต State ทั้งหมด ไม่ว่าจะเกิดขึ้นที่ใดก็ตาม ซึ่งหมายความว่าไม่ว่าการอัปเดต State ของคุณจะเกิดขึ้นภายใน React event handler, callback ของ setTimeout
หรือการ resolve ของ promise, React จะจัดกลุ่มการอัปเดตเหล่านั้นเข้าด้วยกันเป็นการ re-render เพียงครั้งเดียวโดยอัตโนมัติ
ทำไม Automatic Batching จึงมีความสำคัญ?
Automatic batching มีประโยชน์หลักหลายประการ:
- ปรับปรุงประสิทธิภาพ: โดยการลดจำนวนการ re-render, React automatic batching จะลดปริมาณงานที่เบราว์เซอร์ต้องทำเพื่ออัปเดต DOM ส่งผลให้ User Interface เร็วขึ้นและตอบสนองได้ดีขึ้น
- ลดภาระในการเรนเดอร์: การ re-render แต่ละครั้ง React จะต้องเปรียบเทียบ virtual DOM กับ DOM จริงและทำการเปลี่ยนแปลงที่จำเป็น Batching จะลดภาระนี้โดยทำการเปรียบเทียบน้อยลง
- ป้องกันสถานะที่ไม่สอดคล้องกัน: Batching ช่วยให้แน่ใจว่า component จะ re-render ด้วยสถานะสุดท้ายที่สอดคล้องกันเท่านั้น ป้องกันไม่ให้สถานะระหว่างกลางหรือสถานะชั่วคราวถูกแสดงให้ผู้ใช้เห็น
Automatic Batching ทำงานอย่างไร
React ทำ automatic batching ได้โดยการหน่วงเวลาการดำเนินการอัปเดต State ไปจนถึงจุดสิ้นสุดของ execution context ปัจจุบัน ซึ่งช่วยให้ React สามารถรวบรวมการอัปเดต State ทั้งหมดที่เกิดขึ้นในช่วง context นั้นและจัดกลุ่มเข้าด้วยกันเป็นการอัปเดตเพียงครั้งเดียว
พิจารณาตัวอย่างง่ายๆ นี้:
function ExampleComponent() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
function handleClick() {
setTimeout(() => {
setCount1(count1 + 1);
setCount2(count2 + 1);
}, 0);
}
return (
<div>
<p>Count 1: {count1}</p>
<p>Count 2: {count2}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
ก่อน React 18 การคลิกปุ่มจะทำให้เกิดการ re-render สองครั้ง: ครั้งแรกสำหรับ setCount1
และอีกครั้งสำหรับ setCount2
แต่ด้วย automatic batching ใน React 18 การอัปเดต State ทั้งสองจะถูกจัดกลุ่มเข้าด้วยกัน ส่งผลให้เกิดการ re-render เพียงครั้งเดียว
ตัวอย่างการทำงานของ Automatic Batching
1. การอัปเดตแบบอะซิงโครนัส (Asynchronous Updates)
การดำเนินการแบบอะซิงโครนัส เช่น การดึงข้อมูลจาก API มักจะมีการอัปเดต State หลังจากที่การดำเนินการเสร็จสิ้น Automatic batching ช่วยให้แน่ใจว่าการอัปเดต State เหล่านี้ถูกจัดกลุ่มเข้าด้วยกัน แม้ว่าจะเกิดขึ้นภายใน callback แบบอะซิงโครนัสก็ตาม
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const jsonData = await response.json();
setData(jsonData);
setLoading(false);
} catch (error) {
console.error('Error fetching data:', error);
setLoading(false);
}
}
fetchData();
}, []);
if (loading) {
return <p>Loading...</p>;
}
return <div>Data: {JSON.stringify(data)}</div>;
}
ในตัวอย่างนี้ setData
และ setLoading
ถูกเรียกใช้ภายในฟังก์ชัน fetchData
แบบอะซิงโครนัส React จะจัดกลุ่มการอัปเดตเหล่านี้เข้าด้วยกัน ส่งผลให้มีการ re-render เพียงครั้งเดียวเมื่อข้อมูลถูกดึงมาและสถานะการโหลดถูกอัปเดตแล้ว
2. Promises
เช่นเดียวกับการอัปเดตแบบอะซิงโครนัส promises มักจะมีการอัปเดต State เมื่อ promise สำเร็จ (resolve) หรือล้มเหลว (reject) Automatic batching ช่วยให้แน่ใจว่าการอัปเดต State เหล่านี้จะถูกจัดกลุ่มเข้าด้วยกันเช่นกัน
function PromiseComponent() {
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('Promise resolved!');
} else {
reject('Promise rejected!');
}
}, 1000);
});
myPromise
.then((value) => {
setResult(value);
setError(null);
})
.catch((err) => {
setError(err);
setResult(null);
});
}, []);
if (error) {
return <p>Error: {error}</p>;
}
if (result) {
return <p>Result: {result}</p>;
}
return <p>Loading...</p>;
}
ในกรณีนี้ ไม่ว่าจะเป็นการเรียก setResult
และ setError(null)
เมื่อสำเร็จ หรือการเรียก setError
และ setResult(null)
เมื่อล้มเหลว Automatic batching จะรวมการอัปเดตเหล่านี้ให้เป็นการ re-render เพียงครั้งเดียว
3. Native Event Handlers
บางครั้ง คุณอาจต้องใช้ native event handlers (เช่น addEventListener
) แทนที่จะเป็น synthetic event handlers ของ React ซึ่ง automatic batching ก็ยังคงทำงานในกรณีเหล่านี้ด้วย
function NativeEventHandlerComponent() {
const [scrollPosition, setScrollPosition] = useState(0);
useEffect(() => {
function handleScroll() {
setScrollPosition(window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
return <p>Scroll Position: {scrollPosition}</p>;
}
แม้ว่า setScrollPosition
จะถูกเรียกใช้ภายใน native event handler, React ก็ยังคงจัดกลุ่มการอัปเดตเข้าด้วยกัน เพื่อป้องกันการ re-render ที่มากเกินไปในขณะที่ผู้ใช้เลื่อนหน้าจอ
การเลือกไม่ใช้ Automatic Batching
ในบางกรณีที่เกิดขึ้นไม่บ่อยนัก คุณอาจต้องการเลือกไม่ใช้ automatic batching ตัวอย่างเช่น คุณอาจต้องการบังคับให้มีการอัปเดตแบบซิงโครนัสเพื่อให้แน่ใจว่า UI ได้รับการอัปเดตทันที React ได้เตรียม API ชื่อว่า flushSync
ไว้สำหรับจุดประสงค์นี้
หมายเหตุ: การใช้ flushSync
ควรทำเท่าที่จำเป็นเท่านั้น เพราะอาจส่งผลเสียต่อประสิทธิภาพได้ โดยทั่วไปแล้วควรพึ่งพา automatic batching ทุกครั้งที่ทำได้
import { flushSync } from 'react-dom';
function ExampleComponent() {
const [count, setCount] = useState(0);
function handleClick() {
flushSync(() => {
setCount(count + 1);
});
}
return (<button onClick={handleClick}>Increment</button>);
}
ในตัวอย่างนี้ flushSync
จะบังคับให้ React อัปเดต State และ re-render component ทันที โดยข้ามกระบวนการ automatic batching
แนวทางปฏิบัติที่ดีที่สุดสำหรับการเพิ่มประสิทธิภาพการอัปเดต State
แม้ว่า automatic batching จะช่วยปรับปรุงประสิทธิภาพได้อย่างมาก แต่ก็ยังคงเป็นสิ่งสำคัญที่จะต้องปฏิบัติตามแนวทางที่ดีที่สุดสำหรับการเพิ่มประสิทธิภาพการอัปเดต State:
- ใช้ Functional Updates: เมื่อต้องการอัปเดต State โดยอิงจาก State ก่อนหน้า ให้ใช้ functional updates (คือการส่งฟังก์ชันไปยัง state setter) เพื่อหลีกเลี่ยงปัญหาเกี่ยวกับ State ที่ล้าสมัย
- หลีกเลี่ยงการอัปเดต State ที่ไม่จำเป็น: อัปเดต State เฉพาะเมื่อจำเป็นเท่านั้น หลีกเลี่ยงการอัปเดต State ด้วยค่าเดิม
- ทำ Memoize Components: ใช้
React.memo
เพื่อทำ memoize components และป้องกันการ re-render ที่ไม่จำเป็น - ใช้ `useCallback` และ `useMemo`: ทำ Memoize ฟังก์ชันและค่าที่ส่งเป็น props เพื่อป้องกันไม่ให้ child components re-render โดยไม่จำเป็น
- เพิ่มประสิทธิภาพการ Re-render ด้วย `shouldComponentUpdate` (Class Components): แม้ว่า functional components และ hooks จะเป็นที่นิยมมากกว่าในปัจจุบัน แต่หากคุณทำงานกับ class-based components รุ่นเก่า การใช้
shouldComponentUpdate
จะช่วยควบคุมว่า component ควร re-render เมื่อใดโดยอิงจากการเปลี่ยนแปลงของ prop และ state - โปรไฟล์แอปพลิเคชันของคุณ: ใช้ React DevTools เพื่อโปรไฟล์แอปพลิเคชันของคุณและระบุปัญหาคอขวดด้านประสิทธิภาพ
- พิจารณาเรื่อง Immutability: ปฏิบัติต่อ State เสมือนเป็นข้อมูลที่ไม่สามารถเปลี่ยนแปลงได้ (immutable) โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับ object และ array ให้สร้างสำเนาใหม่ของข้อมูลแทนการแก้ไขโดยตรง ซึ่งจะทำให้การตรวจจับการเปลี่ยนแปลงมีประสิทธิภาพมากขึ้น
Automatic Batching และข้อควรพิจารณาในระดับสากล
Automatic batching ซึ่งเป็นการเพิ่มประสิทธิภาพหลักของ React ให้ประโยชน์กับแอปพลิเคชันทั่วโลกโดยไม่คำนึงถึงตำแหน่งของผู้ใช้ ความเร็วของเครือข่าย หรืออุปกรณ์ อย่างไรก็ตาม ผลกระทบของมันจะเห็นได้ชัดเจนยิ่งขึ้นในสถานการณ์ที่มีการเชื่อมต่ออินเทอร์เน็ตที่ช้าหรืออุปกรณ์ที่มีประสิทธิภาพต่ำ สำหรับผู้ชมในระดับนานาชาติ ควรพิจารณาประเด็นเหล่านี้:
- ความหน่วงของเครือข่าย (Network Latency): ในภูมิภาคที่มีความหน่วงของเครือข่ายสูง การลดจำนวนการ re-render สามารถปรับปรุงการตอบสนองที่รับรู้ได้ของแอปพลิเคชันได้อย่างมีนัยสำคัญ Automatic batching ช่วยลดผลกระทบจากความล่าช้าของเครือข่าย
- ความสามารถของอุปกรณ์: ผู้ใช้ในประเทศต่างๆ อาจใช้อุปกรณ์ที่มีกำลังการประมวลผลแตกต่างกัน Automatic batching ช่วยให้มั่นใจได้ถึงประสบการณ์ที่ราบรื่นยิ่งขึ้น โดยเฉพาะบนอุปกรณ์ระดับล่างที่มีทรัพยากรจำกัด
- แอปพลิเคชันที่ซับซ้อน: แอปพลิเคชันที่มี UI ซับซ้อนและมีการอัปเดตข้อมูลบ่อยครั้งจะได้รับประโยชน์สูงสุดจาก automatic batching โดยไม่คำนึงถึงตำแหน่งทางภูมิศาสตร์ของผู้ใช้
- การเข้าถึง (Accessibility): ประสิทธิภาพที่ดีขึ้นส่งผลต่อการเข้าถึงที่ดีขึ้น อินเทอร์เฟซที่ราบรื่นและตอบสนองได้ดียิ่งขึ้นจะเป็นประโยชน์ต่อผู้ใช้ที่มีความพิการซึ่งต้องพึ่งพาเทคโนโลยีช่วยเหลือ
สรุป
React automatic batching เป็นเทคนิคการเพิ่มประสิทธิภาพที่ทรงพลังซึ่งสามารถปรับปรุงประสิทธิภาพของแอปพลิเคชัน React ของคุณได้อย่างมาก ด้วยการจัดกลุ่มการอัปเดต State หลายรายการให้เป็นการ re-render เพียงครั้งเดียวโดยอัตโนมัติ ช่วยลดภาระในการเรนเดอร์ ป้องกันสถานะที่ไม่สอดคล้องกัน และนำไปสู่ประสบการณ์ผู้ใช้ที่ราบรื่นและตอบสนองได้ดียิ่งขึ้น การทำความเข้าใจวิธีการทำงานของ automatic batching และปฏิบัติตามแนวทางที่ดีที่สุดสำหรับการเพิ่มประสิทธิภาพการอัปเดต State จะช่วยให้คุณสามารถสร้างแอปพลิเคชัน React ที่มีประสิทธิภาพสูงซึ่งมอบประสบการณ์ที่ยอดเยี่ยมแก่ผู้ใช้ทั่วโลก การใช้เครื่องมืออย่าง React DevTools จะช่วยปรับปรุงและเพิ่มประสิทธิภาพโปรไฟล์ของแอปพลิเคชันของคุณในสภาพแวดล้อมที่หลากหลายทั่วโลกได้ดียิ่งขึ้น