یکی از لایبرری های محبوب جاوااسکریپت که توی ری اکت خیلی ازش استفاده میشه React Query هستش، این لایبرری ابزارای زیادی رو برای fetch کردن، cache کردن و update کردن دیتا از API در اختیار ما میزاره و به ما کمک میکنه با مدیریت صحیح و بهینه ی ریکوئست هامون اپلیکیشن های پر سرعت و روان و تر تمیزی داشته باشیم.
فواید کلیدی این لایبرری که هر برنامه نویسی رو وسوسه میکنه تا باهاش کار کنه:
توی این مقاله میریم سراغ قابلیت های کلیدی این لایبرری و بهتون نشون میدم که چطور میتونیم از این لایبرری استفاده کنیم، همچنین درباره server states و client states میخونیم و به این سوال که آیا React Query میتونه ابزار مفیدی برای global state manegement هم باشه پاسخ میدم.
npm install react-query
بعد از نصب پکیج باید به شکل زیر عمل کنید:
دقیقا مثل تصویر بالا، در فایل index اقدام به ایمپورت کردن QueryClientProvider و QueryClient میکنیم که یه queryClient میسازیم و به عنوان پراپ client پاس میدیمش به QueryClientProvider که پیچیدیمش دور کامپوننت App و حالا میتونیم راحت تو کامپوننت هامون از react query استفاده کنیم.
useQuery
این هوک از مهم ترین هوک هایی هست که React Query در اختیارتون میزاره و خیلی باهاش کار میکنید. از این هوک برای فچ کردن دیتا از سرور و کش کردن اون دیتا برای سری های بعدی استفاده میشه:
همونطور که توی مثال بالا میبینید این هوک یه آبجکت به ما برمیگردونه که ما با دی استراکچر کردن هر کدوم رو ازش میکشیم بیرون تا استفادشون کنیم.
پارامتر هایی که باید به این هوک بدیم اولی query key هستش که ازش برای اسم گذاری استفاده میکنیم. ینی چی؟ ینی بهش میگیم بیا به این اندپوینت فلان ریکوئست بزن و دیتاشو با این اسم( توی مثال با اسم todos) کش بکن که اگه بعدا خواستم دیتاشو اپدیت کنم و ازم اسم خواستی من به همین اسم اشاره بکنم که بتونی بشناسیش و آپدیتش کنی.
دومین پارامتری که باید بهش پاس بدیم یه فانکشن async هست که توش ریکوئست رو میزنیم و دیتارو برمیگردونیم.
حالا چیزایی که این هوگ برمیگردونه مهم تریناش همین ها هستند که دارین در تصویر بالا مشاهده میکنید.
پراپرتی data شامل دیتایی هست که از ریکوئستمون با موفقیت بهمون برگشته، پراپرتی isLoading یه مقدار boolean هستش که اگه ریکوئستمون هنوز دیتارو نداده و در حالت pending هستش این پراپرتی true خواهد بود و در غیر این صورت false، پراپرتی isError هم دقیقا به همین صورته که تا وقتی که ریکوئستمون در حالت انتظار یا موفقیت باشه مقدار این پراپرتی false خواهد بود و فقط در صورتی که ریکوئستمون ارور برگردونه این پراپرتی true میشه. همچنین یه پراپرتی Error هم داریم که ازش میتونیم برای نشون دادن مسیج ارور Error.message استفاده بکنیم که توی مثال بالا نیست.
نکته ی خفنش اینجاس که این data وقتی با موفقیت پر بشه و ما دیتارو داشته باشیم دفعات بعد این دیتا کش شده و وقتی کاربر دیتارو بخواد ببینه بدون لحظه ای لودینگ و درنگ میتونه دیتارو ببینه درحالی که React Query عزیز اون پشت داره ریکوئست تازه ای میزنه تا ببینه اگه دیتا عوض شده بود با این دیتای کش شده جایگزینش کنه و در غیر اینصورت دیتا تغییری نمیکنه.
به همین راحتی دارید دوتا از قابلیتای کلیدی این لایبرری رو در همین مثال اول میبینید: یک اینکه ما خیلی راحت ریکوئستمون رو مدیریت میکنیم و برای هر سناریویی از جمله وقتی ریکوئستمون ارور بده یا موفق باشه یا لودینگ باشه، دستمون پره که این دنباله ی قابلیت کلیدی بعدی این لایبرری هستش که باعث میشه ما دیگه برای ریکوئسمون استیت های اضافی برای لودینگ و ارور و try catch های بیخود و... ننویسیم و کد رو بهینه تر و تمیز تر میکنه.
useMutation
از این هوک وقتی استفاده میکنیم که میخوایم دیتارو تغییر بدیم مثلا وقتی که میخوایم یه ایتم به دیتا اضافه یا یه ایتم از دیتا حذف یا دیتای فعلی رو تغییر بدیم:
همونطور که توی مثال بالا میبینید این هوک هم یه آبجکت به ما برمیگردونه که ما با دی استراکچر کردن هر کدوم رو ازش میکشیم بیرون تا استفادشون کنیم.
پارامتر هایی که باید به این هوک بدیم اولی فانکشن async هستش که توش ریکوئستمون رو میزنیم و دیتارو برمیگردونیم اگه این فانکشنمون نیاز به پارامتر داشته باشه(که توی مثال بالا newTodo هستش) اون پارامتر رو موقع صدا کردن mutate بهش پاس میدیم.
پارامتر دومی که این هوک میگیری یه ابجکته که میتونیم توش با توجه به پراپرتی هایی که داره کارای مختلفی انجام بدیم، توی مثال بالا از پراپرتی onSuccess استفاده کردیم این متد وقتی که ریکوست موفقیت آمیز باشه صدا زده میشه و توی این متد میتونیم دیتای آپدیت شدمون رو دوباره Refetch یا invalidate کنیم. ینی چی؟ ینی به React Query بگیم دیتای من آپدیت شده پس دیتایی که برام کش کردی و دارم نشون میدم قدیمیه، لطفا برو و دوباره دیتای جدید رو فچ کن تا دیتای کش شده ام آپدیت بشه. اینجاس که اون query key بدرد میخوره. چون react query نمیدونه کدوم دیتارو باید اپدیت کنه و ما با پاس دادن این اسم به متد invalidateQueries میگیم که کدوم دیتارو باید اپدیت بکنه.
درباره ی ٖqueryClient که از متد invalidateQueries اش استفاده کردیم جلوتر بیشتر توضیح میدم.
حالا پراپرتی هایی که این هوک به ما برمیگردونه یکی فانکشن mutate هستش که ازش میتونیم برای آپدیت کردن دیتا و اجرای کل پروسه ای که الان توضیح دادم استفاده کنیم و پراپرتی بعدی isLoading هستش که تا وقتی که mutation هنوز انجام نشده( اپدیت صورت نگرفته و ریکوئست در حالت pending هست) این پراپرتی true خواهد بود تا ازش برای نشون دادن لودینگ استفاده کنیم.
پارامتر دوم هوک useMutation علاوه بر onSuccess متدای دیگه ای هم داره که یکیشون که خیلی کلیدی و جذابه onSettled
وoptimisticUpdate
هستش.
فرض کنید وقتی میخواید یه آیتم به دیتاتون اضافه کنید(بفرستید سمت سرور) شما مطمعنید که دیتا به سرور با موفقیت فرستاده میشه یا به هر دلیلی دوس ندارید وقتی ایتم رو ادد میکنید منتظر جواب سرور بمونید و لودینگ ببینید. پس میاید به محض زدن دکمه ی اضافه کردن آیتم،اون آیتم رو دستی به دیتای کش شدتون اضافه میکنید و کاربر درجا با سرعت نور و بدون هیچ معطلی و لودینگی دیتای جدید رو میبینه:
متد uptimisticUpate به محض اینکه ریکوئست زده بشه اجرا میشه و منتظر جواب نهایی ریکوئست نمیمونه، توی مثال بالا هم ما اومدیم به محض ریکوئست، با استفاده از متد setQueryData به React Query گفتیم که بیا و دیتایی که با اسم todos کش کردی رو اپدیت کن و این اسم todos رو به عنوان پارامتر اول دادیم و پارامتر دوم یه کال بک هست که دیتای کش شده ی فعلی رو به عنوان آرگیومنت میگیره و یه دیتای جدید برمیگردونه که آرایه ای شامل از دیتای کش شده ی فعلی به اضافه ی آیتم ادد شده ی جدید هستش. لازم به ذکره که ما ایدی هارو در این پروسه به صورت دستی و فی البداهه و موقت میدیم ولی وقتی ریکوست کامل شه دوباره دیتای کش شده تغییر میکنه و دیتایی که توی این متد اضافه کردیم پاک میشه تا دیتای اپدیت شده با ایدی های واقعی اضافه شن.
حالا مشکلی که وجود داره اینه که اگه به هر دلیلی سرور ارور بده و نتونیم دیتای کش شده رو اپدیت کنیم همچنان قراره دیتای الکی که توی uptimisticUpdate اضافه کردیم توی کش باقی بمونه در حالی که ما واقعا همچون دیتایی نداریم و به صورت موقت میخواستیم نشونش بدیم تا دیتای اصلی با ایدی واقعی برسه.
اینجاس که متد کلیدی بعدی به دادمون میرسه که بهش اشاره کردم onSettled، این متد بعد از اینکه سرور ریسپانس رو برگردوند بدون در نظر گرفتن ارور یا موفقیت صدا زده میشه و ما میتونیم توش بگیم که اگه سرور ارور برگردونده پس اون دیتای فیکی که توی uptimisticUpdate اضافه کردم رو حذف کن تا همون دیتای اصلی بمونه:
متد onSettled چند تا ارگیومنت داره که ما فقط به error و context نیاز داریم از ارور برای این استفاده میکنیم که چک کنیم اگه ترو بود و ارور برگردونده بود دیتای فیک رو پاک کنیم پس دوباره با همون متد setQueries میایم دیتای کش شده رو میگیریم و فیلترش میکنیم که بیاد فقط ایتمایی که ایدیش با ایدی آیتم فیک ما یکی نیست رو نگه داره که همون دیتای کش شده ی اصلی بود و دیتای کش شدمون رو اینطوری برمیگردونیم به حالت قبل.
همچنین توی ریکوئستمون داریم چک میکنیم که اگه response.ok فالس بود یه ارور برگردونه که باعث صدا زده شدن onSettled موقع ارور گرفتن بشه و همینطور توی uptimisticUpdate آیدی آیتم فیکمون رو برمیگردونیم که مقدارشو با عنوان context به عنوان آرگیومنت onSettled میگیریم و جهت فیلتر کردن دیتا استفادش میکنیم.
به کل این پروسه optimistic update گفته میشه که از قابلیت های کلیدی این لایبرری هستش.
useQueryClient
این بزرگوار رو که تو مثال های بالا استفاده کردیم برای دسترسی به query client استفاده میشه که مسئول مدیریت کش و تغییر دیتای کش شده و invalidate کردنشون هست.
مهم ترین متد هایی که این هوک بهمون میده :
setQueryData
: این متد دیتای کش شده ی query keyای که بهش میدیم رو آپدیت میکنه.getQueryData
: این متد دیتای کش شده ی query keyای که بهش میدیم رو بهمون میده.invalidateQueries
: این متد دیتای کش شده ی query keyای که بهش میدیم رو با Refetch یا همون ریکوئست مجدد از سرور آپدیت میکنه.ما وقتی از useQuery تو مثال اولمون برای گرفتن دیتا استفاده کردیم فقط درباره دو پارامتر اولش حرف زدیم که شامل query key و ریکوئستمون بود حالا یه پارامتر سوم هم داریم که باهاش میتونیم قابلیت های کش کردن و ریکوئست زدنش رو کانفیگ و مدیریت کنیم مهم ترین پراپرتی ها برای کانفیگ کردن این موارد هستند:
staleTime:
با این پراپرتی میگیم که دیتایی که کش کرده چقدر تو حالت stale بمونه قبل از اینکه Refetch بشه و این پراپرتی مقدار عدد رو به عنوان میلی ثانیه زمان میپذیره.cacheTime:
با این پراپرتی میگیم که دیتا چه مدت میتونه تو حالت کش بمونه و بعدش اتوماتیک از حافظه کش پاک میشه که بهمون کمک میکنه بتونیم سایز کشمون رو مدیریت کنیم و نزاریم زیادی بزرگ شه و این پراپرتی مقدار عدد رو به عنوان میلی ثانیه زمان میپذیره.refetchOnMount:
با این پراپرتی میگیم که کوئری باید موقع mount شدن کامپوننت دیتای خودش رو refetch بکنه یا نه و این پراپرتی مقدار boolean میپذیره.refetchOnWindowFocus:
با این پراپرتی میگیم که کوئری باید موقعی که صفحه فوکوس میشه دیتای خودش رو refetch بکنه یا نه که بهمون کمک میکنه وقتی کاربر چند تا صفحه از اپلیکیشنمون رو همزمان باز کرده با جابه جا شدن بین صفحات صفحات دیگه دیتاشون قدیمی نمونه و اتوماتیک اپدیت بشه تا کاربر نیازی به رفرش صفحه نداشته باشه و این پراپرتی مقدار boolean میپذیره.refetchInterval:
با این پراپرتی میگیم که کوئری باید هر چند ثانیه یبار ریکوئست بزنه و دیتارو اتوماتیک آپدیت بکنه و این پراپرتی مقدار عدد رو به عنوان میلی ثانیه زمان میپذیره.retry:
با این پراپرتی میگیم که کوئری باید موقعی که از سرور ارور میگیره چند بار دیگه مجدد ریکوئست بزنه و تلاش کنه که بهمون کمک میکنه ارور های سرور رو خیلی بهتر مدیریت کنیم و تا حد ممکن اپلیکیشنمون رو ریسپانسیو نگه داریم و این پراپرتی مقدار عدد به عنوان تعداد دفعات تلاش مجدد میپذیره.retryDelay:
با این پراپرتی میگیم که اگه داریم از پراپرتی retry استفاده میکنیم، کوئری هر چند ثانیه یبار باید تلاش مجدد رو انجام بده که بهمون کمک میکنه تا پدر سرور رو با تلاش های پشت هم در نیاریم و یه فاصله ای برای نفس کشیدن به سرور بدیم.staleWhileRevalidate:
با این پراپرتی میگیم که زمانی که داری دیتای کش شده رو به کاربر نشون میدی تو پشت صحنه دیتای جدید رو از سرور بگیر و دیتای کش شده رو باهاش اپدیت کن و این پراپرتی مقدار boolean میپذیره.با استفاده از این کانفیگ پراپرتی ها ما میتونیم استراتژی های مختلفی برای سیستم کش کردن اپلیکیشنمون داشته باشیم که من دو تا از استراتژی هارو اینجا توضیح میدم که شما میتونید یا از اینا استفاده کنید یا استراتژی خودتون رو بسته به نیاز پروژتون بسازید:
:
با این استراتژی ما به کاربر دیتای کش شده (stale) رو نمایش میدیم در حالی که تو پشت صحنه دیتای جدید رو میگیریم و باهاش دیتای کش شده رو آپدیت میکنیم. این استراتژی بهمون کمک میکنه که تعداد دفعاتی که کاربر منتظر دیتا میمونه و به لودینگ زل میزنه رو کاهش بدیم و وقتی دیتایی رو بخواد به صورت آنی میتونه دیتای آماده رو ببینه:
با این استراتژی ما به فچ کردن دیتای تازه از نتورک اولویت بالاتری نسبت به دیتای کش شده میدیم و توی شرایطی استفاده ازش مناسبه که بخوایم مطمعن شیم که دیتایی که یوزر میبینه همیشه تازه ی تازه اس حتی اگه به قیمت فدا کردن یکم پرفورمنس بشه:
توی این مثال با ست کردن cacheTime و staleTime رو یک دقیقه داریم میگیم که هر یک دقیقه دیتای کش رو بپاک و دیتای جدید رو Refetch کن و با ست کردن refetchOnMount به false داریم میگیم که حتی موقع mount شدن هم سر خود فچ نکن دیتارو و هر یه دیقه اینکارو انجام بده و دیتارو تازه کن و با ست کردن retry به ۳ بهش میفهمونیم که اگه به ارور خورد تا سه بار تلاش مجدد انجام بده.
نکته: اگه یک دیقه تموم بشه و دیتای جدید رو با سه بار تلاش نتونه از سرور بگیره میره کش رو نگاه میکنه و در صورتی که دیتایی موجود بود نمایش میده و در غیر اینصورت ارور نمایش میده.
اگه میخوایم جواب این سوال رو بدونیم بهتره اول یه مفهومی رو بلد باشیم:
استیت هایی که سمت کلاینت (مثلا مرورگر کاربر) مدیریت میشند و تغییرشون وابسته به عمل کاربر در خود اپلیکیشن هست و تغییرشون نیازی به ارسال ریکوئست نداره، Client State هستند. مثل: وضعیت لاگین کاربر، تمی که برای سایت انتخاب کرده، زبانی که برای سایت انتخاب کرده، دیتای داخل اینپوت های فرم که هنوز سابمیت نشدن.
استیت هایی که سمت سرور ( بک اند) مدیریت میشند و تغییر توی دیتای این استیت ها نیاز به ارسال نتورک ریکوئست داره، Server state هستند. مثل: لیست محصولاتی که برای خرید در یک فروشگاه در دسترس هستند، لیست مقاله هایی که در سایت ویرگول منتشر میشه، لیست آگهی های توی دیوار و...
حالا برگردیم به جواب سوال اصلیمون:
لایبرری react query به طور خاص و با هدف اصلی مدیریت ریکوئست ها و کش کردن دیتا به وجود اومده. اگر چه ما امکان استفاده ازش به عنوان global state management رو داریم ولی این لایبرری برای مدیریت ریکوئست ها و کش کردن دیتا ساخته شده و اگر چه با توجه به توضیحاتی که درباره استیت ها دادم بتونیم ازش برای مدیریت server states استفاده بکنیم اما نمیشه ازش استفاده مفیدی در client states کرد.
البته میتونیم از این لایبرری برای مدیریت server states استفاده کنیم و برای client states از ریداکس یا context و.. استفاده کنیم اما باعث پیچیدگی بیشتر میشه.
نظر شخصی خودم اینه که بهترین گزینه redux toolkit هستش که اگه مقاله ای که دربارش گذاشتم رو مطالعه کرده باشید(بخش redux query)، متوجه میشید که اکثر قابلیت هایی که react query برای مدیریت ریکوئست ها و کش کردن دیتا داره رو ریداکس تولکیت هم داره به علاوه اینکه همه ی استیت ها هم هندل میکنه.
ولی با اینحال با توجه به پروژه ای که دارید خودتون میتونید تصمیم بگیرید که دوس دارید از کدوم پکیج استفاده کنید و من فقط میخواستم با این لایبرری خفن که حرف زیادی برای گفتن داره آشناتون کنم. امیدوارم مفید واقع بوده باشه.
خدانگهدار و موفق باشی ?