قوانین Hook ها در React

Hook (یا قلاب‌) قابلیتی است که در آینده نزدیک منتشر شده و به شما اجازه می‌دهد بدون نوشتن یک class از حالت یا قابلیت‌های دیگر React استفاده کنید. این قابلیت در حال حاضر در نسخۀ ۱۶.۷.۰-alpha ری اکت موجود است.

هوک ها توابع جاوا اسکریپت هستند، اما هنگام استفاده از آنها لازم است دو قانون را رعایت کنید. ما یک پلاگین لینتر جهت اعمال خودکار این قوانین فراهم کرده‌ایم:

هوک ها را فقط در سطوح بالا فراخوانی کنید

هوک ها را داخل حلقه‌ها، شرط‌ها و یا توابع موجود در سطوح پایین فراخوانی نکنید. در مقابل همیشه در سطوح بالای تابع ری اکت از Hook ها استفاده کنید. با رعایت این قانون مطمئن خواهید بود که Hook ها در ترتیب یکسانی در هر رندر کامپوننت فراخوانی خواهند شد. این همان قانونی است که باعث می‌شود ری اکت به طور صحیح حالت هوک ها را بین فراخوانی‌های چندگانۀ useState و useEffect حفظ کند. (در این مورد در ادامۀ صفحه بیشتر توضیح خواهیم داد.)

Hook ها را فقط در کامپوننت های تابعی ری اکت فراخوانی کنید

Hook ها را از توابع معمولی جاوا اسکریپت فراخوانی نکنید. در مقابل می‌توانید:

  • هوک ها را از کامپوننت های تابعی ری اکت فراخوانی کنید.
  • هوک ها را از Hook های سفارشی فراخوانی کنید. (در صفحۀ بعدی این مورد را یاد خواهیم گرفت.)

با رعایت این قانون مطمئن خواهید شد که تمام منطق‌های حالت دار درون یک کامپوننت به طور واضح از کد منبع آن پیدا خواهد بود.

پلاگین ESLint

ما پلاگین ESLint را با نام eslint-plugin-react-hooks منتشر کرده‌ایم که اعمال این قانون‌ها را واجب می‌کند. می‌توانید این پلاگین را به پروژۀ خود اضافه کنید:

npm install eslint-plugin-react-hooks@next
// Your ESLint configuration
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"
  }
}

در آینده قصد داریم این پلاگین را به طور پیش‌فرض در Create React App و جعبه ابزارهای مشابه بگنجانیم.

در این لحظه می‌توانید به صفحۀ بعد که در مورد نحوۀ نوشتن هوک سفارشی خودتان است بروید. در ادامۀ این مطلب ما می‌خواهیم دلایل پشت این قوانین را توضیح دهیم.

توضیحات قوانین

همانطور که قبلا یاد گرفتیم، می‌توانیم چند State Hook یا Effect Hook را در یک کامپوننت استفاده کنیم:

function Form() {
  // ۱. Use the name state variable
  const [name, setName] = useState('Mary');

  // ۲. Use an effect for persisting the form
  useEffect(function persistForm() {
    localStorage.setItem('formData', name);
  });

  // ۳. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');

  // ۴. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = name + ' ' + surname;
  });

  // ...
}

حال ری اکت چگونه متوجه می‌شود که کدام حالت متناظر با کدام فراخوانی useState است؟ پاسخ این است که بر مبنای ترتیبی که هوک ها فراخوانی شده‌اند کارش را انجام می‌دهد. مثال ما کار می‌کند چون ترتیب فراخوانی هوک ها در هر رندر یکسان است:

// ------------
// First render
// ------------
useState('Mary')           // 1. Initialize the name state variable with 'Mary'
useEffect(persistForm)     // 2. Add an effect for persisting the form
useState('Poppins')        // 3. Initialize the surname state variable with 'Poppins'
useEffect(updateTitle)     // 4. Add an effect for updating the title

// -------------
// Second render
// -------------
useState('Mary')           // 1. Read the name state variable (argument is ignored)
useEffect(persistForm)     // 2. Replace the effect for persisting the form
useState('Poppins')        // 3. Read the surname state variable (argument is ignored)
useEffect(updateTitle)     // 4. Replace the effect for updating the title

// ...

تا زمانی که ترتیب فراخوانی Hook ها در هر رندر یکسان است، ری اکت می‌تواند برخی حالت های محلی را با هر کدام از آنها پیوند دهد. اما اگر ما یک فراخوانی Hook را (برای مثال persistForm effect) داخل یک شرط قرار دهیم چه خواهد شد؟

// 🔴 We're breaking the first rule by using a Hook in a condition
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }

شرط name !== ” در رندر اول true بوده و در نتیجه این Hook اجرا می‌گردد. هرچند در رندر بعدی ممکن است کاربر فرم را پاک کرده و شرط false شود. اکنون که این Hook از رندر خارج شده است، ترتیب فراخوانی Hook ها متفاوت خواهد بود:

useState('Mary')           // 1. Read the name state variable (argument is ignored)
// useEffect(persistForm)  // 🔴 This Hook was skipped!
useState('Poppins')        // 🔴 ۲ (but was 3). Fail to read the surname state variable
useEffect(updateTitle)     // 🔴 ۳ (but was 4). Fail to replace the effect

ری اکت نمی‌داند که برای فراخوانی Hook دوم useState چه چیزی را برگرداند. ری اکت انتظار دارد که فراخوانی Hook دوم در این کامپوننت متناظر با persistForm باشد، همانطور که در رندر قبلی بود، اما دیگر نیست. از این نقطه بعد ترتیب فراخوانی Hook ها یکی تغییر کرده و باعث به وجود آمدن باگ‌هایی می‌شود.

به همین دلیل است که Hook ها باید در سطوح بالای کامپوننت ما فراخوانی شوند. اگر می‌خواهیم یک effect را به طور شرطی اجرا کنیم، می‌توانید آن شرط را داخل Hook مورد نظر قرار دهیم:

useEffect(function persistForm() {
    // 👍 We're not breaking the first rule anymore
    if (name !== '') {
      localStorage.setItem('formData', name);
    }
  });

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

گام‌های بعدی

در نهایت، آماده شده‌ایم تا در مورد نوشتن Hook های سفارشی خودمان آموزش لازم را ببینیم. Hook های سفارشی به شما اجازه می‌دهند Hook های فراهم شده توسط ری اکت را در مفهومی که خودتان می‌خواهید با هم ترکیب کنید و از منطق حالت دار معمول بین کامپوننت های مختلف استفادۀ مجدد داشته باشید.

1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 5٫00 out of 5)
Loading...
counter customizable free hit