پرسش و پاسخ های Hooks در React

در این مطلب به تعدادی از پرسش های متداول در مورد Hook ها پاسخ داده خواهد شد.

  • اتخاذ استراتژی
  • آیا نیاز است تمام کامپوننت هایم را بازنویسی کنم؟
  • چه مقدار از دانش من نسبت به React با آن مرتبط خواهد بود؟
  • بهتر است از Hook ها استفاده کنم یا class ها و یا ترکیبی از آنها؟
  • آیا Hook ها تمام موارد کاربرد class ها را پوشش می‌دهند؟
  • آیا Hook ها جایگزین render props و کامپوننت های مرتبۀ بالاتر می‌شوند؟
  • Hook ها برای API های پرطرفداری مانند Redux connect() و React Router چه کاربردی دارند؟
  • آیا Hook ها با تایپ استاتیک هم کار می‌کنند؟
  • چگونه کامپوننت هایی را که از Hook ها استفاده می‌کنند تست کنیم؟
  • اعمال قوانین lint دقیقا چه کاری انجام می‌دهد؟
  • از class ها به سمت Hook ها
  • متدهای چرخۀ زندگی چه ارتباطی با Hook ها دارند؟
  • در Hook ها هم چیزی مانند متغیرهای شیء وجود دارد؟
  • باید از یک و یا چند متغیر حالت استفاده کنم؟
  • فقط می‌توانم در آپدیت ها effect را اجرا کنم؟
  • چگونه prop ها یا حالت قبلی را بدست آورم؟
  • چگونه getDerivedStateFromProps را پیاده سازی کنم؟
  • آیا می‌توانم یک ref به کامپوننت تابعی بدهم؟
  • عبارت const [thing, setThing] = useState() چه معنایی دارد؟
  • بهینه سازی عملکرد
  • آیا می‌توانم یک effect را در آپدیت هایی skip کنم؟
  • چگونه shouldComponentUpdate را پیاده سازی کنم؟
  • چگونه محاسبات را memorize کنم؟
  • چگونه آبجکت‌های سنگین را به کندی بسازم؟
  • آیا Hook ها بخاطر ساخت توابع در رندر کند هستند؟
  • چگونه از انتقال بازخوانی‌ها به پایین جلوگیری کنم؟
  • چگونه یک متغیر پرتغییر را از useCallback بخوانم؟
  • نحوۀ کار
  • ری اکت چگونه فراخوانی‌های Hook را با کامپوننت ها ارتباط می‌دهد؟
  • مزیت برجستۀ Hook ها در چیست؟

اتخاذ استراتژی

آیا نیاز است تمام کامپوننت هایم را بازنویسی کنم؟

خیر. هیچ برنامه‌ای برای حذف class ها از ری اکت نداریم. ما فقط می‌خواهیم محصولات جدید را به مسیر جدیدی سوق دهیم و تمایلی برای بازنویسی نداریم. توصیه می‌کنیم از Hook ها در کدهای جدید استفاده کنید.

چه مقدار از دانش من نسبت به React با آن مرتبط خواهد بود؟

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

Hook ها منحنی یادگیری خود را دارند. اگر در مستندات مربوط به Hook ها کمبودی می‌بینید، آن را به ما اطلاع دهید تا در مورد آن به شما کمک کنیم.

بهتر است از Hook ها استفاده کنم یا class ها و یا ترکیبی از آنها؟

هر زمان آماده بودید، پیشنهاد می‌کنیم در کامپوننت های جدیدی که می‌نویسید استفاده از Hook ها را شروع کنید. مطمئن شوید که تمام اعضای تیم شما با استفاده از آن و مستنداتی که اینجا برایتان فراهم شده آشنا شده باشند. توضیه می‌کنیم class های موجود خود را با Hook ها بازنویسی نکنید، مگر اینکه مثلا به خاطر وجود باگ‌هایی، برنامۀ این کار را داشته باشید.

شما نمی‌توانید از Hook ها درون کامپوننت های class استفاده کنید، اما قطعا می‌توانید در یک درخت، class ها و کامپوننت های تابعی را با Hook ها ترکیب کنید. چه یک کامپوننت از نوع class باشد و چه از نوع تابعی که از Hook ها استفاده می‌کند، یک جزئیات پیاده سازی از ان کامپوننت خواهد بود. در بلندمدت، انتظار داریم Hook ها شیوۀ اصلی نوشتن کامپوننت های ری اکت توسط مردم شوند.

آیا Hook ها تمام موارد کاربرد class ها را پوشش می‌دهند؟

هدف ما نسبت به Hook ها این است که در نزدیک‌ترین زمان ممکن تمام موارد استفادۀ class ها را پوشش دهیم. هنوز هیچ Hook معادلی برای چرخه‌های زندگی غیررایج getSnapshotBeforeUpdate و componentDidCatch موجود نیست اما برنامه داریم به زودی آنها را اضافه کنیم.

تازه شروع کار Hook هاست، لذا یکپارچگی کامل پشتیبانی مانند DevTools یا Flow/TypeScript ممکن است هنوز آماده نباشد. همینطور برخی کتابخانه‌های جانبی نیز ممکن است هنوز با Hook ها سازگار نباشند.

آیا Hook ها جایگزین render props و کامپوننت های مرتبۀ بالاتر می‌شوند؟

اغلب render props و کامپوننت های مرتبۀ بالاتر فقط یک child تنها را رندر می‌کنند. ما فکر می‌کنیم Hook ها راهی آسان‌تر جهت انجام این کار هستند. البته هنوز برای هردو الگو فضا هست (برای مثال، یک کامپوننت اسکرولر مجازی ممکن است یک renderItem prop داشته باشد و یا یک کامپوننت محتوای بصری ممکن است ساختار DOM خودش را داشته باشد). اما در اکثر موارد، Hook ها مفید بوده و باعث کم شدن لایه‌های زیرین در درخت شما خواهند شد.

Hook ها برای API های پرطرفداری مانند Redux connect() و React Router چه کاربردی دارند؟

شما می‌توانید به استفاده از API هایی که همیشه استفاده می‌کردید، ادامه دهید؛ آنها همچنان کار خواهند کرد.

در آینده، نسخه‌های جدید این کتابخانه‌ها ممکن است Hook هایی سفارشی مانند useRedux() یا useRouter() را منتشر کنند که به شما اجازه می‌دهند بدون نیاز به کامپوننت های بسته بندی کننده، از همان قابلیت‌ها استفاده کنید.

آیا Hook ها با تایپ استاتیک هم کار می‌کنند؟

Hook ها با در نظر داشتن تایپ استاتیک طراحی شده‌اند. چون آنها تابع هستند، درست تایپ کردن آنها نسبت به الگوهایی مانند کامپوننت های مرتبۀ بالاتر راحت‌تر است. ما از پیش با هردوی Flow و TypeScript ارتباط برقرار کردیم و آنها برنامه دارند در آینده شامل تعاریفی در مورد Hook های ری اکت شوند.

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

چگونه کامپوننت هایی را که از Hook ها استفاده می‌کنند تست کنیم؟

از دید React، کامپوننتی که از Hook ها استفاده می‌کند نیز درست مانند یک کامپوننت معمولی است. اگر روش تست کردن شما وابسته به درونیات React نباشد، تست کردن کامپوننت هایی که شامل Hook ها می‌شوند، نباید تفاوتی با شیوۀ نرمال تست کردن کامپوننت های شما داشته باشد.

اگر می‌خواهید یک Hook سفارشی را تست کنید، می‌توانید این کار را با ساخت یک کامپوننت در تست خودتان و استفاده از Hook خود در آن کامپوننت، انجام دهید. سپس می‌توانید آن کامپوننت که نوشته‌اید را تست کنید.

اعمال قوانین lint دقیقا چه کاری انجام می‌دهد؟

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

به طور ویژه، قوانین باعث می‌شوند:

  • فراخوانی Hook ها یا داخل یک تابع PascalCase باشند (با فرض اینکه یک کامپوننت هستند) و یا داخل یک تابع useSomething دیگر (با فرض اینکه یک Hook سفارشی هستند).
  • Hook ها به ترتیب یکسانی در هر رندر فراخوانی شوند.

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

از class ها به سمت Hook ها

متدهای چرخۀ زندگی چه ارتباطی با Hook ها دارند؟

  • Constructor : کامپوننت های تابعی نیازی به constructor ندارند. شما می‌توانید حالت را در فراخوانی useState مقداردهی اولیه کنید. اگر محاسبۀ آن دشوار و سنگین است می‌توانید یک تابع را به useState منتقل کنید.
  • getDerivedStateFromProps : هنگام رندر کردن، یک آپدیت را برنامه ریزی می‌کند.
  • shouldComponentUpdate: بخش مربوط به memo که در قسمت بهینه سازی عملکرد آمده است را ببینید.
  • Render : این خودش بدنۀ تابع کامپوننت است.
  • componentDidMount و componentDidUpdate و componentWillUnmount : یک هوک useEffect می‌تواند تمام ترکیبات آنها را بیان کند (شامل مواردی که چندان رایج هم نیستند)
  • componentDidCatch و getDerivedStateFromError : هنوز Hook معادل با این متدها وجود ندارد ولی بزودی اضافه خواهد شد.

در Hook ها هم چیزی مانند متغیرهای شیء وجود دارد؟

بله. هوک useRef() فقط برای DOM ref ها نیست. آبجکت ref یک نگه دارندۀ عمومی است که مشخصۀ current آن قابل تغییر بوده و می‌تواند هر مقداری را نگه دارد، درست مانند یک مشخصۀ شیء در یک class.

شما می‌توانید آن را از داخل useEffect بنویسید:

function Timer() {
  const intervalRef = useRef();

  useEffect(() => {
    const id = setInterval(() => {
      // ...
    });
    intervalRef.current = id;
    return () => {
      clearInterval(intervalRef.current);
    };
  });

  // ...
}

اگر فقط می‌خواستیم یک فاصله ایجاد کنیم، نیاز به آن ref نداشتیم (id می‌توانست برای effect به صورت محلی باشد)، اما اگر بخواهیم فاصله را از یک مدیریت کنندۀ رویداد جدا کنیم، مفید خواهد بود:

// ...
  function handleCancelClick() {
    clearInterval(intervalRef.current);
  }
  // ...

از نظر مفهومی، شما می‌توانید به ref ها به عنوان مشابه متغیرهای شیء در یک class فکر کنید. مگر در حالتی که دارید مقداردهی اولیۀ lazy را انجام می‌دهید، از تنظیم ref ها حین رندرها خودداری کنید، چون باعث رفتارهای عجیبی می‌شود. در مقابل، معمولا شما می‌خواهید ref ها را در مدیریت کننده‌های رویداد و effect ها ویرایش کنید.

باید از یک و یا چند متغیر حالت استفاده کنم؟

اگر از class ها به سمت Hook ها آمده‌اید، احتمالا وسوسه خواهید شد که همیشه useState() را فراخوانی کنید و تمام حالت‌ها را در یک آبجکت قرار دهید. اگر بخواهید می‌توانید این کار را بکنید. در این قسمت مثالی آورده شده است که حرکات موس را دنبال می‌کند. ما موقعیت و ابعاد آن را در حالت محلی نگه می‌داریم:

function Box() {
  const [state, setState] = useState({ left: 0, top: 0, width: 100, height: 100 });
  // ...
}

اکنون فرض کنید می‌خواهیم منطقی بنویسیم که وقتی کاربر موس را تکان می‌دهد، left و top را تغییر دهد. دقت کنید که چگونه ما باید این فیلدها را به طور دستی در آبجکت حالت قبلی ادغام کنیم:

// ...
  useEffect(() => {
    function handleWindowMouseMove(e) {
      // Spreading "...state" ensures we don't "lose" width and height
      setState(state => ({ ...state, left: e.pageX, top: e.pageY }));
    }
    // Note: this implementation is a bit simplified
    window.addEventListener('mousemove', handleWindowMouseMove);
    return () => window.removeEventListener('mousemove', handleWindowMouseMove);
  }, []);
  // ...

این به این خاطر است که وقتی ما یک متغیر حالت را آپدیت می‌کنیم، مقدار آن را جایگزین می‌کنیم. این با this.setState در یک class که فیلدهای آپدیت شده را در آبجکت ادغام می‌کند فرق دارد.

اگر ادغام خودکار را فراموش کرده‌اید، می‌توانید یک هوک useLegacyState بنویسید که آپدیت های آبجکت حالت را ادغام می‌کند. هرچند، در مقابل، توصیه ما این است که حالت را بر مبنای این که چه مقادیری تمایل به تغییر با هم را دارند، در چند متغیر حالت تقسیم کنید.

برای مثال، ما می‌توانیم حالت کامپوننت مثال خود را به آبجکت های position و size تقسیم کنیم، و همیشه position را بدون نیاز به ادغام، جایگزین کنیم:

function Box() {
  const [position, setPosition] = useState({ left: 0, top: 0 });
  const [size, setSize] = useState({ width: 100, height: 100 });

  useEffect(() => {
    function handleWindowMouseMove(e) {
      setPosition({ left: e.pageX, top: e.pageY });
    }
    // ...

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

function Box() {
  const position = useWindowPosition();
  const [size, setSize] = useState({ width: 100, height: 100 });
  // ...
}

function useWindowPosition() {
  const [position, setPosition] = useState({ left: 0, top: 0 });
  useEffect(() => {
    // ...
  }, []);
  return position;
}

دقت کنید که چگونه ما قادر بودیم فراخوانی useState برای متغیر حالت position و effect مرتبط را به داخل یک Hook سفارشی و بدون نیاز به تغییر کد، جا به جا کنیم. اگر تمام حالت ها در یک آبجکت قرار داشتند، استخراج آن می‌توانست سخت‌تر باشد.

اما قرار دادن تمام حالت ها در یک فراخوانی useState و داشتن یک فراخوانی useState برای هر کدام هم می‌توانست کار کند. وقتی شما تعادلی بین دو حالت حدی پیدا کنید و حالت های مرتبط را داخل چند متغیر حالت مستقل گروه بندی کنید، کامپوننت ها خواناتر خواهند بود. اگر منطق حالت پیچیده شود، توصیه ما مدیریت آن با یک کاهنده (reducer) و یا یک Hook سفارشی است.

فقط می‌توانم در آپدیت ها effect را اجرا کنم؟

این یک مورد استفادۀ نادر است. اگر نیاز به انجام آن داشتید، می‌توانید از یک ref تغییرپذیر استفاده کنید تا به طور دستی یک مقدار بولین متناظر با اینکه در رندر اول یا بعدی هستید را ذخیره کنید و سپس آن flag را در effect خود چک کنید. (اگر دیدید که خیلی به این کار نیازمندید، شاید بهتر باشد یک Hook سفارشی برای این کار بسازید.)

چگونه prop ها یا حالت قبلی را بدست آورم؟

در حال حاضر می‌توانید این کار را به طور دستی با یک ref انجام دهید:

function Counter() {
  const [count, setCount] = useState(0);

  const prevCountRef = useRef();
  useEffect(() => {
    prevCountRef.current = count;
  });
  const prevCount = prevCountRef.current;

  return <h1>Now: {count}, before: {prevCount}</h1>;
}

این می‌تواند کمی پیچیده باشد اما شما می‌توانید آن را داخل یک Hook سفارشی استخراج کنید:

function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  return <h1>Now: {count}, before: {prevCount}</h1>;
}

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

دقت کنید که این چگونه برای prop ها، حالت و یا هر مقدار محاسبه شدۀ دیگری کار می‌کند.

function Counter() {
  const [count, setCount] = useState(0);

  const calculation = count * 100;
  const prevCalculation = usePrevious(calculation);
  // ...

از آن جایی که این مورد تقریبا پرکاربرد است، احتمال دارد در آینده ری اکت یک هوک usePrevious برای این کار آماده کند.

همچنین می‌توانید الگوی پیشنهادی برای حالت به دست آمده را ببینید.

چگونه getDerivedStateFromProps را پیاده سازی کنم؟

هرچند احتمالا به آن نیازی نخواهید داشت، اما در موارد نادری که نیاز داشتید (مثلا پیاده سازی یک کامپوننت <Transition>) شما می‌توانید حالت را درست در زمان رندر آپدیت کنید. ری اکت کامپوننت را با حالت آپدیت شده، فورا پس از خارج شدن از رندر اول، رندر مجدد می‌کند، لذا سنگین نخواهد بود.

در اینجا ما مقدار قبلی row prop را در یک متغیر حالت ذخیره می‌کنیم و در نتیجه می‌توانیم مقایسه را انجام دهیم:

function ScrollView({row}) {
  let [isScrollingDown, setIsScrollingDown] = useState(false);
  let [prevRow, setPrevRow] = useState(null);

  if (row !== prevRow) {
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
}

این کار ممکن است در ابتدا عجیب به نظر بیاید، اما از نظر مفهومی، یک آپدیت در زمان رندر، دقیقا همان کاری است که getDerivedStateFromProps همیشه انجام می‌داد.

آیا می‌توانم یک ref به کامپوننت تابعی بدهم؟

هرچند زیاد به این کار نیاز پیدا نخواهید کرد، ممکن است بخواهید برخی متدهای ضروری را در معرض یک کامپوننت والد قرار دهید و این کار را می‌توانید با هوک useImperativeHandle انجام دهید.

عبارت const [thing, setThing] = useState() چه معنایی دارد؟

اگر با این سینتکس آشنا نیستید، توضیحات مربوط به این قسمت را می‌توانید در صفحۀ مربوط به State Hook ببینید.

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

آیا می‌توانم یک effect را در آپدیت هایی skip کنم؟

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

چگونه shouldComponentUpdate را پیاده سازی کنم؟

شما می‌توانید یک کامپوننت تابعی را با React.memo بسته بندی کنید تا به طور سطحی prop های آن را مقایسه کنید:

const Button = React.memo((props) => {
  // your component
});

این یک Hook نیست، چون مانند Hook ها compose (تولید، ساخت) نمی‌کند. React.memo معادل PureComponent است اما فقط prop ها را مقایسه می‌کند. (همچنین شما می‌توانید آرگومان دومی اضافه کنید تا تابع مقایسه خاصی که prop های قبلی و جدید را می‌گیرد را مشخص کنید. اگر true را برگرداند یعنی آپدیت skip شده است.)

React.memo حالت را مقایسه نمی‌کند چون هیچ آبجکت حالتی وجود ندارد. اما شما می‌توانید children را نیز pure کنید و یا حتی children مستقل را با useMemo بهینه سازی کنید.

چگونه محاسبات را memorize کنم؟

هوک useMemo به شما اجازه می‌دهد محاسبات بین چند رندر را با به یاد آوری محاسبات قبلی، کسب کنید:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

این کد computeExpensiveValue(a, b) را فراخوانی می‌کند. اما اگر ورودی‌های [a, b] نسبت به آخرین مقدارشان تغییر نکنند، useMemo دیگر برای بار دیگر آن را فراخوانی نکرده و از آخرین مقداری که برگرداند مجددا استفاده خواهد کرد.

به یاد داشته باشید که تابع منتقل شده به useMemo حین رندر اجرا می‌شود. کاری را که به طور معمول حین رندر انجام نمی‌دهید، آنجا هم انجام ندهید. برای مثال اثرات جانبی متعلق به useEffect هستند، نه useMemo.

شما می‌توانید روی useMemo به عنوان یک بهینه ساز عملکرد تکیه کنید، نه تضمین معنایی. در آینده شاید ری اکت تصمیم بگیرد برخی مقادیر memorize شدۀ قبلی را فراموش کرده و آنها را در رندر بعدی مجددا محاسبه کند تا برای کامپوننت های خارج صفحه فضا ایجاد کند. کد خود را طری بنویسید که بدون useMemo هم بتواند کار کند و سپس آن را برای بهبود عملکرد اضافه کنید. (در برخی موارد نادر که یک مقدار هرگز نیابد محاسبۀ مجدد شود، می‌توانید یک ref را lazily initialize کنید.)

همچنین useMemo به شما اجازه می‌دهد یک رندر مجدد سنگین از یک child را skip کنید:

function Parent({ a, b }) {
  // Only re-rendered if `a` changes:
  const child1 = useMemo(() => <Child1 a={a} />, [a]);
  // Only re-rendered if `b` changes:
  const child2 = useMemo(() => <Child2 b={b} />, [b]);
  return (
    <>
      {child1}
      {child2}
    </>
  )
}

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

چگونه آبجکت‌های سنگین را به کندی بسازم؟

استفاده از useMemo به شما اجازه می‌دهد محاسبات سنگین را در صورتی که ورودی‌ها یکسان باشند memorize کند و به یاد بسپارید. هرچند این کار فقط یک چیز جزئی است و تضمین نمی‌کند محاسبات اجرای مجدد نخواهد شد. اما گاهی اوقات نیاز است مطمئن شویم یک آبجکت فقط یک مرتبه ساخته می‌شود.

اولین مورد رایج وقتی است که حالت اولیه سنگین باشد:

function Table(props) {
  // ⚠️ createRows() is called on every render
  const [rows, setRows] = useState(createRows(props.count));
  // ...
}

جهت اجتناب از ساخت مجدد حالت اولیۀ نادیده گرفته شده، می‌توانیم یک تابع را به useState منتقل کنیم:

function Table(props) {
  // ✅ createRows() is only called once
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}

ری اکت این تابع را فقط حین اولین رندر فراخوانی می‌کند. می‌توانید بخش مربوط به useState API را ببینید.

همچنین گاهی اوقات ممکن است بخواهیم از ساخت مجدد مقدار اولیۀ useRef() نیز جلوگیری کنید. برای مثال، شاید بخواهید مطمئن شوید که برخی شیء class فقط یک مرتبه تولید می‌شوند:

function Image(props) {
  // ⚠️ IntersectionObserver is created on every render
  const ref = useRef(new IntersectionObserver(onIntersect));
  // ...
}

useRef یک تابع خاص را بیش از حد نمی‌پذیرد. در مقابل شما می‌توانید تابع خود را بنویسید که آن را به کندی (lazily) می‌سازد و تنظیم می‌کند:

function Image(props) {
  const ref = useRef(null);

  // ✅ IntersectionObserver is created lazily once
  function getObserver() {
    let observer = ref.current;
    if (observer !== null) {
      return observer;
    }
    let newObserver = new IntersectionObserver(onIntersect);
    ref.current = newObserver;
    return newObserver;
  }

  // When you need it, call getObserver()
  // ...
}

این کار از ساخت یک آبجکت سنگین جلوگیری می‌کند، تا زمانی که واقعا برای اولین بار مورد نیاز باشد. اگر از Flow یا TypeScript استفاده می‌کنید، می‌توانید جهت سازگاری، یک نوع غیر null را به getObserver() بدهید.

آیا Hook ها بخاطر ساخت توابع در رندر کند هستند؟

خیر. در مرورگرهای مدرن، عملکرد خام بستن در مقایسه با class ها تفاوت قابل ملاحظه‌ای ندارد، مگر در موارد خاصی.

به علاوه، در نظر داشته باشید که طراحی Hook ها از چند نظر مؤثرتر است:

  • Hook ها به خیلی از مواردی که class ها احتیاج دارند نیازی ندارند مانند هزینۀ ساخت شیء های class و bind کردن مدیریت کننده‌های رویدادها.
  • کد آیدیوماتیک هنگام استفاده از Hook ها نیازی به سطوح پایین درخت کامپوننت ندارند، چیزی که در کدبیس‌هایی که از کامپوننت های مرتبۀ بالاتر، render props و context استفاده می‌کنند بسیار شایع است. با درخت های کامپوننت کوچکتر، ری اکت کار کمتری برای انجام خواهد داشت.

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

  • هوک useCallback به شما اجازه می‌دهد مرجع فراخوانی یکسانی را بین رندرهای مجدد حفظ کنید به گونه‌ای که shouldComponentUpdate کارش را انجام دهد:
// Will not change unless `a` or `b` changes
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);
  • هوک useMemo زمانی که آپدیت children انجام می‌شود، کنترل را آسان‌تر می‌کند که باعث کاهش نیاز به کامپوننت های pure می‌گردد.
  • و در نهایت، هوک useReducer نیاز به انتقال فراخوانی‌ها به طور عمیق را همانطور که در ادامه توضیح داده شده است، کاهش می‌دهد.

چگونه از انتقال بازخوانی‌ها به پایین جلوگیری کنم؟

ما متوجه شدیم که اکثر افراد با انتقال دستی فراخوانی‌ها به سطوح مختلف درخت کامپوننت راحت نیستند.

در درخت های کامپوننت بزرگ، راه جایگزین ما انتقال یک تابع dispatch از useReducer به وسیلۀ context است:

const TodosDispatch = React.createContext(null);

function TodosApp() {
  // Tip: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer);

  return (
    <TodosDispatch.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosDispatch.Provider>
  );
}

هر child درخت داخل TodosApp می‌تواند از تابع dispatch جهت انتقال عملکردها به بالا در TodosApp استفاده کند:

function DeepChild(props) {
  // If we want to perform an action, we can get dispatch from context.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }

  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

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

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

چگونه یک متغیر پرتغییر را از useCallback بخوانم؟

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

در برخی موارد نادر ممکن است نیاز داشته باشید با useCallback یک فراخوانی را memorize کنید و به یاد بسپارید، اما memorize کردن به خاطر اینکه تابع داخلی دفعات زیادی باید بازسازی شود، خوب کار نخواهد کرد. اگر تابعی که در حال memorize کردن آن هستید یک مدیریت کنندۀ رویداد است و حین رندر از آن استفاده نمی‌شود، می‌توانید از یک ref به عنوان یک متغیر شیء استفاده کنید و آخرین مقدار وارد شده به آن را به طور دستی ذخیره کنید:

function Form() {
  const [text, updateText] = useState('');
  const textRef = useRef();

  useLayoutEffect(() => {
    textRef.current = text; // Write it to the ref
  });

  const handleSubmit = useCallback(() => {
    const currentText = textRef.current; // Read it from the ref
    alert(currentText);
  }, [textRef]); // Don't recreate handleSubmit like [text] would do

  return (
    <>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}

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

function Form() {
  const [text, updateText] = useState('');
  // Will be memoized even if `text` changes:
  const handleSubmit = useEventCallback(() => {
    alert(text);
  }, [text]);

  return (
    <>
      <input value={text} onChange={e => updateText(e.target.value)} />
      <ExpensiveTree onSubmit={handleSubmit} />
    </>
  );
}

function useEventCallback(fn, dependencies) {
  const ref = useRef(() => {
    throw new Error('Cannot call an event handler while rendering.');
  });

  useLayoutEffect(() => {
    ref.current = fn;
  }, [fn, ...dependencies]);

  return useCallback(() => {
    const fn = ref.current;
    return fn();
  }, [ref]);
}

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

نحوۀ کار

ری اکت چگونه فراخوانی‌های Hook را با کامپوننت ها ارتباط می‌دهد؟

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

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

مزیت برجستۀ Hook ها در چیست؟

Hook ها ایده‌هایی از منابع مختلف را با هم ترکیب کرده‌اند:

  • تجربه‌های قدیمی ما از API های تابعی در react-future
  • تجربیات جمعی ری اکت از API های render prop شامل کامپوننت بازخوردی Ryan Florence
  • اتخاذ کلمۀ کلیدی Dominic Gannaway به عنوان sugar syntax برای render prop
  • متغیرهای حالت و سلول‌های حالت در DisplayScript
  • کامپوننت های کاهنده در ReasonReact
  • اشتراکات در Rx
  • افکت های جبری در Multicore OCaml

ایده اولیه طراحی Hook ها توسط Sebastian Markbåge مطرح شد و سپس توسط Andrew Clark و Sophie Alpert و Dominic Gannaway و اعضای دیگر تیم ری اکت اصلاح شد.

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