📚 بخش ۱۹: تغییر State و اثر آن بر رندر مجدد کامپوننت در React

۱. مقدمه — چرا این مبحث مهم است؟
خیلی از تازهکارها در React فکر میکنند:
«هر وقت state تغییر کند فقط همان بخش خاص آپدیت میشود.»
ولی واقعیت این است که هر تغییری در state کامپوننت باعث فراخوانی دوبارهی کل تابع آن کامپوننت میشود و React تفاوتهای DOM را با نسخه قبلی مقایسه میکند (Diffing Algorithm).
اگر این را خوب بفهمیم:
- کد بهینهتری مینویسیم.
- از رندرهای غیرضروری جلوگیری میکنیم.
- عمر اپلیکیشن و عملکردش بهتر خواهد شد.
۲. وقتی State تغییر میکند دقیقاً چه میشود؟
مراحل کلی:
- شما تابع Setter (مثل
setCount) را صدا میزنید. - React مقدار state را به حالت جدید بهروز میکند (در حافظهی داخلی خودش).
- React آن کامپوننت را Mark میکند که دوباره رندر شود.
- در چرخهی بعدی رندر، تابع کامپوننت از نو اجرا میشود.
- React خروجی جدید JSX را با خروجی قبلی مقایسه میکند (Diffing).
- فقط بخشهایی از DOM که تغییر کردهاند در صفحه بهروزرسانی میشوند (Virtual DOM Optimization).
۳. مثال ساده
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
console.log("📢 کامپوننت رندر شد");
return (
<div>
<h1>شمارش: {count}</h1>
<button onClick={() => setCount(count + 1)}>افزایش</button>
</div>
);
}
💡 هر بار که روی دکمه کلیک میکنی، در Console هم پیام 📢 کامپوننت رندر شد دوباره نوشته میشود.
این نشان میدهد که کل تابع کامپوننت از دوباره اجرا میشود.
۴. نکته بسیار مهم — تغییر مستقیم state کار نمیکند
function WrongExample() {
const [count, setCount] = useState(0);
const increase = () => {
count = count + 1; // 🚫 این فقط متغیر محلی را تغییر میدهد
};
return (
<>
<p>{count}</p>
<button onClick={increase}>افزایش</button>
</>
);
}
📌 این کار اشتباه است چون با تغییر مستقیم count، React متوجه تغییر نمیشود و رندری اتفاق نمیافتد.
✅ باید همیشه از Setter استفاده کنیم:
setCount(count + 1);
۵. بروزرسانیهای پشت سر هم (Batching)
React برای بهبود عملکرد، آپدیتهای state پشت سر هم را با هم ترکیب میکند تا فقط یکبار رندر انجام دهد.
مثال:
function BatchExample() {
const [num, setNum] = useState(0);
const handleClick = () => {
setNum(num + 1);
setNum(num + 2);
setNum(num + 3);
console.log(num); // 👈 اینجا هنوز مقدار قدیمی است
};
return (
<button onClick={handleClick}>افزایش</button>
);
}
💡 چون آپدیتها batch میشوند، همه از مقدار اولیه در همان کلیک استفاده میکنند.
✅ حل مشکل: استفاده از callback form:
setNum(prev => prev + 1);
setNum(prev => prev + 2);
setNum(prev => prev + 3);
۶. اثر تغییر state روی کامپوننتهای فرزند
وقتی state یک کامپوننت والد تغییر میکند:
- والد دوباره رندر میشود.
- همهی فرزندانش هم دوباره رندر میشوند (مگر با تکنیکهایی مثل
React.memoبهینهسازی شده باشند).
مثال:
function Child() {
console.log("🔹 Child render");
return <h2>من فرزند هستم</h2>;
}
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>++</button>
<Child />
</div>
);
}
📌 هر بار که والد آپدیت شود، حتی اگر props فرزند تغییر نکند، باز هم Child رندر میشود مگر اینکه از memoization استفاده کنیم.
۷. نکات بهینهسازی
- بهجای چند state کوچک که وابسته به هم هستند، یک شیء state بساز (اما تغییرش را به روش Immutable انجام بده).
- اگر یک فرزند props ثابت دارد، آن را با
React.memoدور نگه دار تا بیدلیل رندر نشود. - منطقی که نیازی به اجرا در هر رندر ندارد را خارج از کامپوننت تعریف کن.
- از
useCallbackوuseMemoبرای جلوگیری از ساخت دوباره توابع یا دادههای سنگین در هر رندر استفاده کن.
۸. مثال واقعی — جلوگیری از رندرهای غیرضروری
import { useState, memo } from 'react';
const Child = memo(function Child({ name }) {
console.log("🔹 Child render");
return <p>سلام {name}</p>;
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>++</button>
<Child name="علی" />
</div>
);
}
📌 اینجا با memo باعث شدیم که وقتی count تغییر میکند ولی props فرزند ثابت است، فرزند دوباره رندر نشود.
۹. اشتباهات رایج مبتدیان
❌ آپدیت state بر اساس مقدار فعلی بدون استفاده از callback → ممکن است مقدار اشتباهی ذخیره شود.
❌ نگهداری دادهای که از props محاسبه میشود در state → باعث ناهماهنگی دادهها میشود.
❌ تلاش برای تغییر state داخل یک لوپ یا بدون محدودیت → باعث چرخه بیپایان (Infinite loop) میشود.
۱۰. تمرینهای پیشنهادی
- یک Timer بساز که هر ثانیه state را افزایش دهد و ببین هر تغییر چه بخشهایی را دوباره رندر میکند.
- پروژه بخش قبل (Counter) را طوری تغییر بده که فقط وقتی props خاصی تغییر کرد، یک فرزند رندر شود.
- یک فرم با state شیء بساز و ببین تغییر یکی از فیلدها چطور باعث رندر دوباره میشود.
۱۱. جمعبندی
- تغییر state همیشه باعث اجرای دوباره کامپوننت میشود.
- React فقط بخش تغییر یافته DOM را جایگزین میکند.
- برای آپدیتهای متوالی به مقدار state، از callback form استفاده کنید.
- مراقب رندرهای غیرضروری کامپوننتهای فرزند باشید.
- بهینهسازی در پروژههای بزرگ اهمیت زیادی دارد.