سلام. به قسمت چهارم از آموزش ریکت خوش اومدید. توی این قسمت با هم state ها و متد های lifecycle رو توی کلاس کامپوننت ها بررسی میکنیم. اگر قسمت های قبلی رو نخوندید، پیشنهاد میکنم حتما بخونید چون یه سری چیزاشون به هم دیگه مربوط هستن.
توی قسمت دوم که در مورد رندرینگ المنت ها توی ریکت توضیحاتی دادم، یه مثال زدم واستون که مثالی از ساعت بود. این مثال:
حالا میخوایم این مثال رو تبدیل کنیم به یه کامپوننت واقعی که قابل استفاده مجدد باشه و بتونیم n جا ازش استفاده کنیم. ازونجایی که فعلا به متد های لایف سایکل و استیت ها توی فانکشن کامپوننت ها نمیپردازم چون طبق داکیومنت فعلی ریکت دارم پیش میرم و به اون قسمت ها هم میرسیم، من بهتون پیشنهاد میکنم برای تمرین هم که شده، این فانکشن رو تبدیلش کنید به یه کلاس کامپوننت که طرز ساختش رو توی قسمت قبل توضیح دادم. اصلا نگران این نباشید که ساعت کار نمیکنه و به فکر راه انداختن ساعت نباشید. شما فقط این فانکشن رو به یه کلاس کامپوننت تبدیلش کنید که یو آی داشته باشه و کد های منطقی ای نداشته باشه.
خب. موفق بودید؟ کد این کامپوننت رو میتونید ببینید:
این کلاس کامپوننت، غیر قابل تغییر هست. یعنی وقتی به دام افزوده شد، تغییر نمیکنه و یه ساعت ثابت شما میبینید. ما این رو نمیخوایم. ما میخوایم هر ثانیه، کامپوننتمون آپدیت باشه. برای این کار، شما باید از چیزی به اسم استیت استفاده کنید. استیت، آبجکتی از چیز هایی هستند که ما میخوایم با تغییر کردن اون ها، کامپوننتمون آپدیت بشه. توی این کامپوننت، ما میخوایم ساعت رو آپدیت کنیم. پس ساعتمون رو باید بزاریم توی استیت.
برای استفاده از استیت شما باید متد کانستراکتور که یه آرگومان به نام پراپس داره به بدنه کلاستون اضافه کنید. توی بدنه متد کانستراکتور باید از متد سوپر استفاده کنید و آرگومان متد کانستراکتور رو بهش پاس بدید. بعدش باید با استفاده از this.state یه آبجکت که مقادیر مورد نیازتون که در صورت تغییر اون ها، کامپوننت آپدیت میشه رو بهش انتساب بدید. در اینجا چون ما میخوایم ساعت رو توی استیت ذخیره کنیم، دیگه نیازی نیست که توی جی اس ایکس یه اینستنس دیگه از کلاس Date بسازیم. میایم از مقدار ذخیره شده توی استیتمون استفاده میکنیم. چون اون مقدار هر بار آپدیت میشه. برای دسترسی به یه مقدار توی استیت هم باید از کلیدواژه this و سپس state و پراپرتی مورد نظر استفاده کنید. به این شکل:
خب ما الان قدم اول ساخت ساعتمون رو برداشتیم. قدم دوم چی هست؟ تغییر استیت!
ما میخوایم وقتی ساعتمون به دام افزوده شد، متد setInterval رو اجرا کنیم و بعد هر یک ثانیه مقدار استیتمون رو آپدیت کنیم. کار آپدیت استیتمون رو میسپریم به یه متد به نام tick. پس بعد از هر یک ثانیه، متد tick باید فراخونی شه. حالا چجوری کاری کنیم که وقتی کامپوننت لود شد، متد ست اینتروال فراخوانی بشه؟ با استفاده از متد های لایف سایکل!
توی ریکت، یه سری متد ها هستن(چه توی کلاس کامپوننت چه توی فانکشن کامپوننت) که مربوط به چرخه زندگی کامپوننت هستن. مثلا ما میخوایم وقتی کامپوننت واسه اولین بار به دام افزوده شد، یه سری کارا انجام بدیم. فقط واسه واسه اولین بار که کامپوننت به دام افزوده شد! برای اینکار متد وجود داره. یا مثلا ما میخوایم بعد از هر بار آپدیت شدن کامپوننتمون، یه کاری انجام بدیم. برای این هم متدی وجود داره. یا ما میخوایم اجازه آپدیت شدن یه کامپوننت رو صادر کنیم. برای اینکار هم متد وجود داره!! به این متد ها که مربوط چرخه عمر کامپوننت هستن، میگن متد های لایف سایکل. توی این مقاله با دو تا از متد های لایف سایکل آشنا میشید.
دیدید که ساعت ما کار نمیکرد. حالا میخوایم با استفاده از متد های لایف سایکل ساعتمون رو راه بندازیم. میخوایم وقتی کامپوننتمون برای اولین بار به دام افزوده شد، ساعت شروع به کار کنه. به این کار میگن mounting. برای اینکار، ما باید از متد componentDidMount استفاده کنیم. اصلا هم چیز عجیب غریبی نیست کار با این متد. خب چجوری از این متد استفاده کنیم؟ توی بدنه کلاس کامپوننتمون این متد رو اضافه میکنیم و توی بدنه این متد، ساعتمون رو با متد setInterval راه میندازیم که هر یک ثانیه، متد tick رو فراخوانی میکنه و متد tick هم استیتمونو آپدیت میکنه. به این شکل:
خب. اول با متد tick شروع میکنیم. این متد، همونطور که گفتم، استیت ما رو آپدیت میکنه. شما برای آپدیت کردن استیت باید از متد this.setState استفاده کنید. این متد دو تا آرگومان دریافت میکنه که ما فقط با آرگومان اولش کار داریم در اینجا. برای آپدیت کردن پراپرتی Date توی آبجکتی که به استیتمون دادیم، باید یه آبجکت به عنوان آرگومان اول به متد this.setState بدیم. توی این آبجکت، اسم پراپرتی ای که میخوایم توی استیت آپدیت بشه رو مینویسیم و مقدار جدید رو بهش انتساب میدیم که در اینجا پراپرتی Date رو ما میخوایم آپدیت کنیم. با انجام دادن اینکار، ما استیتمون رو آپدیت میکنیم و آپدیت شدن استیت باعث آپدیت شدن کامپوننتمون میشه.
یه نکته که نیازه بهش توجه کنید اینه که توی آبجکتی که به this.setState میدید، فقط پراپرتی هایی که میخواین آپدیت کنین رو بنویسین و مقدار جدید رو بهشون انتساب بدین. مثلا اگر استیت کامپوننتتون سه تا پراپرتی داشت به نام های name، age و city و شما میخواستید فقط name و age رو آپدیت کنید، فقط این دو تا پراپرتی ای که میخواین آپدیت کنین رو توی آبچکتی که به عنوان آرگومان اول this.setState میدید بنویسید و مقدار جدید بهش انتساب بدید. نیازی نیست city رو بنویسید. چون ریکت خودش مقادیر آپدیت شده رو با استیت شما ترکیب میکنه و پراپرتی city آبجکت توی استیتتون از بین نمیره. پس نگران این موضوع نباشید.
خب. ما الان منطق آپدیت شدن ساعتمون رو هم نوشتیم. بریم سراغ منطق شروع به کار کردن ساعتمون.
گفتیم ما میخوایم وقتی کامپوننت ساعتمون به دام افزوده شد، ست اینتروال شروع به کار کنه. پس باید چیکار کنیم؟ باید از متد componentDidMount استفاده کنیم. همونطور که قبل تر گفتم، این متد به محض اینکه کامپوننت به دام افزوده شد، اجرا میشه. خب ما توی componentDidMount توی کد بالا، ست اینتروال رو استارت زدیم. متد ست اینتروال یه آی دی مختص به خودش رو برمیگردونه که ما میتونیم از اون بعدا برای متوقف کردن ست اینتروال استفاده کنیم. به همین دلیل اون رو توی timerID توی کلاسمون ذخیره میکنیم. ازونجایی که this.state معنی خاصی داره و this.props توسط خود ریکت به کلاس افزوده شده، ما میتونیم بدون نگرانی از بین رفتن داده هامون، توی کلاسمون داده هامونو ذخیره کنیم. مثل this.timerID.
خب. الان اگه این کامپوننت رو به دام اضافه کنید، میبینید که ساعت شما به درستی کار میکنه :)
ولی یه چیز دیگه مونده. ممکنه ما بخوایم کامپوننتمون رو بعد از اینکه مثلا کاربر روی یه دکمه کلیک کرد، از روی دام حذف کنیم. آیا این درسته که ما کامپوننت رو حذف کنیم ولی ست اینتروال همینجوری واسه خودش کار کنه؟ یا اصلا فرض کنید با کلیک کردن روی اون دکمه، اگه ساعت روی دام بود اون ساعت حذف میشه. اگه ساعت روی دام نبود، اون ساعت اضافه میشه به دام. خب مثلا الان کاربر 20 بار روی دکمه کلیک کنه. 10 بار متد ست اینتروال اجرا میشه!!! حتی وقتی کامپوننت از دام حذف شد، اون ست اینتروالا حذف نمیشن! خب این اصلا برای سرعت بخشیدن به اپمون خوب نیست. راه حل چیه؟ استفاده از متد لایف سایکل componentWillUnmount.
این متد چیکار میکنه؟ این متد قبل از اینکه ریکت، کامپوننت مورد نظر رو از دام حذف کنه، ریکت اول این متد رو اجرا میکنه و بعدش کامپوننت رو از دام حذف میکنه. ما دقیقا به این متد نیاز داریم. ما میخوایم وقتی کامپوننت میخواد از دام حذف بشه، اون ست اینتروال رو حذف کنیم. پس باید به این شکل عمل کنیم:
در اینجا ما توی متد componentWillUnmount با استفاده از متد clearInterval ست اینتروال ساعتمون با آی دی ای که ازش ذخیره کردیم رو پاک کردیم. الان ما یه ساعت کاملا استاندارد و با پرفورمنس بالا و قابل استفاده داریم. و میتونید هر چقدر که دوست داشتید ازین ساعت جا های مختلف برنامه تون استفاده کنید بدون نگرانی از اینکه اگه یکیشون از دام حذف شه، ست اینتروالش باقی میمونه.
دو تا چیز هست که شما باید توی ریکت راجع به استیت توجه داشته باشید:
توی ریکت، شما به هیچ عنوان نباید مقادیر استیت رو مستقیما تغییر بدید. همیشه برای تغییر دادن استیت باید از متد this.setState توی کلاس کامپوننتا استفاده کنید. چون تغییر دادن مستقیم استیت، باعث رفرش شدن کامپوننت شما نمیشه. برای مثال:
اگر راجع به مفهوم همگام و ناهمگامی نمیدونید، پیشنهاد میکنم حتما به این مقاله که من نوشتم سر بزنید. چون اگه مفوم ناهمگامی رو ندونید این قسمت رو خیلی سخت متوجه میشید. توی اون مقاله خیلی کامل و با دلیل واستون توضیح دادم این موضوع رو.
خب. اینجا واستون توضیح میدم این موضوع رو. اول با اسینکرونس بودن آپدیت شدن استیت ها شروع میکنم. ببینید، متد های ست استیت توی کلاس کامپوننت ها جمع آوری میشن و سپس همشون به یه آپدیت برای کامپوننت تبدیل میشن. شاید بپرسید خب چرا؟ چون این به پرفورمنس برنامه مون کمک زیادی میکنه. خب مشکل از کجا شروع میشه؟ از اینجا که هر ست استیت فقط و فقط به مقدار حال حاضر کامپوننت دسترسی داره. یعنی اینجوری نیست که با دستور ست استیت اول که استیتو آپدیت کردیم، استیت آپدیت شده برای بقیه ست استیت ها هم در دسترس باشه! بزارید با یه مثال واستون روشن کنم موضوع رو.
ما یه کامپوننت شمارنده داریم. این کامپوننت میخواد با هر بار کلیک کاربر روی دکمه +، با استفاده از 3 بار فراخوانی کردن ست استیت و افزایش عدد یک به مقدار حال حاضر استیت، استیت شمارنده رو 3 تا افزایش بده. به این شکل:
ولی این کد در کمال تعجب مشکل داره و اگه کاربر روی دکمه + کلیک کنه، فقط یه دونه به مقدار شمارنده توی استیت اضافه میشه! چرا این اتفاق میوفته؟ چون همونطور که گفتم ست استیت ها جمع آوری میشن و در آخر تبدیل به یک آپدیت واسه کامپوننت میشن به جای سه تا آپدیت و هر ست استیت به مقدار استیت زمان شروع عملیات توی کامپوننتش دسترسی داره نه به مقدار آپدیت شده توسط ست استیت قبلی! به ترتیب واستون توضیح میدم.
اینجا، وقتی متد increment صدا زده شد، ست استیت ها اول جمع آوری میشن. بعدش ست استیت اولی انجام میشه. شمارنده یکی افزایش پیدا میکنه. ست استیت دوم ازونجایی که به مقدار استیت در زمان فراخوانی increment که 0 هست فقط دسترسی داره نه به مقدار به روز شده که 1 هست، میاد و به 0 عدد 1 رو اضافه میکنه. ست استیت سوم هم همینطور. در آخر این آبجکتا با هم ترکیب میشن(قبل تر اشاره کردم) و یکیشون اعمال میشه چون همشون مثل همن. بعدش که مقدار استیت اصلی آپدیت شد، ریکت متد رندر کامپوننتمون رو صدا میزنه! الان فکر میکنم متوجه شده باشید که چرا این اتفاق میوفته. خب. راه حل چی هست؟ چجوری میتونیم به استیت آپدیت شده دست پیدا کنیم توی هر ست استیت که بتونیم واقعا شمارنده توی استیت رو 3 تا افزایش بدیم؟ با استفاده از نوع دوم متد ست استیت!
نوع دوم متد ست استیت اصلا چیز عجیب غریبی نیست. فقط کافیه یه کالبک فانکشن با یه آرگومان به متد ست استیت پاس بدیم و مقداری که میخوایم آپدیت کنیم رو ریترن کنیم از اون کالبک. و به جای اینکه از استیت اصلی خود کامپوننتمون استفاده کنیم، از اون آرگومان کالبک استفاده میکنیم چون مقدار به روز استیت رو نشونمون میده. حالا به راحتی میتونیم با استفاده از سه بار فراخوانی ست استیت، به مقدار شمارنده مون سه تا اضافه کنیم. البته میتونیم اولین ست استیت رو به صورت معمولی فراخوانی کنیم چون در هر صورت به مقدار مورد نیازش از استیت خود کامپوننت دسترسی داره. به این شکل میشه کار های بالا رو انجام داد:
الان به شمارنده استیت اصلی ما 3 تا اضافه میشه. چون آرگومان اول کالبک متد ست استیت، واسه ما استیت به روز شده رو میاره که همونطور که گفتم میشه ست استیت اول رو به شکل معمولی هم نوشت چون در هر صورت مقدار شمارنده استیت جدیدش همون مقداریه که موقع استفاده از کامپوننت بوده. ولی ست استیت های بعدی رو باید حتما از نوع دوم استفاده کرد به دلیلی که بالاتر توضیح دادم. در اینجا شما میتونید با استفاده از ابزار بریک پوینت بروزر کروم، مشاهده کنید که اول همه ست استیت ها جمع آوری میشن و سپس کالبک هاشون صدا زده میشن.
خب. الان به احتمال زیاد منظور از اسینکرونس بودن استیت ها رو درک کردید توی ریکت. بریم سراغ درک اسینکرونس بودن آپدیت پراپس توی ریکت.
اگر قسمت های قبلی این سری رو خونده باشید، میدونید که پراپس توی کامپوننت های ریکتی غیر قابل تغییر هستن. پس منظور از اسینکرونوس بودن پراپس چی هست؟
ما میدونیم که هر نوع پراپی میتونیم به کامپوننتای فرزندمون بدیم. اون پراپ میخواد نوعش فانکشن باشه، آبجکت باشه، استرینگ باشه، هر چیزی باشه.
فرض کنید ما میخوایم عددی که به مقدار counter توی استیت اضافه میشه رو از کاربر بگیریم. و میخوایم بعد از اضافه شدن اولین مقدار به شمارنده استیتمون، یه دونه به عدد وارد شده توسط کاربر اضافه بشه. اینپوتمون هم یه کامپوننت جدا داره و مقدار اینپوت توی استیتش ذخیره شده. باید چیکار کنیم اینجا؟ باید مقدار استیت رو از طریق پراپ به کامپوننت ارسال کنیم و بعد متد ست استیت رو هم از طریق پراپ ارسال کنیم که بتونیم مقدار استیت کامپوننت پدر رو عوض کنیم. تا اینجا همه چی خوب هست.
ولی وقتی که این کد رو اجرا میکنیم بار اول که روی + کلیک میکنیم متوجه میشید که 3 تا به شمارنده استیت اضافه شده به جای 5 تا! ولی به چه دلیل؟ چون ست استیت به پراپس حال حاضر خودش فقط دسترسی داره نه به پراپس جدیدش! راه حل چی هست؟ یادتونه گفتم متد ست استیت نوع دومی داره که کالبک فانکشن میگیره و آرگومان اول اون کالبک فانکشن، مربوط به استیت به روز شده هست؟ باید بگم که این کالبک آرگومان دوم هم داره. آرگومان دومش مخصوص پراپس به روز شده هست. ما میتونیم کد بالا رو به کد پایین تبدیل کنیم:
خب. اینجا ما فکر میکنیم همه چی درست هست تا زمانی که برنامه رو تست میکنیم. اینجاست که میفهمیم به جای اینکه مقدار شمارنده توی استیتمون 5 تا بره جلو، 6 تا میره جلو. دلیلش چی هست؟ دلیلش این هست که وقتی همه ست استیت ها جمع آوری شدن، چون یه ست استیت مال یه کامپوننت دیگه هست و هیچ ربطی به کامپوننت counter نداره، ریکت میاد و اول استیت توی کامپوننت پدر رو آپدیت میکنه و باعث میشه کامپوننت پدر ری رندر بشه. بعدش میره و ست استیتای کامپوننت Counter رو تبدیل به یه آپدیت میکنه و استیت اصلی رو آپدیت میکنه. این باعث میشه که وقتی استیت کامپوننت پدر به روز شد، توی پراپس جدید همه ست استیتای مربوط به کامپوننت counter مقدار amount برابر با 2 بشه و سه تا دو به استیت اضافه بشه و در نتیجه به استیت اصلی 6 تا اضافه بشه. پس باید چیکار کنیم؟ از همون نکته ای که گفتم همه ست استیتا به پراپس حال حاضر خودشون توی زمان شروع عملیات دسترسی دارن باید استفاده کنیم. چون کار ست استیتای مربوط به کامپوننت خودمون که تموم نشده. فقط مقدار استیت کامپوننت پدر به روز شده و باعث نمیشه که مقدار پراپسی که ست استیتای خودمون داشتن تغییر کنه چون در زمان شروع، پراپ amount ما 1 بوده.
پس خیلی راحت میایم و به پراپ فعلی خودمون اشاره میکنیم. به این شکل:
الان دیگه اگه کاربر بار اول روی + کلیک کرد، 5 تا به شمارنده مون اضافه میشه. بارای بعدی 6 تا. چون بعد از بار اول استیت کامپوننت پدر تغییر میکنه دیگه.
خب این هم از این. امیدوارم اینجا رو متوجه شده باشید. سعی کردم خیلی ساده و با مثال توضیح بدم واستون.
توی ریکت یه مفهومی هست که بهش میگن (Data down Action up). میخوایم این مفهوم رو با هم بررسی کنیم. اول با Data down شروع میکنم. ببینید، ما توی ریکت، فقط به کامپوننت های فرزند میتونیم اطلاعات رو بفرستیم. منظور از Data down این هست. ساختار دام رو یادتون هست؟ ساختارش یه حالت درختی ای از آبجکت ها هست که ریشه این درخت تگ html هست. توی ریکت هم یه همچین ساختاری داریم منتهی از نوع کامپوننتی. اگه یادتون باشه توی مقاله های قبلی گفتم که توی اپ های ریکتی، معمولا ریکت دولوپر ها میان و تمام ساختارشون رو میزارن توی یه کامپوننت اصلی به اسم App یا هرچیز دیگه. این کامپوننت نقش پدر برای کامپوننتای بدنه ش داره. دقیقا مثل تگ html. حالا کامپوننت App، میتونه به همه فرزنداش دیتا بفرسته از طریق پراپس. کامپوننت های فرزند میتونن همون دیتا ها رو بفرستن برای کامپوننتای فرزندشون. دوباره اون کامپوننتا هم همینکار رو میتونن بکنن. به این درخت میگن UI tree. و این شد از مفهوم Data down و یه سری توضیحات دیگه.
حالا Action up چیه؟ توی ریکت، کامپوننتا، به هم دیگه دسترسی ندارن. و این یعنی کامپوننتا نمیتونن متد های کامپوننتای دیگه رو خودشون اجرا کنن. اینجا مفهوم action up میاد وسط. وقتی ما فانکشنی رو از طریق پراپس میفرستیم به یه کامپوننت زیر مجموعه، اگر اون رو اجراش کنیم، اون فانکشن باید از کامپوننتایی که ازش از طریق پراپس اومده، بره بالا تا برسه به اون کامپوننتی که توش تعریف شده. اونجا اجرا میشه و اگر استیتی چیزی تغییر کرد یا نکرد هم خودش هم اون مقدار دوباره از طریق پراپس فرستاده میشن پایین. برای مثال:
قبل تر که داشتم مفهوم اسینکرونس بودن آپدیت شدن پراپس و ست استیت ها رو توضیح میدادم، اگر یادتون باشه من از کامپوننت App، ست استیت و مقدار counter رو فرستادم به کامپوننت فرزندش که Counter بود. توی متد increment اون رو صداش زدم. اینجا چه اتفاقی میوفته؟ دقیقا همین بحث data down action up میاد وسط. دیتا ها که یکی فانکشنمون بود و یکی مقدار شمارنده مون بود فرستاده میشن به کامپوننت فرزند و بعدش با صدا زدن متد increment، متد ست استیت فرستاده میشه به کامپوننت App. اونجا اجرا میشه و بعدش که استیت تغییر پیدا کرد دیتا ها که خود ست استیت و مقدار شمارنده هست، دوباره فرستاده میشن پایین.
نکته: کامپوننت ها همشون از هم دیگه جدا هستن و به هم دیگه بستگی ندارن! یعنی مثلا شما شما کامپوننت ساعتی که توی این قسمت ساختیم رو هر چند بار که خواستید میتونید ازشون استفاده کنید و به بدنه کامپوننت پدرشون وصل کنید. اصلا این ها بهم دیگه مربوط نیستن و کارشون جدا از هم هست.
خب. این هم از این قسمت. امیدوارم خوشتون اومده باشه از این قسمت و کلی چیز یاد گرفته باشید.
اگه دوست داشتید لایک کنید مقاله مو. اگه دوست داشتید دنبال کنید من رو. اگه دوست داشتید یه کامنت مثبت و یا انتقاد منطقی بزارید واسم. خلاصه هرکاری دوست داشتید توی زندگیتون انجام بدید؛ هرکاری که به کسی آسیب نزنه و یا غمگینش نکنه :)