وضعیت: حافظه یک کامپوننت در React

mohsen1 ماه قبل1 ماه قبل
ارسال شده در
react/docs/v19

کامپوننت‌ها اغلب نیازمند تغییر محتوای نمایش داده شده بر اساس تعامل کاربر هستند. تایپ کردن در فرم باید فیلد ورودی را به‌روزرسانی کند، کلیک روی "بعدی" در یک نمایشگر تصویر باید تصویر بعدی را نمایش دهد، و کلیک روی "خرید" باید یک محصول را به سبد خرید اضافه کند. کامپوننت‌ها باید "به خاطر بسپارند": مقدار ورودی فعلی، تصویر فعلی، و سبد خرید. در ری‌اکت، این نوع حافظه خاص کامپوننت، وضعیت نامیده می‌شود.

وقتی متغیر معمولی کافی نیست

در اینجا کامپوننتی تعریف شده که تصویر مجسمه ای را نمایش می‌دهد. کلیک روی دکمه "بعدی" باید با تغییر index به 1، سپس 2، و به همین ترتیب، مجسمه های بعدی را نشان دهد. اما این کد کار نمی کند (می‌توانید امتحان کنید!):

مشاهده کد و نتیجه اجرای آن در CodeSandbox

      import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}
    

مدیر رویداد handleClick در حال به‌روزرسانی یک متغیر محلی به نام index است. اما دو نکته مانع از قابل مشاهده بودن این تغییر می‌شود:

  • متغیرهای محلی در بین رندرها حفظ نمی‌شوند. وقتی ری‌اکت این کامپوننت را برای بار دوم رندر می‌کند، آن را از نو رندر می‌کند و هیچ تغییری در متغیرهای محلی را در نظر نمی‌گیرد.
  • تغییرات در متغیرهای محلی باعث رندر مجدد نمی شوند. ری‌اکت متوجه نیاز به رندر مجدد کامپوننت با تغییر داده ها نمی شود.

برای به‌روزرسانی یک کامپوننت با داده‌های جدید، دو چیز باید اتفاق بیفتد:

  • حفظ داده‌ها بین رندرها.
  • تحریک (Trigger) ری‌اکت برای رندر مجدد کامپوننت با داده‌های جدید (رندر مجدد).

هوک useState این دو را فراهم می‌کند:

  • یک متغیر وضعیت برای حفظ داده‌ها بین رندرها.
  • یک تابع setter وضعیت برای تغییر متغیر و تحریک ری‌اکت برای رندر مجدد کامپوننت.

اضافه کردن متغیر وضعیت

برای اضافه کردن متغیر وضعیت، هوک useState را از ری‌اکت در بالای فایل ایمپورت کنید:

      import { useState } from 'react';
    

سپس، خط زیر را:

      let index = 0;
    

با این کد جایگزین کنید:

      const [index, setIndex] = useState(0);
    

index یک متغیر وضعیت و setIndex تابع ست کننده آن است.

دستور [ ] استفاده شده ، به نام تخریب آرایه شناخته می‌شود و به شما اجازه می‌دهد تا از یک آرایه مقادیر را بخوانید. آرایه‌ای که توسط useState برگشت داده می‌شود همیشه دارای دو آیتم است.

و این نحوه عملکرد تغییر مقدار وضعیت در handleClick است:

      function handleClick() {
  setIndex(index + 1);
}
    

اکنون کلیک روی دکمه "بعدی" مجسمه فعلی را تغییر می‌دهد:

مشاهده کد و نتیجه اجرای آن در CodeSandbox

      import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}
    

آشنایی با اولین هوک

در ری‌اکت، useState، همان‌طور که هر تابع دیگری که با "use" آغاز می‌شود، هوک نامیده می‌شود.

هوک‌ها توابع خاصی هستند که فقط در زمان رندر شدن (که در مطلب بعدی به تفصیل به آن خواهیم پرداخت) در دسترس هستند. آنها به شما اجازه می‌دهند تا "به" ویژگی‌های مختلف ری‌اکت متصل شوید.

وضعیت تنها یکی از این ویژگی‌ها است، اما با سایر هوک‌های دیگر نیز آشنا خواهید شد.

هوک‌ها—توابعی که با شما نمی‌توانید هوک‌ها را در داخل شرایط، حلقه‌ها یا توابع تو در تو فراخوانی کنید. هوک‌ها توابع هستند، اما نگاه به آنها به چشم تعاریف بدون شرط برای نیازهای کامپوننت شما، مفید است. "از" این ویژگی‌های ری‌اکت درست در بالای کامپوننت خود استفاده می‌کنید، مشابه نحوه ایمپورت ماژول‌ها را در ابتدای فایلها.

آناتومی useState

هنگامی که هوک useState را فراخوانی می‌کنید، به ری‌اکت می‌گویید که می‌خواهید این کامپوننت چیزی را به خاطر بسپارد:

      const [index, setIndex] = useState(0);
    

در این مورد، شما می‌خواهید ری‌اکت index را به خاطر بسپارد.

رایج است که این جفت را به شکل const [something, setSomething] نام‌گذاری کنید. می‌توانید آن را هر چه بخواهید نام‌گذاری کنید، اما رعایت کنوانسیون‌ها باعث می‌شود که فهم آنها در پروژه‌های مختلف راحت‌تر باشد.

تنها آرگومان useState مقدار اولیه متغیر وضعیت شما است. در این مثال، مقدار اولیه index با useState(0) تنظیم شده است.

هر بار که کامپوننت شما رندر می‌شود، useState آرایه‌ای شامل دو مقدار به شما می‌دهد:

  • متغیر وضعیت (index) با مقداری که ذخیره کرده‌اید.
  • تابع setter وضعیت (setIndex) که می‌تواند متغیر وضعیت را به‌روزرسانی کرده و ری‌اکت را برای رندر مجدد کامپوننت تحریک کند.

این نحوه عملکرد آن در کد است:

      const [index, setIndex] = useState(0);
    
  • کامپوننت شما اولین بار رندر می‌شود. از آنجایی که شما 0 را به useState به‌عنوان مقدار اولیه برای index پاس داده‌اید، [0, setIndex] را برمی‌گرداند. ری‌اکت مقدار 0 را بعنوان آخرین مقدار وضعیت به خاطر می سپرد.
  • شما وضعیت را به‌روزرسانی می‌کنید. وقتی کاربر روی دکمه کلیک می‌کند، setIndex(index + 1) فراخوانی می‌شود. index برابر با 0 است، بنابراین setIndex(1) می‌شود. این به ری‌اکت می‌گوید که index اکنون برابر با 1 است و رندر دیگری را اجرا می‌کند.
  • رندر دوم کامپوننت شما. ری‌اکت هنوز useState(0) را می‌بیند، اما چون ری‌اکت به یاد می‌آورد که شما index را به 1 تنظیم کرده‌اید، [1, setIndex] را برمی‌گرداند.
  • و به همین ترتیب!

دادن متغیرهای وضعیت متعدد به یک کامپوننت

شما می‌توانید به تعداد دلخواه متغیرهای وضعیت از انواع مختلف در یک کامپوننت داشته باشید. این کامپوننت دو متغیر وضعیت دارد، یک عدد index و یک boolean به نام showMore که وقتی روی "نمایش جزئیات" کلیک می‌کنید، تغییر می‌کند:

مشاهده کد و نتیجه اجرای آن در CodeSandbox

      import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}
    

داشتن متغیرهای وضعیت متعدد غیر مرتبط، مثل index و showMore ، ایده خوبی است. اما اگر دو متغیر وضعیت را همیشه با هم تغییر می‌دهید، ممکن است راحت‌تر باشد که آنها را در یک متغیر ترکیب کنید. به عنوان مثال، اگر شما یک فرم با چندین فیلد داشته باشید، آسان‌تر است که یک متغیر وضعیت واحد که یک شی را نگه می‌دارد، داشته باشید تا یک متغیر به ازای هر فیلد. برای نکات بیشتر به انتخاب ساختار وضعیت مراجعه کنید.

چگونه ری‌اکت می‌داند کدام وضعیت را برگرداند؟

شاید متوجه شده‌اید که در فراخوانی useState هیچ اطلاعاتی درباره کدام متغیر وضعیت به هوک پاس داده نمی‌شود. هیچ "شناسنده‌ای" به useState پاس داده نمی‌شود، پس چگونه می‌داند کدام یک از متغیرهای وضعیت را برگرداند؟ آیا به نوعی جادوی خاصی مثل تجزیه توابع شما متکی است؟ پاسخ منفی است.

در عوض، برای فعال کردن سینتکس مختصرشان، هوک‌ها به یک نظم ثابت در فراخوانی در هر رندر از همان کامپوننت متکی هستند. این در عمل خوب کار می‌کند زیرا اگر قاعده بالا ("فقط هوک‌ها را در سطح بالا فراخوانی کنید") را رعایت کنید، هوک‌ها همیشه به همین ترتیب فراخوانی خواهند شد. علاوه بر این، پلاگین lint می تواند بیشتر اشتباهات مانند این را شناسایی می‌کند.

ری‌اکت به صورت داخلی آرایه ای از جفت‌های وضعیت برای هر کامپوننت را نگهداری می کند. ایندکس جفت جاری که قبل از رندر برابر با 0 تنظیم شده است را نیز نگه می‌دارد. هر بار که useState را فراخوانی می‌کنید، ری‌اکت جفت وضعیت بعدی را به شما می‌دهد و ایندکس را افزایش می‌دهد. در مطلب هوک‌های ری‌اکت: نه جادو، فقط آرایه‌ها. می‌توانید اطلاعات بیشتری درباره این مکانیزم بخوانید.

این مثال از ری‌اکت استفاده نمی‌کند اما به شما ایده‌ای از نحوه عملکرد درونی useState می‌دهد:

مشاهده کد و نتیجه اجرای آن در CodeSandbox

      let componentHooks = [];
let currentHookIndex = 0;

// How useState works inside React (simplified).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // This is not the first render,
    // so the state pair already exists.
    // Return it and prepare for next Hook call.
    currentHookIndex++;
    return pair;
  }

  // This is the first time we're rendering,
  // so create a state pair and store it.
  pair = [initialState, setState];

  function setState(nextState) {
    // When the user requests a state change,
    // put the new value into the pair.
    pair[0] = nextState;
    updateDOM();
  }

  // Store the pair for future renders
  // and prepare for the next Hook call.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

function Gallery() {
  // Each useState() call will get the next pair.
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  // This example doesn't use React, so
  // return an output object instead of JSX.
  return {
    onNextClick: handleNextClick,
    onMoreClick: handleMoreClick,
    header: `${sculpture.name} by ${sculpture.artist}`,
    counter: `${index + 1} of ${sculptureList.length}`,
    more: `${showMore ? 'Hide' : 'Show'} details`,
    description: showMore ? sculpture.description : null,
    imageSrc: sculpture.url,
    imageAlt: sculpture.alt
  };
}

function updateDOM() {
  // Reset the current Hook index
  // before rendering the component.
  currentHookIndex = 0;
  let output = Gallery();

  // Update the DOM to match the output.
  // This is the part React does for you.
  nextButton.onclick = output.onNextClick;
  header.textContent = output.header;
  moreButton.onclick = output.onMoreClick;
  moreButton.textContent = output.more;
  image.src = output.imageSrc;
  image.alt = output.imageAlt;
  if (output.description !== null) {
    description.textContent = output.description;
    description.style.display = '';
  } else {
    description.style.display = 'none';
  }
}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
let description = document.getElementById('description');
let image = document.getElementById('image');
let sculptureList = [{
  name: 'Homenaje a la Neurocirugía',
  artist: 'Marta Colvin Andrade',
  description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
  url: 'https://i.imgur.com/Mx7dA2Y.jpg',
  alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'  
}, {
  name: 'Floralis Genérica',
  artist: 'Eduardo Catalano',
  description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.',
  url: 'https://i.imgur.com/ZF6s192m.jpg',
  alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.'
}];

// Make UI match the initial state.
updateDOM();
    

برای استفاده از ری‌اکت لازم نیست که کد بالا را درک کنید، اما ممکن است مدل ذهنی ای مفیدی برای شما باشد.

وضعیت ایزوله و خصوصی است

وضعیت برای نمونه کامپوننت در صفحه نمایش محلی است. به عبارت دیگر، اگر شما همان کامپوننت را دو بار رندر کنید، هر نسخه به‌طور کامل وضعیت‌ایزوله‌ای خواهد داشت! تغییر یکی از آنها بر دیگری تأثیر نمی‌گذارد.

در این مثال، کامپوننت Gallery مثال قبلی بدون هیچ تغییری در منطق آن دوبار استفاده شده است. امتحان کنید که روی دکمه‌های داخل هر یک از گالری‌ها کلیک کنید. توجه کنید که وضعیت آنها مستقل است:

      import Gallery from './Gallery.js';

export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}
    

مشاهده کد و نتیجه اجرای آن در CodeSandbox

این چیزی است که وضعیت را از متغیرهای معمولی که ممکن است در بالای ماژول خود تعریف کنید، متمایز می‌کند. وضعیت به یک فراخوانی تابع خاص یا جایی در کد وابسته نیست، اما نسبت به یک مکان خاص در صفحه "محلی" است. شما دو کامپوننت <Gallery /> را رندر کرده‌اید، بنابراین وضعیت آنها جداگانه ذخیره می‌شود.

همچنین توجه کنید که کامپوننت Page هیچ چیزی در مورد وضعیت Gallery نمی‌داند و حتی نمی‌داند که آیا اصلاً وضعیتی دارد یا نه. بر خلاف props، وضعیت برای کامپوننتی که آن را تعریف کرده کاملاً خصوصی است. کامپوننت والد نمی‌توانند آن را تغییر دهد. این به شما اجازه می‌دهد وضعیت را به هر کامپوننتی اضافه یا آن را حذف کنید بدون اینکه بر سایر کامپوننت‌ها تأثیر بگذارد.

اگر می‌خواستید هر دو گالری وضعیت‌های خود را همگام نگه‌دارند چه؟ راه درست برای انجام این کار در ری‌اکت این است که وضعیت را از کامپوننت‌های فرزند حذف کرده و آن را به نزدیک‌ترین والد مشترک آن‌ها اضافه کنید. مطالب بعدی بر سازماندهی وضعیت یک کامپوننت تمرکز خواهد کرد، اما ما به این موضوع در تقسیم وضعیت بین کامپوننت‌ها باز خواهیم گشت.

جمع بندی

  • از یک متغیر وضعیت زمانی استفاده کنید که یک کامپوننت نیاز دارد که برخی اطلاعات را در میان رندرها به "خاطر بسپارد."
  • متغیرهای وضعیت با فراخوانی هوک useState اعلام می‌شوند.
  • هوک‌ها توابع خاصی هستند که با use شروع می‌شوند. آنها به شما اجازه می‌دهند تا به ویژگی‌های ری‌اکت مانند وضعیت "متصل" شوید.
  • هوک‌ها ممکن است شما را به یاد واردات بیاندازند: آنها باید بدون شرط فراخوانی شوند. فراخوانی هوک‌ها، از جمله useState، تنها در سطح بالای یک کامپوننت یا یک هوک دیگر معتبر است.
  • هوک useState یک جفت از مقادیر را برمی‌گرداند: وضعیت فعلی و تابع به‌روز رسانی آن.
  • شما می‌توانید بیش از یک متغیر وضعیت داشته باشید. ری‌اکت به طور داخلی آنها را با ترتیبشان همسان می‌کند.
  • وضعیت خصوصی برای کامپوننت است. اگر آن را در دو مکان رندر کنید، هر نسخه وضعیت خاص خود را دارد.

چالش ها

کامل کردن گالری

وقتی شما روی دکمه "بعدی" در آخرین مجسمه کلیک می‌کنید، کد دچار خطا می‌شود. منطق را اصلاح کنید تا از وقوع خطا جلوگیری شود. می‌توانید این کار را با اضافه کردن منطق اضافی به مدیر رویداد انجام دهید یا با غیرفعال کردن دکمه هنگامی که مجسمه بعدی برای نمایش وجود ندارد.

بعد از اصلاح خطا، یک دکمه "قبلی" به کد اضافه کنید که مجسمه قبلی را نشان دهد. نباید در هنگام نمایش مجسمه اول دچار خطا شود.

مشاهده کد چالش و اصلاح آن در CodeSandbox

اصلاح ورودی‌های فرمی که گیر کرده‌اند

وقتی شما در فیلدهای ورودی تایپ می‌کنید، هیچ چیزی ظاهر نمی‌شود. انگار که مقادیر ورودی "گیر کرده اند" و با رشته‌های خالی مقدار دهی شده اند. ویژگی value اولین <input> با متغیر firstName تنظیم شده است، و value برای <input> دوم با متغیر lastName تنظیم شده است. این قسمت درست است. هر دو ورودی دارای رویداد onChange هستند که سعی می‌کنند متغیرها را براساس آخرین ورودی کاربر (e.target.value) به‌روزرسانی کنند. با این حال، به نظر می‌رسد که متغیرها بین رندرهای مجدد "به خاطر سپرده نمی شوند". این را با استفاده از متغیرهای وضعیت اصلاح کنید.

مشاهده کد چالش و اصلاح آن در CodeSandbox

اصلاح یک خطا

اینجا یک فرم کوچک وجود دارد که باید به کاربر اجازه دهد برخی بازخوردها را ارائه دهد. وقتی بازخورد ارسال می‌شود، قرار است پیام تشکر نمایش داده شود. اما با یک پیام خطا که می‌گوید "Rendered fewer hooks than expected"، دچار خطا می‌شود. آیا می‌توانید اشتباه را شناسایی کرده و اصلاح کنید؟

راهنمایی: آیا محدودیت‌هایی در مورد جایی که هوک‌ها می توانند فراخوانی شوند وجود دارد؟ آیا این کامپوننت قوانینی را نقض کرده است؟ بررسی کنید که آیا هیچ دستوری برای غیر فعال کردن بررسی‌های linter استفاده نشده باشد - این عاملی است که اشکالات را معمولاً پنهان می کند!

مشاهده کد چالش و اصلاح آن در CodeSandbox

حذف وضعیت غیر ضروری

هنگامی که دکمه کلیک می‌شود، این مثال باید از کاربر نامش را بپرسد و سپس یک پیام هشدار برای خوش‌آمدگویی به او نمایش دهد. شما سعی کردید از وضعیت برای نگه‌داری نام استفاده کنید، اما به دلایلی همیشه "سلام، !" را نشان می‌دهد.

برای اصلاح این کد، متغیر وضعیت غیر ضروری را حذف کنید. (ما به مطلب اینکه چرا این کد کار نمی کند بعداً خواهیم پرداخت.)

آیا می‌توانید توضیح دهید که چرا این متغیر وضعیت غیر ضروری بود؟

مشاهده کد چالش و اصلاح آن در CodeSandbox

جواب چالش ها

چالش اول

می توانید شرط محافظتی در داخل هر دو تابع مدیریت رویداد اضافه کنید و دکمه‌ها را در مواقع نیاز غیرفعال می‌کند: مشاهده جواب چالش و نتیجه اجرای آن در CodeSandbox

      import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);
  let hasPrev = index > 0;
  let hasNext = index < sculptureList.length - 1;
  function handlePrevClick() {
    if (hasPrev) {
      setIndex(index - 1);
    }
  }
  function handleNextClick() {
    if (hasNext) {
      setIndex(index + 1);
    }
  }
  function handleMoreClick() {
    setShowMore(!showMore);
  }
  let sculpture = sculptureList[index];
  return (
    <>
      <button
        onClick={handlePrevClick}
        disabled={!hasPrev}
      >
        Previous
      </button>
      <button
        onClick={handleNextClick}
        disabled={!hasNext}
      >
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}
    

توجه کنید که چگونه hasPrev و hasNext هر دو برای JSX برگشتی و داخل توابع مدیریت رویدادها استفاده شده‌اند! به دلیل وجود قابلیت "بستارها در جاوااسکریپت" هر متغیری که در حین رندر تعریف شده، در توابع و JSX کامپوننت نیز قابل دسترسی است.

چالش دوم

اول، useState را از ری‌اکت ایمپورت کنید. سپس، firstName و lastName را با متغیرهای وضعیت جایگزین کنید که با فراخوانی useState تعریف شوند. سرانجام، هر دستور مقدار دهی firstName = ... را با setFirstName(...) جایگزین کنید و همین کار را برای lastName انجام دهید. فراموش نکنید که handleReset را نیز به‌روزرسانی کنید تا دکمه بازنشانی کار کند.

مشاهده جواب چالش و نتیجه اجرای آن در CodeSandbox

      import { useState } from 'react';
export default function Form() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');
  function handleFirstNameChange(e) {
    setFirstName(e.target.value);
  }
  function handleLastNameChange(e) {
    setLastName(e.target.value);
  }
  function handleReset() {
    setFirstName('');
    setLastName('');
  }
  return (
    <form onSubmit={e => e.preventDefault()}>
      <input
        placeholder="First name"
        value={firstName}
        onChange={handleFirstNameChange}
      />
      <input
        placeholder="Last name"
        value={lastName}
        onChange={handleLastNameChange}
      />
      <h1>Hi, {firstName} {lastName}</h1>
      <button onClick={handleReset}>Reset</button>
    </form>
  );
}
    

چالش سوم

هوک‌ها فقط می‌توانند در سطح بالای تابع کامپوننت فراخوانی شوند. در اینجا، اولین تعریف isSent این قاعده را رعایت می‌کند، اما تعریف message در یک شرط تو در تو تعریف شده است. آن بخش کد را از شرط خارج کنید تا مشکل اصلاح شود: مشاهده جواب چالش و نتیجه اجرای آن در CodeSandbox

      import { useState } from 'react';
export default function FeedbackForm() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('');
  if (isSent) {
    return <h1>Thank you!</h1>;
  } else {
    return (
      <form onSubmit={e => {
        e.preventDefault();
        alert(`Sending: "${message}"`);
        setIsSent(true);
      }}>
        <textarea
          placeholder="Message"
          value={message}
          onChange={e => setMessage(e.target.value)}
        />
        <br />
        <button type="submit">Send</button>
      </form>
    );
  }
}
    

به یاد داشته باشید، هوک‌ها باید بدون شرط و همیشه به همان ترتیب فراخوانی شوند! شما همچنین می‌توانید شاخه else غیر ضروری را حذف کنید تا تو در تویی را کاهش دهید. اما هنوز مهم است که تمام فراخوانی‌های هوک قبل از اولین return انجام شوند. مشاهده جواب چالش و نتیجه اجرای آن در CodeSandbox

      import { useState } from 'react';
export default function FeedbackForm() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('');
  if (isSent) {
    return <h1>Thank you!</h1>;
  }
  return (
    <form onSubmit={e => {
      e.preventDefault();
      alert(`Sending: "${message}"`);
      setIsSent(true);
    }}>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <br />
      <button type="submit">Send</button>
    </form>
  );
}
    

سعی کنید فراخوانی دوم useState را به بعد از شرط if منتقل کنید و توجه داشته باشید که چگونه این عمل دوباره کار را خراب می‌کند. اگر linter شما برای ری‌اکت تنظیم شده باشد، باید هنگام انجام اشتباهاتی مانند این، یک خطای lint مشاهده کنید. اگر هنگام اجرای کد معیوب محلی، خطایی نمی‌بینید، باید linting را برای پروژه خود تنظیم کنید.

چالش چهارم

این یک نسخه اصلاح شده است که از یک متغیر name معمولی که در تابعی که به آن نیاز دارد، تعریف شده‌است، استفاده می‌کند: مشاهده جواب چالش و نتیجه اجرای آن در CodeSandbox

      export default function FeedbackForm() {
  function handleClick() {
    const name = prompt('What is your name?');
    alert(`Hello, ${name}!`);
  }
  return (
    <button onClick={handleClick}>
      Greet
    </button>
  );
}
    

یک متغیر وضعیت تنها زمانی ضروری است که بخواهد اطلاعاتی را در میان رندرهای مجدد یک کامپوننت نگه دارد. درون یک مدیر رویداد واحد، یک متغیر معمولی به درستی همین را کار می‌کند. زمانی که یک متغیر معمولی درست کار می‌کند، از متغیرهای وضعیت استفاده نکنید.

رای
0
ارسال نظر
مرتب سازی:
اولین نفری باشید که نظر می دهید!