در جلسه قبل در رابطه با چرخه ویرایش (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 در کامپوننت های کاربردی چیست؟
اگر به کامپوننت cockpit.js نگاه کنید، حتما متوجه می شوید که این کامپوننت از نوع کاربردی است (درون یک تابع قرار دارد) بنابراین نمی توانیم از lifecycle ها در آن استفاده کنیم. راه حل معادل آن استفاده از یک hook به نام useEffect است که باید آن را با react وارد همین فایل cockpit.js کنیم:
import React, { useEffect } from 'react';
تا الان با دو عدد از مهم ترین hook های react آشنا شده ایم:
نکته: قبلا هم گفته بودیم که Lifecycle hook ها هیچ ربطی به react hook ها (مانند useState و useEffect و ...) ندارند. وجود کلمه hook در آن ها نباید شما را گیج کند.
useEffect یک آرگومان می گیرد که یک تابع است و برای هر چرخه render اجرا می شود. به طور مثال:
useEffect(() => { console.log('[Cockpit.js] useEffect'); })
این کد را درون بدنه cockpit.js اضافه کرده ایم. حالا اگر به مرورگر برویم می بینیم که با refresh شدن صفحه، کد log در قسمت console نمایش داده می شود:
از آنجایی که تابع درون useEffect برای هر چرخه render اجرا می شود، اگر روی دکمه Toggle Persons کلیک کنیم دوباره آن را خواهیم دید:
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 شدن بیهوده کامپوننت ها صحبت خواهیم کرد اما فعلا در همین حد کافی برای شروع است.
بنابراین
برخی از lifecycle hook ها مانند getDerivedStateFromProps معادلی در کامپوننت های کاربردی ندارند اما مشکلی نیست چرا که اصلا به آن ها نیازی نداریم. چرا؟ به کدهای فایل cockpit.js نگاه کنید:
const cockpit = (props) => { useEffect(() => { console.log('[Cockpit.js] useEffect'); }) // بقیه کدها ...
ما در اینجا به props دسترسی داریم و اگر بخواهیم state را بر اساس آن تنظیم کنیم می توانیم از useState استفاده کنیم؛ داده مورد نظر را از props گرفته و به useState بدهیم تا به عنوان state اولیه باشد.
امیدوارم این قسمت به درک کلی شما از تفاوت های موجود در کامپوننت های کلاس-محور و کاربردی کمک کرده باشید. در قسمت های بعدی با useEffect به صورت پیشرفته تری کار خواهیم کرد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.