چگونه از هوک‌های ری‌اکت استفاده کنیم؟ - آموزش کامل برای تازه‌کاران

هوک‌های ری‌اکت
هوک‌های ری‌اکت

سلام به همه!، هوک‌ها یکی از ویژگی‌های اصلی کد ری‌اکت مدرن، و همچنین یکی از مفاهیم پایه‌ای هستند که موقع یادگیری این لایبرری باید باهاش راحت باشید.

در این مقاله قصد دارم برخی از مفیدترین هوک‌هایی را که ری‌اکت در اختیار ما می‌گذارد، نحوه کار آن‌ها و مثال‌هایی از موقعیت‌هایی که می‌توانیم از آن‌ها استفاده کنیم را توضیح دهم.

امیدوارم که از خواندن این مقاله لذت ببرید. این مقاله ترجمه‌ای از جدیدترین مقاله‌ی freecodecamp هست و امیدوارم که ترجمه‌ی خوبی باشه و این مطلب براتون جا بیوفته.

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

چنانچه بعضی کدها در تصاویر ناخوانا هستند، آن‌ها را در Github Gist نیز گذاشته‌ام.

مقداری تاریخچه در مورد ری‌اکت و اینکه هوک‌ها برای چه هستند

همانطور که ممکن است بدانید، ری‌اکت یک لایبرری اوپن سورس است که جهت ساخت UI به روشی ساده‌تر و کارآمدتر از ابزارهای قبلی (مانند vanilla JS و jQuery) استفاده می‌شود. ری‌اکت توسط کمپانی متا (همان فیس‌بوک) توسعه یافت و در سال ۲۰۱۳ برای عموم منتشر شد.

هوک‌ها قابلیتی بودند که سال‌ها بعد در سال ۲۰۱۶ (ری‌اکت ورژن ۱۶.۸) معرفی شدند. فقط برای اینکه بدانیم هوک‌ها برای چه چیزی هستند و چرا نسبت به آنچه قبلا انجام شده بود بهبود یافته‌اند، نمونه‌ای از کدهای «پیش از هوک» را در مقابل برخی از کدهای «پس از هوک» ری‌اکت مدرن ببینیم.

در کد ری‌اکت قدیمی ما از کلاس کامپوننت‌ها استفاده می‌کردیم. اون‌ها یک متد render حاوی JSX داشتند که مسئول رندر کردن UI است.

و اگر می‌خواستیم این کامپوننت stateای را ذخیره کند، باید آن را در یک متد constructor تعریف می‌کردیم و با فراخوانی this.setState آن را تغییر می‌دادیم. در زیر یک مثال کوتاه برای شما جهت پیدا کردن ایده وجود دارد:

مثالی از کلاس کامپوننت
مثالی از کلاس کامپوننت

این خیلی مهم هست که اشاره کنیم که فانکشن کامپوننت‌ها (چیزی که امروزه استفاده می‌کنیم) در زمان ری‌اکت «پیش از هوک» نیز وجود داشتند. اما فقط می‌توانیم از آن‌ها برای کامپوننت‌های stateless استفاده کنیم - به این معنی که کامپوننت‌هایی که state ذخیره نمی‌کنند و مسئولیت هیچ منطق پیچیده‌ای به غیر از رندر کردن UI نیستند.

با ادغام هوک‌ها، اکنون می‌توانیم از فانکشن کامپوننت‌ها(و ترکیب ساده‌تر آن‌ها) همراه با همه عملکردهای پیچیده‌تر که کلاس کامپوننت‌ها به ما ارائه می‌دهند، استفاده کنیم.

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

در اینجا مثال دیگری میبینید که ما آنچه را که در کلاس کامپوننت خود داشتیم (مثال بالا) به یک فانکشنال کامپوننت تبدیل کردیم:

مثالی از فانکشنال کامپوننت
مثالی از فانکشنال کامپوننت

هوک‌های پرکاربرد در ری‌اکت

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

هوک useState

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

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

یک state درواقع objectای است که اطلاعات مربوط به یک کامپوننت خاص را در خود نگه میدارد. توابع جاوااسکریپت ساده توانایی ذخیره اطلاعات را ندارند. کد درون آن‌ها اجرا می شود و پس از پایان اجرا «ناپدید» می‌شود.

اما به لطف state، فانکشنال کامپوننت‌های ری‌اکت می‌توانند اطلاعات را حتی پس از اجرا ذخیره کنند. وقتی برای ذخیره یا «به خاطر سپردن» چیزی، یا به روشی متفاوت بسته به محیط، به یک کامپوننت نیاز داریم، state دقیقا همان چیزی‌ست که ما به آن نیاز داریم تا این کار را طوری که می‌خواهیم انجام دهد.

ذکر این نکته ضروری است که همه‌ی کامپوننت‌های اپلیکیشن ری‌اکتی نباید دارای state باشند. کامپوننت‌های stateless نیز وجود دارند که فقط محتوای خود را بدون نیاز به ذخیره اطلاعات رندر می‌کنند، و این خیلی خوب است.

نکته مهم دیگری که باید به آن اشاره کرد این است که تغییر state یکی از دو موردی است که باعث می‌شود یک کامپوننت ری‌اکتی دوباره رندر شود (دیگری تغییر در props است). به این ترتیب، حالت اطلاعات مربوط به کامپوننت را ذخیره می‌کند و همچنین رفتار آن را کنترل می‌کند.

برای پیاده‌سازی state در کامپوننت‌مان، ری‌اکت یک هوکی به نام useState در اختیار ما قرار می‌دهد. بیایید با مثال زیر ببینیم چگونه کار می‌کند.

مثالی از useState
مثالی از useState
  • ابتدا هوک را از ری‌اکت ایمپورت می‌کنیم: import { useState } from 'react'
  • سپس state را مقداردهی اولیه می‌کنیم: const [count, setCount] = useState(0)

در اینجا یک نام متغیر برای state (count) و یک نام تابع که هر بار که نیاز به به‌روزرسانی آن state داشته باشیم (setCount) استفاده خواهیم کرد. سپس مقدار اولیه state را (0) تنظیم می‌کنیم که به طور پیش فرض هر بار که برنامه شروع می‌شود، بارگذاری می شود.

  • در نهایت، همانطور که در بالا گفتیم، هر بار که می‌خواهیم state را به‌روز کنیم، باید از تابعی که تعریف کردیم استفاده کنیم: setCount. برای استفاده از آن، فقط باید آن را با پاس دادن state جدیدی که به عنوان پارامتر می‌خواهیم، ​​صدا کنیم. یعنی اگر بخواهیم 1 را به state قبلی اضافه کنیم، setCount(count+1) را صدا می زنیم.

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

ذکر این نکته ضروری است که تابع setState یک تابع asynchronous است. بنابراین اگر سعی کنیم state را بلافاصله پس از به‌روزرسانی آن بخوانیم، مانند زیر:

نمونه فانکشن setCount با کلیک بر روی دکمه
نمونه فانکشن setCount با کلیک بر روی دکمه

ما مقدار قبلی state را بدون به‌روزرسانی دریافت می‌کنیم.

روش صحیح خواندن state پس از به‌روزرسانی، استفاده از هوک useEffect است. این هوک به ما این امکان را می‌دهد که پس از هر ری‌رندر کامپوننت (به طور پیش فرض) یا بعد از هر متغیر خاصی که در آن تغییرات را اعلام می‌کنیم، یک تابع اجرا کنیم.

چیزی مثل این:

خواندن یک state پس از به‌روزرسانی با استفاده از useEffect
خواندن یک state پس از به‌روزرسانی با استفاده از useEffect

همچنین، این واقعیت که useState یک تابع asynchronous است، هنگام در نظر گرفتن تغییرات state بسیار مکرر و سریع، پیامدهایی دارد. این نکته‌ی بسیار مهمی است که همیشه باید در ذهن نگه داریم.

به عنوان مثال، موردی را در نظر بگیرید که یک کاربر دکمه ADD را چندین بار پشت سر هم فشار می‌دهد، یا حلقه‌ای که یک رویداد کلیک را چندین بار منتشر می‌کند.

با به‌روزرسانی stateای مانند setCount(count+1) ما این خطر را می‌پذیریم که با اجرای رویداد بعدی، count هنوز به‌روزرسانی نشده باشد.

برای مثال، فرض کنید در شروع count = 0است. سپس setCount(count+1) فراخوانی می شود و state به طور asynchronous به‌روز می‌شود.

اما دوباره setCount(count+1) قبل از تکمیل به‌روزرسانی state فراخوانی می‌شود. این بدان معنی‌ست که هنوز count = 0 است، یعنی که setCount دوم stateمان را به درستی به‌روز نمی‌کند.

یک رویکرد تدافعی‌تر این است که به setCount یک callback داده شود، مانند:

setCount(prevCount => prevCount+1)

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

اگر می‌خواهید نگاهی عمیق‌تر به روش‌های مدیریت state در ری‌اکت بیندازید، می‌توانید به این مقاله که چندی پیش نوشته شده نگاهی بیندازید. (به زودی ترجمه آن را خواهم گذاشت.)

هوک useEffect

به همراه useState، هوک useEffect احتمالاً هوکی است که بیشتر از همه در هنگام توسعه یک اپلیکیشن ری‌اکت استفاده می‌کنید. این دو مانند نان و کره برای توسعه‌دهنده ری‌اکت است.

هوک UseEffect به شما این امکان را می‌دهد که یک اثر جانبی (side effect) روی کامپوننت خود اجرا کنید. اثر جانبی اساساً به معنای چیزی است که پس از وقوع یک چیز خاص دیگر اتفاق می‌افتد.

یک مورد معمول استفاده واکشی داده‌ها (fetch data) پس از نصب (mount) کامپوننت است (در اینجا نیاز هست تا لایف سایکل‌ها در ری‌اکت را بلد باشید). فرض کنید ما تابعی به نام fetchData داریم که مسئول آن واکشی است - هوک useEffect ما ممکن است شبیه به این باشد:

صدا زدن تابع fetchData داخل هوک useEffect
صدا زدن تابع fetchData داخل هوک useEffect


ساختار این هوک بسیار ساده است. دو آرگومان را می‌پذیرد. ابتدا یک callback داریم که تابع ما را اجرا می‌کند و سپس یک آرایه به نام «آرایه وابستگی ها» داریم. اگر مانند مثال بالا آن را خالی بگذاریم، بلافاصله پس از رندر شدن کامپوننت، callback اجرا می‌شود.

حال فرض کنید می‌خواهیم اثر جانبی (side effect) ما پس از تغییرات متغیر اجرا شود. به دنبال مثال قبلی که برای هوک useState استفاده کردیم، برای اثر جانبی پس از تغییرات متغیر count، می‌توانیم useEffect را به این صورت تنظیم کنیم:

به‌روز شدن پیغام کنسول پس از تغییر متغیر Count توسط useEffect
به‌روز شدن پیغام کنسول پس از تغییر متغیر Count توسط useEffect

سومین و آخرین موردی که در مورد useEffect باید به آن اشاره کرد، امکان بازگشت یک تابع «پاکسازی» است. این تابع «پاکسازی» زمانی که کامپوننت جدا (unmount) شود، اجرا می‌شود. به دنبال مثال قبلی، افزودن یک تابع پاکسازی ممکن است به شکل زیر باشد:

بازگشت یک تابع پاکسازی در useEffect
بازگشت یک تابع پاکسازی در useEffect

توابع پاکسازی در useEffect معمولاً برای لغو اشتراک‌ها استفاده می‌شوند تا از تلاش ری‌اکت برای به‌روزرسانی state یک کامپوننت که قبلاً unmount شده است، جلوگیری شود.

برای اطلاعات بیشتر در مورد تابع پاکسازی در useEffect، می توانید به این مقاله و یا این مقاله مراجعه کنید.

هوک useContext

در سال ۲۰۱۶ React context API با نسخه 16.3 ری‌اکت منتشر شد. کاری که context در ری‌اکت انجام می‌دهد، ارائه راه حلی برای prop drilling است.

مفهوم prop drilling به موقعیتی اشاره دارد که در آن ما یک کامپوننت والد داریم که یک state را ذخیره می‌کند. و در زیر آن والد، ما سطوح بسیاری از کامپوننت فرزند داریم.

اگر بخواهیم آن state را در یک کامپوونت فرزند که عمیقاً در آن ساختار تودرتو (nested) است ارائه کنیم، راه حل این است که state را به عنوان prop در سراسر زنجیره کامپوننت منتقل کنیم.

تصویری از prop drilling
تصویری از prop drilling

این گزینه به خوبی کار می‌کند. اما مشکل این است که ما باید همان کد را در جاهای مختلف تکرار کنیم، که اگر بعداً نیاز به تغییر کد خود داشته باشیم (دیر یا زود مجبور خواهید بود این کار را انجام دهید) کار با آن بسیار خسته کننده است و مستعد ایجاد خطاها و اشکالات است.

حالا Context API این وضعیت را با ارائه «مکانی» برای ذخیره stateای که باید از بخش‌های مختلف اپلیکیشن‌مان و در امتداد سطوح مختلف درخت کامپوننت استفاده شود را حل می‌کند.

روش کار به این صورت است که کامپوننت context آن state داده شده را ذخیره می کند، و از هر کامپوننت داده شده می توانیم آن state را بخوانیم و به‌روز کنیم، مهم نیست که آن کامپوننت در کجا قرار دارد. ما همه propها را فراموش می‌کنیم. در عوض، ما فقط می‌توانیم مستقیماً با context کار کنیم و همه کامپوننت‌هایی که آن context state را می‌خوانند، وقتی وضعیت به‌روزرسانی می‌شود، دوباره رندر می‌شوند.

تصویری از همان موقعیت اما با استفاده از context
تصویری از همان موقعیت اما با استفاده از context

حال که پایه‌های نظری را فهمیدیم، بیایید ببینیم که چگونه هوک useContext به ما اجازه می دهد از این API استفاده کنیم. اجرای معمول چیزی شبیه به این خواهد بود. در یک پوشه به اسم "context" دو فایل خواهیم داشت. Context.js و ContextProvider.js.

در Context.js، ما فقط با استفاده از تابع createContext مقدار context API را مقداردهی اولیه می کنیم، که به عنوان آرگومان state اولیه‌ای را که می‌خواهیم ارائه کنیم را ارسال می‌کنیم. (در این مورد چیزی نمی خواهیم، ​​بنابراین می توانیم null را ارسال کنیم).

نمونه فایل context.js
نمونه فایل context.js

در ContextProvider.js، آن contextای را که در فایل قبلی مقداردهی اولیه کردیم، ایمپورت می‌کنیم. ما همچنین stateهایی را که می‌خواهیم بعدا استفاده کنیم، مقداردهی اولیه می‌کنیم و از کامپوننت اپلیکشن خود به‌روزرسانی می‌کنیم. در نهایت، context provider را برمی‌گردانیم و با آبجکت value، تمام stateها و توابع setState را که می‌خواهیم بعدا استفاده کنیم، ارسال می‌کنیم.

یک ContextProvider درواقع یک HOC است. یک کامپوننت مرتبه بالاتر (higher order component) یا HOC مشابه یک تابع مرتبه بالاتر در جاوا اسکریپت است (من یک مقاله در مورد آن در اینجا دارم).

توابع مرتبه بالاتر توابعی هستند که توابع دیگر را به عنوان آرگومان می گیرند یا توابع دیگر را برمی‌گرداند. React HOCها یک کامپوننت را به عنوان یک prop می‌گیرند و آن را تا حدی دستکاری می‌کنند بدون اینکه در واقع خود کامپوننت را تغییر دهند. شما می‌توانید به این مانند کامپوننت‌های wrapper فکر کنید.

ContextProviderنمونه فایل
ContextProviderنمونه فایل

در فایل App.js خود، تمام کامپوننت‌هایی را که می‌خواهیم بتوانیم با state تعامل داشته باشیم، با contextProvider خود می‌پوشانیم (wrap می‌کنیم). در این مورد ما می خواهیم که کل اپلیکیشن بتواند context را استفاده کرده و به‌روز کند، بنابراین همه آن را می‌پوشانیم.

فایل app.js که توسط contextProviderمان پوشیده شده است.
فایل app.js که توسط contextProviderمان پوشیده شده است.

در نهایت، از کامپوننتی که می‌خواهیم context state را بخوانیم/به‌روزرسانی کنیم، context و هوک useContext را ایمپورت کرده و stateها و توابع را به روش زیر تخریب (destructure) می‌کنیم (و فقط از state عادی و توابع setState استفاده کنید).

نمونه کامپوننت header که از useContext استفاده می‌کند.
نمونه کامپوننت header که از useContext استفاده می‌کند.

این boilerplate کمی بیشتر از پاس دادن همه چیز از روی props است، اما پس از راه اندازی بسیار قابل نگهداری‌تر، ساده‌تر و قابل درک‌تر است.

نکته جالب دیگری که باید به آن اشاره کرد این است که ما می‌توانیم contextهای مختلفی را در اپلیکیشن خود داشته باشیم. ما می‌توانیم آنها را به بخش‌های مختلف تفکیک کنیم. به عنوان مثال، فرض کنید ما یکی برای stateهای احراز هویت داریم، دیگری در رابطه با تنظیمات و پیکربندی کاربر، دیگری برای پرداخت‌ها یا هر چیز دیگری... و سپس می‌توانیم آن contextها را فقط در اطراف اجزای خاصی که نیاز به استفاده از آن‌ها دارند، بپوشانیم (wrap کنیم).

بنابراین اگر ما اطلاعات زیادی داریم که باید در اطراف اپلیکیشن خود به اشتراک گذاشته شود، داشتن contextهای مختلف بسیار روش ماژولارتر و مرتب‌تر برای نزدیک شدن به این موضوع خواهد بود.

هوک useReducer

هوک useReducer هوکی است که به ما این امکان را می‌دهد تا به صورت نیتیو reducers را در اپلیکیشن خود پیاده‌سازی کنیم تا stateهای پیچیده را مدیریت کنیم. اگر با Redux یا کتابخانه‌های مدیریت state مشابه آشنایی دارید، احتمالاً کلمه «reducer» زنگی آشنا برای شما به صدا در می‌آورد.

اساسا، reducerها نوعی تابع هستند که دو یا چند آرگومان را می‌گیرند، نوعی عمل را با آن‌ها انجام می‌دهند و یک نتیجه واحد را که از دو آرگومان به دست می‌آید، برمی گرداند.

یک reducer یک تابع خالص است که حالت قبلی و یک اکشن را به عنوان آرگومان می‌گیرد و حالت بعدی را برمی‌گرداند. reducer نامیده می‌شود زیرا همان نوع تابعی است که می‌توانید به آرایه ارسال کنید:

Array.prototype.reduce(reducer, initialValue)

اما قبل از اینکه به سراغ reducerها برویم، اگر از قبل هوک useState برای مدیریت state خود داریم، چرا به این نیاز داریم؟

خب، مشکلی که ممکن است در هنگام استفاده از useState پیش بیاید این است که state جدیدی که باید تنظیم شود به state قبلی بستگی دارد یا زمانی که تغییرات state اغلب در اپلیکیشن ما اتفاق می‌افتد.

در این مواقع، useState ممکن است برخی رفتارهای غیرمنتظره و غیرقابل پیش‌بینی را انجام دهد. بنابراین اینجاست که reducerها برای حل این مشکل وارد می‌شوند.

هوک useReducer هوکی است که ری‌اکت ارائه می‌کند تا ما بتوانیم reducerهایی را برای مدیریت state خود پیاده‌سازی کنیم. در اینجا یک مثال پیاده‌سازی است:

نمونه‌ای از مدیریت stateها با useReducer
نمونه‌ای از مدیریت stateها با useReducer
  • ما با ایمپورت کردن هوک از ری‌اکت شروع می‌کنیم: import { useReducer } from 'react'
  • سپس یک تابع reducer را تعریف می‌کنیم، که به عنوان پارامتر state فعلی و یک اکشن برای کار بر روی آن state می‌گیرد. در داخل آن، یک دستور switch خواهد داشت که نوع اکشن را می‌خواند، اکشن مربوطه را روی state اجرا می‌کند و state به‌روز شده را برمی‌گرداند.

استفاده از statements switch بر روی reducerها و حروف بزرگ برای اعلام اکشن‌ها رایج است.

نمونه‌ای از یک فانکشن reducer
نمونه‌ای از یک فانکشن reducer
  • پس از آن، زمان آن رسیده است که هوک useReducer خود را تعریف کنیم، که به نظر تقریباً شبیه به هوک useState است. ما یک مقدار را برای state خود اعلام می‌کنیم (در اینجا 'state')، تابعی که برای تغییر آن استفاده می کنیم ('dispatch') و سپس useReducer تابع reducer را به عنوان پارامتر اول و state پیش فرض را به عنوان پارامتر دوم می‌گیرد.
تعریف هوک useReducer
تعریف هوک useReducer
  • در نهایت، برای به‌روزرسانی state خود، مستقیماً reducer را صدا نمی‌زنیم، بلکه در عوض تابعی را که ایجاد کردیم ('dispatch') فراخوانی می‌کنیم. به آن نوع اکشن مربوطه را که می‌خواهیم اجرا کنیم، پاس می‌دهیم. در پشت صحنه، تابع dispatch به reducer متصل می‌شود و در واقع state را تغییر می‌دهد.
فراخوانی تابع dispatch
فراخوانی تابع dispatch

این نسبت به استفاده از useState کمی بیشتر boilerplate است، اما useReducer آنقدرها هم پیچیده نیست.

برای جمع بندی، فقط نیاز داریم:

  • یک reducer، این تابعی است که تمام تغییرات state ممکن را یکپارچه می‌کند.
  • یک تابع dispatch، که اکشن‌های اصلاحی را به reducer ارسال می‌کند.

چیزی که در اینجا وجود دارد این است که عناصر UI نمی‌توانند state را مستقیماً مانند قبل در هنگام فراخوانی setState با مقدار به‌روز کنند. اکنون آن‌ها باید یک نوع اکشن را فراخوانی کنند و از طریق reducer عبور کنند، که مدیریت state را ماژولارتر و قابل پیش‌بینی‌تر می‌کند.

همچنین، اگر با Redux و دیگر کتابخانه‌های مدیریت state آشنا هستید، احتمالاً متوجه شده‌اید که با Context API و هوک useReducer می‌توانیم به راحتی همان ویژگی‌هایی را که Redux ارائه می‌دهد پیاده‌سازی کنیم، اما به صورت نیتیو در ری‌اکت مدرن. بنابراین شخصاً، من فکر می‌کنم برای بیشتر موارد استفاده، شما نیازی به کتابخانه مدیریت state در کد ری‌اکت مدرن ندارید.

با این حال، داستان قبلاً متفاوت بود، و احتمالاً به همین دلیل است که بسیاری از کتابخانه‌های مدیریت state محبوب شده‌اند و بسیاری از توسعه‌دهندگان هنوز هم امروزه در حال استفاده از آن‌ها هستند.

هوک useRef

هوک useRef تابعی است که یک آبجکت ref قابل تغییر (mutable ref object) را برمی‌گرداند که ویژگی .current آن با آرگومان ارسال شده (initialValue) مقداردهی اولیه می‌شود. شیء برگشتی برای تمام طول عمر کامپوننت باقی می‌ماند.

دو کاربرد اصلی از useRef وجود دارد: پیگیری یک متغیر قابل تغییر (mutable variable) از طریق ری‌رندر، و دسترسی به گره‌های DOM یا عناصر ری‌اکت (DOM nodes or React elements).

ما می‌توانیم با استفاده از هوک useRef به روش زیر یک ref را تعریف کنیم:

const ref = useRef(initialValue)

سپس reference.current به مقدار مرجع دسترسی پیدا می‌کند و reference.current = newValue مقدار مرجع را به‌روز می‌کند. خیلی ساده.

دو نکته در مورد refها وجود دارد که باید به خاطر بسپارید:

  • مقدار مرجع بین رندرهای مجدد کامپوننت‌ها باقی می‌ماند.
  • به‌روز‌رسانی یک مرجع باعث ری‌رندر کامپوننت نمی‌شود.

برای مشاهده مثالی از این موضوع، بیایید حالتی را تصور کنیم که در آن باید تعداد کلیک‌های روی یک دکمه را بدون ری‌رندر یک کامپوننت بشماریم. ما می‌توانیم این کار را به صورت زیر انجام دهیم:

مثال استفاده از useRef
مثال استفاده از useRef

با به‌روزرسانی و ثبت ref، از ری‌رندر کامپوننت جلوگیری می‌کنیم و به هدف خود می‌رسیم. بنابراین، ۲ تفاوت اصلی بین refrence و state عبارتند از:

  • به‌روزرسانی یک مرجع (refrence) باعث ری‌رندر نمی‌شود، در حالی که به‌روزرسانی state باعث می‌شود کامپوننت دوباره رندر شود.
  • و همچنین... به‌روزرسانی مرجع (refrence) همزمان (synchronous) است (مقدار مرجع به روز شده فوراً در دسترس است)، در حالی که به روز رسانی state ناهمزمان (asynchronous) است (متغیر state پس از ری‌رندر به‌روز می‌شود).

برخی از هوک‌های کمتر رایج اما همچنان مفید

در اینجا من دو هوک را که برای memoization در ری‌اکت استفاده می‌شود، ارائه می‌کنم. اگر با memoization آشنایی ندارید، می‌توانید به این مقاله مراجعه کنید.

اساساً به خاطر سپردن (memoization) به این معنی است که کامپوننت‌ها را مجبور کنیم تا props و state را «به خاطر بسپارن»، بنابراین آن‌ها فقط وقتی که props/state تغییر کنند، ری‌رندر می‌شوند و از ری‌رندرهای غیرضروری جلوگیری می‌شود. این کار باعث افزایش پرفورمنس اپلیکیشن‌مان می‌شود.

هوک useCallback

هوک useCallback توابع callbackرا که به‌عنوان props دریافت می‌کند، به خاطر می‌سپارد، بنابراین در هربار ری‌رندر، دوباره ایجاد نمی‌شوند. استفاده صحیح از useCallback می‌تواند پرفورمنس اپلیکیشن‌مان را بهبود بخشد.

راهی که می‌توانیم آن را پیاده‌سازی کنیم این است که تابعی را که به‌عنوان props به یک کامپوننت فرزند در هوک useCallback بپوشانیم (wrap کنیم)، مانند این:

استفاده از هوک useCallback
استفاده از هوک useCallback

کاری که useCallback انجام می‌دهد این است که با وجود ری‌رندر شدن کامپوننت والد، مقدار تابع را نگه می‌دارد. این بدان معناست که تا زمانی که مقدار تابع نیز ثابت بماند، prop فرزند ثابت می‌ماند. و این مشکل ری‌رندر غیرضروری فرزند را حل می‌کند.

برای استفاده از آن، فقط باید هوک useCallback را دور تابعی که تعریف می‌کنیم بپوشانیم (wrap کنیم). useCallback همچنین دارای یک آرایه وابستگی مانند useEffect است. در آرایه موجود در هوک، می‌توانیم متغیرهایی را تعریف کنیم که وقتی آن متغیر تغییر می‌کند، باعث تغییر مقدار تابع می‌شود (دقیقاً به همان روشی که useEffect کار می‌کند).

نمونه‌ای از آرایه وابستگی هوک useCallback
نمونه‌ای از آرایه وابستگی هوک useCallback

هوک useMemo

هوک useMemo یک هوک بسیار شبیه به useCallback است. اما useMemo به جای کش کردن یک تابع، مقدار بازگشتی یک تابع را کش می‌کند.

در مثال زیر useMemo عدد 2 را کش می‌کند:

مثالی از useMemo
مثالی از useMemo

در حالی که useCallback کل تابع () => num + 1 را کش می‌کند.

کاستوم هوک‌های ری‌اکتی

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

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

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

نمونه‌ای از کاستوم هوک به اسم useWindowSize
نمونه‌ای از کاستوم هوک به اسم useWindowSize

بعد از ساختن کاستوم هوک به شکل بالا، ما می‌توانیم کاستوم هوک‌مان را در هر کامپوننتی که می‌خواهیم به شکل زیر فراخوانی کنیم:

طریقه استفاده از کاستوم هوک در یک کامپوننت
طریقه استفاده از کاستوم هوک در یک کامپوننت

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

اگر می‌خواهید در مورد کاستوم هوک‌ها بیشتر بدانید، در اینجا یک وب‌سایت اختصاص داده شده به این موضوع وجود دارد.

جمع‌بندی

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

حتماً جهت بهبود روند نگارش و ترجمه مقالات، نظرات‌تون رو برام بنویسید.

آدیوس
آدیوس