تغییر ساختار Tree و اثر آن بر State

در درس‌های قبل این فصل دیدیم که حفظ یا بازنشانی State، به ثابت ماندن نوع، موقعیت و key یک عنصر بستگی دارد. در این درس آخر فصل، چند الگوی رایج را مرور می‌کنیم که در آن‌ها، یک تغییر ظاهراً کوچک در ساختار JSX، به‌طور غیرمنتظره باعث تغییر موقعیت واقعی یک کامپوننت در درخت می‌شود و State آن را از بین می‌برد.

اضافه شدن یک عنصر والد جدید

یکی از رایج‌ترین دام‌ها، اضافه کردن یک عنصر بیرونی جدید دور یک کامپوننت، فقط در یکی از دو حالت شرطی است:

function Page({ isWide }) {
  if (isWide) {
    return (
      <div className="wide">
        <Counter />
      </div>
    );
  }

  return <Counter />;
}

در نگاه اول، به نظر می‌رسد Counter در هر دو حالت «همان جا» قرار دارد. اما از دید React، در حالت اول، Counter فرزند یک div است؛ در حالت دوم، خودش مستقیماً ریشه است. این دو موقعیت متفاوت‌اند؛ پس با تغییر isWide، React نمونه قدیمی را حذف و نمونه جدید را می‌سازد، و State شمارنده از بین می‌رود.

راه‌حل: ساختار یکسان در هر دو شاخه

function Page({ isWide }) {
  return (
    <div className={isWide ? "wide" : ""}>
      <Counter />
    </div>
  );
}

با یکی کردن ساختار بیرونی و فقط تغییر یک Prop یا کلاس روی همان عنصر، موقعیت Counter در هر دو حالت ثابت می‌ماند و State آن حفظ می‌شود.

جابه‌جایی ترتیب فرزندان

تغییر ترتیب چند عنصر خواهر در یک والد (بدون key) هم می‌تواند اثر مشابهی داشته باشد؛ چون React موقعیت را از روی جایگاه در لیست فرزندان تشخیص می‌دهد:

// قبل
<div>
  <Sidebar />
  <MainContent />
</div>

// بعد: ترتیب عوض شده
<div>
  <MainContent />
  <Sidebar />
</div>

اینجا React می‌بیند فرزند اول از نوع Sidebar به نوع MainContent تغییر کرده؛ یعنی از دیدش، محتوای هر دو موقعیت عوض شده، نه اینکه فقط جای دو عنصر آشنا برعکس شده باشد. نتیجه، از بین رفتن State هر دو است، حتی اگر خود دو کامپوننت دست‌نخورده باشند.

تغییر نوع عنصر ریشه یک شرط

حتی تغییر یک تگ ساده HTML در ریشه دو شاخه شرطی (مثلاً از section به div)، از دید React یعنی نوع عوض شده؛ و همان‌طور که در درس اول فصل دیدیم، تغییر نوع در یک موقعیت، یعنی هویت جدید، حتی اگر همه فرزندان زیرش، ظاهراً یکسان مانده باشند.

یک قاعده عملی برای پرهیز از این دام

پیش از نوشتن یک شرط که خروجی متفاوتی برای حالت‌های مختلف تولید می‌کند، از خودتان بپرسید: آیا ساختار بیرونی (نوع عناصر والد و ترتیب فرزندان) در هر دو شاخه یکسان است؟ اگر بله، State داخلی حفظ می‌شود. اگر نه، و این تغییر ساختار عمدی نبوده، معمولاً بهتر است شرط را به سطحی پایین‌تر (مثلاً یک کلاس یا Prop) منتقل کنید، نه به نوع یا جایگاه خود عنصر.

مثال قابل اجرا

در این مثال، با تغییر عرض، یک div اضافی فقط در یک حالت دور Counter قرار می‌گیرد و State از بین می‌رود؛ ساختار کد را بررسی کنید تا دلیل را ببینید.

از بین رفتن State با اضافه شدن یک والد جدید
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

اضافه یا حذف شدن یک عنصر والد فقط در یکی از دو شاخه شرطی، جابه‌جایی ترتیب فرزندان بدون key، یا تغییر نوع عنصر ریشه یک شرط، همگی می‌توانند موقعیت واقعی یک کامپوننت در درخت را عوض کنند و State آن را بدون قصد قبلی از بین ببرند. راه‌حل معمولاً یکسان نگه‌داشتن ساختار بیرونی و انتقال شرط به سطحی پایین‌تر است. با این درس، فصل دوازدهم به پایان می‌رسد و در ادامه به آزمون فصل ۱۲ می‌رویم.