فهم الـ Automatic Batching في React 18
في React 18، ظهرت ميزة جديدة وهي Automatic Batching، اللي بتساعد في تقليل الـ renders وبالتالي بتحسن الأداء بشكل كبير. قبل ما نتكلم عن الـ Automatic Batching، خلينا نفهم الأول إيه اللي كان بيحصل في الفيرجنز اللي فاتت من React وإيه التغيير اللي حصل؟
Before React 18
في الفيرجنز القديمة من React، كان الـ Batching موجود، بس كان limited الـ Batching ببساطة يعني إن React بتجمع أكتر من تحديث لل (state) في re-render واحد عشان تحسن الأداء. ده كان بيحصل جوه الـ event handlers بس، يعني لو عملت تحديث للstate برا الـ event handler React كانت بتعمل re-render لكل تحديث لوحده.
في الـ Class components، كنت ممكن تعمل تحديث للstate باستخدام setState ، بس لو التحديث ده كان جوه function غير الـ event handler زي الـ Promise أو setTimeout الـ React كانت بتعمل re-render لكل تحديث لوحده. تعال سوا نبص ع مثال عشان الدنيا تكون أوضح بص للكود ده في المثال ده، لو عملت تحديث للstate جوه Promise،
class App extends React.Component {
constructor() {
super();
this.state = { name: "setState" };
this.handleClickSync = this.handleClickSync.bind(this);
this.handleClickAsync = this.handleClickAsync.bind(this);
}
handleClickSync() {
Promise.resolve().then(() => {
this.setState({ name: "sync" }, () => {
// This callback will be called after the state has been updated.
console.log("state inside callback", this.state.name); // sync
});
console.log("state outside callback", this.state.name); // setState (old value)
});
}
handleClickAsync() {
this.setState({ name: "Async" }, () => {
// This callback will be called after the state has been updated.
console.log("state inside callback", this.state.name); // Async
});
console.log("state outside callback", this.state.name); // setState (old value)
}
render() {
return (
<div>
<h1 onClick={this.handleClickSync}>Sync setState</h1>
<h1 onClick={this.handleClickAsync}>Async setState</h1>
</div>
);
}
}
export default App;
React كانت بتعمل re-render لكل تحديث لوحده، عشان كده لو عندك n تحديثات، هيحصل n re-renders. مش فاهم تعال أوضحلك أكتر
setState جوا Promise هيتعامل كـ async update، وده يعني إن الـ re-render هيحصل بعد تنفيذ كل الكود جوا الـ then. لكن لما تحط الـ console.log بعد setState مباشرة، الكود ده هيشتغل قبل ما الـ state تتحدث، عشان كده الـ
console.log('state', this.state.name)
هيطبع القيمة القديمة للـ state، اللي هي “setState” أو “sync” لو كنت دست على العنصر الأولاني
في handleClickAsync، نفس الكلام، ممكن تستخدم callback كان عندك كام سنة لما عرفت ان setState فيها callback ، بهزر معاك بفكك يجدع :(
React 18
في React 18، جابوا ميزة الـ Automatic Batching، اللي بتخلي الـ Batching يشتغل بشكل تلقائي في كل الأماكن، مش بس جوه الـ event handlers. دلوقتي، لو عملت تحديث للstate جوه Promise أو setTimeout أو أي حاجة تانية، React هتجمع كل التحديثات في re-render واحد.
بص للكود ده عشان تشوف مثال ع دا
const App = () => {
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
const handleClickWithBatching = () => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
};
const handleClickWithoutBatching = () => {
Promise.resolve().then(() => {
setCounter1((count) => count + 1);
setCounter2((count) => count + 1);
});
};
console.log('counters', counter1, counter2);
return (
<div className="App">
<h2 onClick={handleClickWithBatching}>Single Re-render</h2>
<h2 onClick={handleClickWithoutBatching}>Multiple Re-render</h2>
</div>
);
};
في المثال ده، سواء التحديثات كانت جوه Batching أو برا، React 18 هتعمل re-render واحد بس للتحديثات دي.
disabling Batching
لو لسبب ما عايز تلغي الـ Batching وتخلي React تعمل re-render لكل تحديث لوحده، تقدر تستخدم flushSync إزاي ؟ بص للكود ده
import { flushSync } from 'react-dom'
const handleClick = () => {
flushSync(() => {
setCounter1((count) => count + 1);
});
flushSync(() => {
setCounter2((count) => count + 1);
});
};
نييجي للسؤال المهم
إيه هي ReactDOM.unstable_batchedUpdates؟
هي API كانت موجودة في React بتسمح للديفلوبرز ربنا يوعدنا جميعا ويجعلنا منهم ، متستغربش اه والله ، بإنهم يجمعوا (batch) تحديثات (state) بشكل يدوي في React.
لما نقول إن التحديثات متجمعة في دفعة واحدة (batched)، معناها إن React بتجمع كل التحديثات اللي بتحصل في وقت معين وبتعمل (re-render) للـ component مرة واحدة بدل ما تعمل لكل تحديث لوحده.
قبل React 18، الـ React كانت بتقوم بالـ batching بشكل تلقائي لما تكون التحديثات جاية من داخل event handlers (زي لما تدوس على زرار). لكن لو التحديثات جاية من داخل (asynchronous operations) زي Promises أو setTimeout، React مكنتش بتجمع التحديثات بشكل تلقائي، وده كان بيخلي الديفلوبرز يلجأوا للـ API ReactDOM.unstable_batchedUpdates
عشان يقدروا يعملوا batching للتحديثات اللي جاية من العمليات دي. طبعا كل دا اتغير في ريأكت 18 طب ليه ReactDOM.unstable_batchedUpdates
لسه موجودة؟ عشان تحافظ على التوافقية مع الإصدارات القديمة. بعض المشاريع اللي مبنية على الإصدارات القديمة من React ممكن تكون بتستخدم الـ API دي بشكل يدوي، وعلشان مفيش حاجة تبوظ عند التحديث للإصدار الجديد، React قررت تخلي الـ API موجودة لفترة مؤقتة.