React Query: Data Magic

این روزا اکثر اپلیکیشن‌های وب با سرویس‌‌های سمت سرور کار میکنن و کمتر اپلیکیشنی پیدا میکنید که بدون استفاده از دیتای بیرونی کار کنه. همین موضوع باعث شده که ابزارهای جدیدی برای مدیریت و تعامل با سرویس‌های بیرونی توسعه داده بشن که React-Query یکی از باحال ترین اونهاست. تو این مقاله قصد نداریم با React-Query آشنا شیم! قراره از آشنایی اولیه پا رو یکم فراتر بذاریم و با بعضی از ویژگی‌های خاصش سر و کله بزنیم!


مفاهیم پایه

(اگه میدونید React-Query چیه و با مفاهیم پایش هم آشنایید، میتونید این قسمت رو مطالعه نکنید.)

این کتابخونه همونطور که از اسمش پیداست، روی پروژه‌هایی که با React توسعه داده شدن قابل نصب و استفاده هست. React-Query با ارائه دو هوک useQuery و useMutation کلیه امور مربوط به state-management، کش کردن، تلاش مجدد و ... رو میتونه مدیریت کنه. برای پیاده‌سازیش هم فقط کافیه Provider مربوط بهش رو به عنوان یه HOC یا Higher Order Component به پروژتون اضافه کنید.
در نظر داشته باشید که React-Query نمیتونه جای fetch یا axios رو بگیره و از اونا استفاده میکنه. در واقع شما باید متدهایی که درخواست به سمت سرور ارسال میکنن رو بنویسید، اون ها رو به useMutation یا useQuery پاس بدید و بقیه عملیات رو به React-Query بسپرید.

یه مثال با هم ببینیم:

همونطور که میبینید، تو این مثال یه تابع برای درخواست به سرور به نام fetchTodoList نوشته شده و اون تابع به useQuery پاس داده شده و در نهایت چیزی که از useQuery گرفته شده، مقادیر isLoading، data و error هست. مقادیر دیگه ای هم میتونیم ازش بگیریم که توی این مثال آورده نشدن اما احتمالا الان براتون واضح شده که React-Query داره چقدر کار ما رو راحت میکنه!
فرض کنید میخواستید مقدار isLoading رو خودتون و با استفاده از useState مدیریت کنید، نیاز بود که state رو تعریف کنید، قبل از صدا زدن تابع مقدارش رو true و بعد از اتمام کار مقدارش رو false کنید، همچنین نیاز بود که با استفاده از هوک useEffect تابع fetchTodoList رو صدا بزنید. خلاصه بعد از مقداری دنگ و فنگ بالاخره میتونستید تازه به isLoading برسید!

کمی عمیق‌تر!

(اگه با query key و error handling در React-Query آشنا هستید، میتونید این قسمت رو هم مطالعه نکنید.)

اگه مثال بالا رو دقیق‌تر نگاه بکنید، میبینید که به useQuery قبل از پاس دادن تابع fetchTodoList یه مقدار از جنس رشته پاس داده شده. این مقدار رو بهش میگن query key. اما کارش چیه؟
همونطور که گفتم یکی از کارهایی که React-Query انجام میده مدیریت کش response هاست. با استفاده از query key، دیتاهای مختلفی که از طریق request های مختلف به دست میان از هم جدا سازی میشن و بعدا میشه با استفاده از همین key ها کارای جالبی انجام داد که باهاشون آشنا خواهیم شد.

شاید براتون این سوال پیش اومده باشه که React-Query چطوری متوجه Error میشه و مقدار براش ست میکنه؟
اگه به تابع fetchTodoList نگاه کنید، زمانی که مقدار response.ok درست نیست (true نیست)، یه Error ایجاد و throw شده. در نظر بگیرید که React-Query اصلا با این که سرور چه پاسخی داده کاری نداره و کد تابع شما باید به throw کردن یه Exception یا Error به React-Query بفهمونه که کار به درستی پیش نرفته. مقدار Error هم دقیقا همون چیزی هست که شما throw میکنید.

تفاوت useQuery و useMutation

همونطور که گفته شد، React Query با استفاده از دو هوک useQuery و useMutation کارش رو انجام میده. اما تفاوت این دو هوک چیه؟
اگه مستندات خود React-Query رو بخونیم، نوشته شده که برای فرآیند‌های Create، Update و Delete باید از useMutation استفاده بشه و برای Read از useQuery. به عبارت دیگه درخواست‌هایی با متدهای post، put، patch و delete رو باید از طریق useMutation انجام بدیم و get ها رو از طریق useQuery. اما اگه در پروژه‌های واقعی بخوایم کاربردهاشون رو اینطوری در نظر بگیریم به پیچیدگی هایی میخوریم. با جستجوی بیشتر و تجربه‌ای که من به دست آوردم و همچنین چیزی که سازنده React-Query گفته، تفاوت این دو هوک رو میشه اینطوری گفت:
هوک useQuery، الگوی اعلانی (Declarative) و هوک useMutation، الگوی دستوری (Imperative) دارد.
(اگه نمیدونید این دو الگو چی هستن، این مقاله از من رو میتونید مطالعه کنید.)
اما منظور چیه؟
هر جایی که نیاز داریم خودمون request بزنیم، تلاش مجدد بکنیم و ... بهتره از useMutation استفاده کنیم و هر جایی میخوایم این کارها رو کاملا به React-Query بسپریم، بهتره از useQuery استفاده کنیم.
برای مثل فرض کنید که شما نیاز دارید بعد از ثبت یه درخواست، نتیجه اون رو از طریق متد get دریافت کنید، اگه با منطق اول پیش بریم، برای این کار باید از useQuery استفاده کنیم که برای انجام این کار چون useQuery در همون ابتدا درخواست میزنه فقط با غیر فعال کردنش میشه این کار رو انجام داد اما با استفاده از useMutation میتونیم هر وقت که نیاز داریم درخواست مورد نظر رو ارسال کنیم.

مفاهیم پیشرفته‌تر

حالا که خیلی کلی با مفاهیم اولیه در React-Query آشنا شدیم، میخوایم یه نگاه حرفه‌ای تری بهش بکنیم و با ویژگی‌هایی کمی ناشناخته کار کنیم.

بروزرسانی زمان دار با refetchInterval

شرایطی رو در نظر بگیرید که نیاز دارید درخواستی رو هر یک ثانیه یک بار برای سرور ارسال کنید و داده ای رو دریافت کنید. به کمک متد refetchInterval که در useQuery وجود داره میتونید این کار رو به راحتی انجام بدید. به این صورت:

توی مثال بالا، شرطی گذاشته شده که مادامی که کد پاسخ سرور ۲۰۲ باشه، این درخواست با فاصله ۱۰۰۰ میلی ثانیه یا همون یک ثانیه تکرار بشه. در واقع مقداری که این تابع برمیگردونه فاصله هر بروزرسانی با بروزرسانی بعدی رو مشخص میکنه و اگه این مقدار false باشه بروزرسانی انجام نخواهد شد.

بروزرسانی با فوکوس روی صفحه یا refetchOnWindowFocus

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

مقدار queryClient چیزی هست که به provider مخصوص React-Query پاس میدیم.
همچنین میشه برای یه درخواست خاص هم این مقدار رو مشابه refetchInterval داخل کانفیگ useQuery ست کرد.

نگهداری داده‌های قبلی یا keepPreviousData

فرض کنید که داده‌ای دارید و میخواید اون رو refetch یا بروزرسانی کنید. در حالت پیش‌فرض، داده قبلی پاک میشه و داده‌های جدید جایگزین اون میشن. اما اگه در فرآیند دریافت داده‌های جدید مشکلی به وجود بیاد شما دیگه داده‌ای برای نشون دادن به کاربر ندارید. به کمک ست کردن مقدار keepPreviousData با مقدار true، React-Query داده‌های قبلی رو نگه میداره و در صورتی که نتونید داده‌های جدید رو بگیرید، دستتون تو پوست گردو نمیمونه! به شکل زیر:

نامعتبرسازی کش یا invalidateQueryCache

حالتی رو در نظر بگیرید که توی یه صفحه لیستی از آیتم‌ها رو دارید و برای هر آیتم دکمه‌ای گذاشتید که با کلیک روی اون دکمه، درخواست حذف اون آیتم برای سرور ارسال میشه. بعد از اون احتمالا میخواید که آیتم رو از لیستی که کاربر میبینه هم پاک کنید. به کمک invalidate کردن کش درخواستی که اون لیست رو از سرور گرفته شما میتونید دیتای بروز شده اون لیست رو داشته باشید.

برای این کار لازمه query key مربوط به اون درخواست رو داشته باشید و به شکل زیر عمل کنید:

تو این مثال، توی تابع removeUserHandler، بعد از حذف کاربر و با استفاده از invalidateQueryCache، لیست کاربرها رو بروزرسانی کردیم. توجه کنید که با استفاده از تابع refetch که از useQuery استخراج شده هم میتونستیم این کار رو بکنیم. کاربرد invalidateQueryCache برای زمانی هست که دستری به تابع refetch دشوار باشه و با استفاده از اون میتونیم به refetch میانبر بزنیم!

درخواست‌های وابسته به متغیر

اگه درخواستی که میخواید ارسال کنید متغیر هم داشت، مثلا فیلتر یا جستجویی باید روش اعمال میشد، میتونید به راحتی مدیریتش کنید! به شکل زیر:

مشابه عملکرد dependancy ها در useEffect و useCallback و ...، تو React-Query هم از طریق پاس دادن یه آرایه به جای کلید میشه dependancy ایجاد کرد. توی این مثال با عوض شدن مقدار search، درخواست تکرار میشه.


قطعا کاربرد و ویژگی‌های بیشتری رو میشه از React-Query بیان کرد. اما برای طولانی نشدن بیش از حد این مقاله همینجا این قسمت رو تموم میکنیم و سعی میکنم در آینده بیشتر ازش بگم.