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

""

۱. مقدمه — چرا این مبحث مهم است؟

خیلی از تازه‌کارها در React فکر می‌کنند:

«هر وقت state تغییر کند فقط همان بخش خاص آپدیت می‌شود.»

ولی واقعیت این است که هر تغییری در state کامپوننت باعث فراخوانی دوباره‌ی کل تابع آن کامپوننت می‌شود و React تفاوت‌های DOM را با نسخه قبلی مقایسه می‌کند (Diffing Algorithm).

اگر این را خوب بفهمیم:

  • کد بهینه‌تری می‌نویسیم.
  • از رندرهای غیرضروری جلوگیری می‌کنیم.
  • عمر اپلیکیشن و عملکردش بهتر خواهد شد.

۲. وقتی State تغییر می‌کند دقیقاً چه می‌شود؟

مراحل کلی:

  1. شما تابع Setter (مثل setCount) را صدا می‌زنید.
  2. React مقدار state را به حالت جدید به‌روز می‌کند (در حافظه‌ی داخلی خودش).
  3. React آن کامپوننت را Mark می‌کند که دوباره رندر شود.
  4. در چرخه‌ی بعدی رندر، تابع کامپوننت از نو اجرا می‌شود.
  5. React خروجی جدید JSX را با خروجی قبلی مقایسه می‌کند (Diffing).
  6. فقط بخش‌هایی از 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) می‌شود.


۱۰. تمرین‌های پیشنهادی

  1. یک Timer بساز که هر ثانیه state را افزایش دهد و ببین هر تغییر چه بخش‌هایی را دوباره رندر می‌کند.
  2. پروژه بخش قبل (Counter) را طوری تغییر بده که فقط وقتی props خاصی تغییر کرد، یک فرزند رندر شود.
  3. یک فرم با state شیء بساز و ببین تغییر یکی از فیلدها چطور باعث رندر دوباره می‌شود.

۱۱. جمع‌بندی

  • تغییر state همیشه باعث اجرای دوباره کامپوننت می‌شود.
  • React فقط بخش تغییر یافته DOM را جایگزین می‌کند.
  • برای آپدیت‌های متوالی به مقدار state، از callback form استفاده کنید.
  • مراقب رندرهای غیرضروری کامپوننت‌های فرزند باشید.
  • بهینه‌سازی در پروژه‌های بزرگ اهمیت زیادی دارد.
محمد وب‌سایت

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *