چرخه‌ی ویرایش برای state و useEffect

Edit Cycle for State and useEffect

23 بهمن 1399
چرخه‌ی ویرایش برای state و useEffect

چرخه‌ ویرایش برای state

در جلسه قبل در رابطه با چرخه ویرایش (Update Lifecycle) برای props صحبت کردیم و مهم ترین نکته ای که باید به خاطر بسپارید این است که componentDidUpdate از همه آنها مهم تر است. در این جلسه باید در رابطه با چرخه‌ ویرایش برای state صحبت کنیم. اگر به کامپوننت App.js نگاه کنید متوجه می شوید که ما در آن state را تغییر می دهیم (زمانی که کاربر درون input چیزی تایپ کند). بنابراین پس از componentDidMount متد componentDidUpdate را برایش می نویسیم:

  componentDidUpdate() {
    console.log('[App.js] componentDidUpdate');
  }

حالا قبل از همین کد از متد shouldComponentUpdate استفاده می کنیم:

  shouldComponentUpdate(nextProps, nextState) {
    console.log('[App.js] shouldComponentUpdate');
  }

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

  static getDerivedStateFromProps(props, state) {
    console.log('[App.js] getDerivedStateFromProps', props);
    return state;
  }

  componentDidMount() {
    console.log('[App.js] componentDidMount');
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log('[App.js] shouldComponentUpdate');
  }

  componentDidUpdate() {
    console.log('[App.js] componentDidUpdate');
  }

اگر الان به مرورگر بروید در console با خطا مواجه شده و دکمه Toggle Persons دیگر کار نمی کند. می دانید چرا؟ همانطور که گفتم shouldComponentUpdate باید چیزی را برگرداند و اگر چیزی را برایش ننویسیم مقدار undefined برگردانده می شود و در چنین حالتی از بروز رسانی و فرآیند update جلوگیری خواهد شد. به طور مثال برای واضح تر شدن کد آن را بدین شکل می نویسم:

  shouldComponentUpdate(nextProps, nextState) {
    console.log('[App.js] shouldComponentUpdate');
    return false;
  }

این کد باعث می شود دکمه Toggle Persons کار نکند و معادل کد قبلی ما است. در مورد این متد بعدا بیشتر صحبت خواهیم کرد اما فعلا آن را بدین صورت می نویسیم تا کدهایمان دچار مشکل نشوند:

  shouldComponentUpdate(nextProps, nextState) {
    console.log('[App.js] shouldComponentUpdate');
    return true;
  }

حالا اگر به مرورگر و قسمت Console آن بروید با تایپ کردن درون input شاهد تمامی این تغییرات خواهید بود.

هدف از این قسمت و قسمت قبلی این بود که به طور کامل با چرخه زندگی کامپوننت ها آشنا شوید تا بتوانید هر قسمت را به صورت دلخواه تغییر دهید. مهم ترین متدهایی که باید به یاد داشته باشید componentDidMount و componentDidUpdate و shouldComponentUpdate هستند. با دو متد اول می توانید کارهایی مانند درخواست داده جدید از سرور را انجام دهید و با استفاده از shouldComponentUpdate نیز می توانید عملکرد و سرعت برنامه تان را بهبود ببخشید (بعدا در رابطه با آن صحبت خواهیم کرد).

حالا نوبت کامپوننت های کاربردی است. همانطور که گفتیم Lifecycle ها فقط برای کامپوننت های کلاس-محور در دسترس هستند. با معرفی hook ها در نسخه جدید react میتوان state را در کامپوننت های کاربردی نیز داشت بنابراین کدهای معادل برای lifecycle hook در کامپوننت های کاربردی چیست؟

استفاده از useEffect در کامپوننت های کاربردی

اگر به کامپوننت cockpit.js نگاه کنید، حتما متوجه می شوید که این کامپوننت از نوع کاربردی است (درون یک تابع قرار دارد) بنابراین نمی توانیم از lifecycle ها در آن استفاده کنیم. راه حل معادل آن استفاده از یک hook به نام useEffect است که باید آن را با react وارد همین فایل cockpit.js کنیم:

import React, { useEffect } from 'react';

تا الان با دو عدد از مهم ترین hook های react آشنا شده ایم:

  • useState که برای کار با state در کامپوننت های کاربردی بود (ر.ک جلسات قبل).
  • useEffect که تمام Lifecycle hook های کامپوننت های کلاس-محور را درون یک hook جمع کرده است!

نکته: قبلا هم گفته بودیم که Lifecycle hook ها هیچ ربطی به react hook ها (مانند useState و useEffect و ...) ندارند. وجود کلمه hook در آن ها نباید شما را گیج کند.

useEffect یک آرگومان می گیرد که یک تابع است و برای هر چرخه render اجرا می شود. به طور مثال:

    useEffect(() => {
        console.log('[Cockpit.js] useEffect');
    })

این کد را درون بدنه cockpit.js اضافه کرده ایم. حالا اگر به مرورگر برویم می بینیم که با refresh شدن صفحه، کد log در قسمت console نمایش داده می شود:

useEffect قبل از کلیک روی دکمه ی Toggle
useEffect قبل از کلیک روی دکمه Toggle

 از آنجایی که تابع درون useEffect برای هر چرخه render اجرا می شود، اگر روی دکمه Toggle Persons کلیک کنیم دوباره آن را خواهیم دید:

useEffect پس از کلیک روی Toggle Person

useEffect پس از کلیک روی Toggle Person

چرا؟ به دلیل اینکه ما در برنامه تغییر ایجاد کرده ایم و این تغییر باعث render شدن دوباره cockpit می شود. حالا اگر چیزی درون input ها تایپ کنید باز هم تابع درون useEffect یک پیام log دیگر را در console نمایش می دهد. درست است که چیزی را در cockpit تغییر نداده ایم اما react چنین منطقی دارد:

App.js دوباره render می شود چرا که تایپ در input باعث تغییر state می شود. بنابراین متد render درون فایل App.js صدا زده می شود (همان قسمتی که کدهای JSX درون آن است). اگر دقت کنید عنصر cockpit نیز در همان قسمت قرار دارد:

    return (
      <div className={classes.App}>
        <Cockpit
          title={this.props.appTitle}
          showPersons={this.state.showPersons}
          persons={this.state.persons}
          clicked={this.togglePersonsHandler} />
        {persons}
      </div>
    );

به همین خاطر قسمت cockpit هم دوباره Render می شود (بعدا با انجام بهینه سازی از این فرآیند جلوگیری خواهیم کرد).

نکته: زمانی که میگویم دوباره render می شود (کلیدواژه re-render به معنی render دوباره) منظور من render شدن در DOM نیست. react یک virtual DOM دارد که جدا از DOM واقعی در مرورگر است. زمانی که تغییری ایجاد می شود react ابتدا virtual DOM خود را render می کند و اگر نیازی بود به سراغ render کردن DOM اصلی در مرورگر می رود. در قسمت های بعدی در مورد virtual DOM و جلوگیری از render شدن بیهوده کامپوننت ها صحبت خواهیم کرد اما فعلا در همین حد کافی برای شروع است.

بنابراین

  • useEffect به ازای هر بروزرسانی (update) یک بار اجرا می شود بنابراین می توانیم از آن به جای componentDidUpdate استفاده کنیم! شما می توانید با آن دقیقا مانند componentDidUpdate رفتار کنید (مثلا درون آن درخواست HTTP ارسال کنید و ....).
  • useEffect در هنگام ساخته شدن یک کامپوننت نیز اجرا می شود (اگر مرورگر را reload کنید و هیچ تغییری در آن ندهید باز هم useEffect را در console می بینید. ر.ک به تصاویر بالا). گفته بودیم که useEffect برای هر چرخه render اجرا می شود که قاعدتا شامل اولین چرخه (ساخت کامپوننت) هم خواهد بود بنابراین از این جهت شبیه به componentDidMount و به شکل کلی ترکیبی از componentDidMount و componentDidUpdate است.

برخی از lifecycle hook ها مانند getDerivedStateFromProps معادلی در کامپوننت های کاربردی ندارند اما مشکلی نیست چرا که اصلا به آن ها نیازی نداریم. چرا؟ به کدهای فایل cockpit.js نگاه کنید:

const cockpit = (props) => {

    useEffect(() => {
        console.log('[Cockpit.js] useEffect');
    })
// بقیه کدها ...

ما در اینجا به props دسترسی داریم و اگر بخواهیم state را بر اساس آن تنظیم کنیم می توانیم از useState استفاده کنیم؛ داده مورد نظر را از props گرفته و به useState بدهیم تا به عنوان state اولیه باشد.

امیدوارم این قسمت به درک کلی شما از تفاوت های موجود در کامپوننت های کلاس-محور و کاربردی کمک کرده باشید. در قسمت های بعدی با useEffect به صورت پیشرفته تری کار خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.