
Batching là gì ?
Batching là phương pháp gom nhóm các nhiệm vụ tương tự lại với nhau để xử lý cùng lúc, thay vì làm lẻ tẻ từng cái một. Mục đích là để tăng năng suất bằng cách giảm thời gian bị gián đoạn khi chuyển đổi giữa các loại công việc khác nhau
React luôn cung cấp một cơ chế giúp tối ưu hóa hiệu suất nhất định khi xử lý các bản cập nhật state. Thông thường, cập nhập state 1sẽ kích hoạt re-render giao diện.
Do đó, để tối ưu hóa hiệu suất, React gộp một số bản cập nhật state này lại với nhau, đảm bảo chỉ có một lần render lại cho tất cả các bản cập nhật, thay vì một lần render lại cho mỗi bản cập nhật riêng lẻ.
Cập nhật state thông thường
useEffect(() => {
setValA(newStateA);
setValB(newStateB);
}, [....]);
- Trước React 18, batching chỉ xảy ra trong event handlers của React, nên
useEffectcó thể gây 2 lần render. - Nhưng từ React 18 trở đi, batching mở rộng ra tất cả context, bao gồm
useEffect,Promise,setTimeout, v.v.
Nên:
- React 17 hoặc cũ hơn: có thể render 2 lần.
- React 18 trở lên: chỉ render 1 lần.
Hướng xử lý khi xử dụng React 17 hoặc cũ hơn là Batching thủ công :
React có API ẩn gọi là: unstable_batchedUpdates
import { unstable_batchedUpdates } from 'react-dom';
useEffect(() => {
unstable_batchedUpdates(() => {
setValA(newStateA);
setValB(newStateB);
});
}, []);
Bằng cách này với react từ phiên bản 17 trở về trước, vẫn có thể tối ưu và giảm số lần render. Tuy nhiên với cách này nên chú ý không unstable_batchedUpdates trong event handler vì react đã tự động batching. Điều này có thể dẫn đến trì hoãn render nhiều hơn mong muốn, có thể gây ra lỗi UI không cập nhật ngay hoặc state lag một nhịp.
Cập nhật state bất đồng bộ
Trong trường hợp này, chúng ta sẽ thực hiện một thay đổi bằng cách cập nhật trạng thái trong setTimeout.
useEffect(() => {
setTimeout(() => {
setValA(newStateA);
setValB(newStateB);
}, 500);
}, []);
React 17 chỉ batching cập nhật state ở trong event handler. Tương tự nếu một sự kiện bất đồng bộ cập nhật được gọi trong event handler :
function handleClick() {
setTimeout(() => {
setValA(newStateA);
setValB(newStateB);
}, 500);
}
Ở cả hai trường hợp với React 17 và các phiên bản cũ hơn đề render 2 lần. Đây chính là lý do React 18 giới thiệu khái niệm batching tự động, mở rộng phạm vi xử lý batching và hoạt động trong hầu hết mọi trường hợp. Việc này sẽ giúp việc render chỉ diễn ra 1 lần. Cách xử lý tương tự với hàm đồng bộ :
setTimeout(() => {
unstable_batchedUpdates(() => {
setValA((p) => p + "A");
setValB((p) => p + "B");
});
}, 500);
Tuy nhiên, vẫn có một số trường hợp không batch được:
Trường hợp 1: Cập nhật state ngoài vùng React (và không trong batch context).
Ví dụ khi bạn gọi React state update từ callback của thư viện bên ngoài. React không kiểm soát được callback đó → không batch (trừ khi bọc thủ công bằng unstable_batchedUpdates).
Trường hợp 2: Cập nhật trong “transition” riêng biệt
React 18 có Concurrent Rendering2, và một số API như startTransition sẽ tách batch ra có chủ ý.
setCount(1);
startTransition(() => {
setValue("new");
});
- State trong React là một đối tượng lưu trữ dữ liệu nội bộ của một component.
↩︎ - Concurrent Rendering là cơ chế mới của React 18 cho phép React tạm dừng, hủy, hoặc ưu tiên lại quá trình render — thay vì phải render “một lèo” như trước đây. ↩︎
Trường hợp 3: Cập nhật trong flushSync
flushSync là API để ép React render ngay lập tức, phá vỡ batching
import { flushSync } from 'react-dom';
flushSync(() => {
setA(1);
});
setB(2);
Trường hợp 4: Promise
Việc sử dụng cập nhật state sau mỗi await, Promise.then, setTimeout, setInterval sẽ tách event handler thành 2 phase xử lý riêng biệt. Trong trường hợp này sẽ phá vỡ batching. Ví dụ :
function handleClick() {
setCount(c => c + 1); // ⬅️ Re-render lần 1
Promise.resolve().then(() => {
setCount(c => c + 1); // ⬅️ Re-render lần 2
});
}
async function handleClick() {
setLoading(true); ⬅️ Re-render lần 1
await new Promise(r => setTimeout(r, 0)); // tách batching
setData("done"); // ⬅️ Re-render lần 2
setLoading(false); // ⬅️ Re-render lần 2
}
Nói đơn giản: nếu tất cả setState chạy liền nhau mà không rời sang microtask hoặc macrotask khác,
React sẽ batch được.
Tóm tắt
React batch các setState trong cùng tick event loop và trong context React.
Bị ngắt batching khi:
- Bước qua
await, Promise,hoặctimer - Context rời khỏi React
- Dùng render cũ (
ReactDOM.render)
=> React 18 mở rộng batching tự động → nhưng await vẫn sẽ tách batching.
| Trường hợp | Có batching? | Giải thích |
|---|---|---|
Trong event React (onClick, onChange, onSubmit…) | ✅ Có | React kiểm soát context |
Trong useEffect / useLayoutEffect / useMemo | ✅ Có | Còn trong context React |
| Trong callback đồng bộ (liền mạch, không async) | ✅ Có | Cùng tick event loop |
| Trong Promise / async / await / .then / queueMicrotask | ❌ Không (React 17) / ⚠️ Có thể (React 18) | Chuyển sang microtask khác |
| Trong setTimeout / setInterval / requestAnimationFrame | ❌ Không (React 17) / ✅ Có (React 18) | Async macrotask |
| Trong event native (addEventListener, DOM event gốc) | ❌ Không | React không quản lý |
| Trong fetch, WebSocket, API callback | ❌ Không | Không thuộc context React |
Kết Luận
| Cấp độ | Có cần hiểu batching không? | Lý do |
|---|---|---|
| Beginner | 😌 Chỉ cần biết khái niệm | Để không bối rối khi thấy nhiều render |
| Mid-level | ✅ Nên hiểu khi nào có / không | Giúp tránh render dư & bug async |
| Senior / Library dev | 🔥 Phải hiểu sâu | Để kiểm soát re-render, tối ưu, và debug concurrency |
