محسن محمدی رهنما
محسن محمدی رهنما
خواندن ۱۵ دقیقه·۲ سال پیش

کار با React Query در Nextjs

سلام دوستان خوبم !?، توی این مقاله میخوام یه توضیح درباره این که React Query چی هست و کجا برامون کاربرد داره و اینکه پیاده سازی اون توی Nextjs چجوریه رو با استفاده از یه مینی پروژه توضیح بدم، خوشحال میشم تا آخر این مقاله همراه من باشین.

Next.js vs ReactQuery
Next.js vs ReactQuery


درباره React Query

خب بیایین اول درباره اینکه React Query چی هست و چکاری برامون انجام میده صحبت کنیم ?. React Query یک کتابخانه مدیریت داده Preconfigured ( از پیش پیکربندی شده ) ReactJS است، که به شما قدرت و کنترل بر مدیریت server-side (حالت سمت سرور)، Fetching و ذخیره داده ها و مدیریت خطاها را به روشی ساده و کاملا راحت بدون اینکه تأثیری سوء ی بر Global state برنامه شما بزاره.

خب ما تا اینجا فهمیدیم تعریف React Query چیه ، حالا بریم ببینیم کارش چیه .

همونطور که خودتون بهتر میدونید داده های سرور Async هستند و نمیشه داده ها رو در React ذخیره کرد. حالا چاره چیه ? آهااا اینجا جایی که کتابخانه React Query می درخشد ? تا به ما این امکان رو بده تا داده های Async را دستکاری کنین، اونارو را در حافظه پنهان یا همون Cache نگه داری کنیم و همچنین داده های قدیمی را به روز و یا اون ها رو Synchronize کنیم من که خودم تا باهاش کار نکرده بودم باورم نمیشد همچین چیز خفنی باشه. ?

ویژگی های React Query

خب من اینجا چندتا از ویژگی های این کتابخونه جادویی رو براتون آوردم بریم باهم بررسیش کنیم.

ذخیره سازی(Caching): Window Focus Re-fetching – این امکان را به شما میده تا بسته به فعالیت برنامه شما، داده ها را از قبل واکشی (Fetch) کنه.

درخواست مجدد(Request retry): امکان تنظیم تعداد دفعات مجدد درخواست در صورت بروز خطا.

واکشی اولیه (Prefetching) - بسته به اینکه برنامه شما پس از درخواست شبکه به‌روزرسانی به داده‌های تازه نیاز دارد، می‌تواند داده‌ها را از قبل واکشی کنه. این می تونه داده ها را در پس زمینه به روز کنه.

راستی داشت یادم میرفت ?ویژگی دیگه ای هم که داره اینه که می‌تونید از Cache های پیچیده استفاده کنید تا بتونید برنامه کاملاً بهینه داشته باشین.

از هر چه بگذریم سخن کد خوش تر است ?

خب بیاید این دوستمون رو توی عمل تستش کنیم ببینیم چند مرده حلاجه ، میخوام یه مینی پروژه رو باهم ببریم جلو با استفاده از nextjs . خب قبل از هر کاری وارد سایت Nextjs میشیم و کد دستوری زیر رو داخل ترمینال VScode (حالا با توجه به IDE که استفاده میکنین من اینجا از VScode استفاده کردم?) وارد میکنید

npx create-next-app@latest --typescript

بعد از اتمام نصب CNA ، میریم سراغ نصب axios برای انجام عملیات CRUD روی Api مون .

npm i axios

بعد از اون حالا پکیج React Query رو نصب میکنیم با این خط دستوری که براتون در پایین نوشتم

npm i @tanstack/react-query

یکی دیگه از ابزار های قدرتمند React Query که میخوام بهتون معرفی کنم DevTools اون هست.

npm i @tanstack/react-query-devtools

کاری که این ابزار برامون میکنه ، نمایش تمامی اکشن هایی رو که ما روی query هامون میزنیم و اتفاقاتی روی اون ها می افته و نمایش خطاها درصورت وجود مشکل در خصوص تغییرات روی query ها و ... در کل بگم از شیر مرغ تا جون آدمی زاد رو توی فرآیندهایی که روی query هامون میزنیم رو بهمون نشون میده این بزرگوار ? که هم در زمانمون صرفه جویی بشه و هم کد بهینه تری بنویسیم، اگه بخوام مثال بزنم یه جور مثه Redux DevTools می مونه ، حالا توی پروژه کامل بهتون توضیح میدم چجوری کار میکنه نگران نباشین.

خب الان پروژه مون به Nextjs و React Query مسلح شد ، حالا بریم سراغ این که با اینا میخوایم چیکار کنیم. توی این پروژه ای ما میخوایم با کمک هم بنویسیمش میخوایم لیستی از Post ها رو بگیریم روی اون Pagination انجام بدیم و روی هر کدوم از Post ها که کلیک کردیم Comment مربوط به اون پست نمایش بدیم. خب در وهله اول توی پروژه ای که ایجاد کردیم وارد pages/_app.tsx میشیم و تگ Component رو داخل provider مربوط به React Query میزاریم به این شکل که در تصویر پایین مشاهده میکنید


خب تبریک میگم الان پروژه تون یه پروژه Nextjs ی شد که با React Query تونستید اون رو sync کنین ? خب بریم ببینیم چیکار کردیم، اصلا نگران نباشین هیچ کار عجیب غریبی نکردیم ، ببینید در وهله اول اومدیم QueryClientProvider ( همون provider ی که در قسمت بالا عرض کردم) رو از React Query مون import کردیم و تگ Component رو داخل اون قرار دادیم تا هر جای پروژه مون به query ها مون دسترسی داشته باشیم بعد از اون اومدیم Hydrate اضافه کردیم تا وقتی خواستیم اکشنی روی query ها مون بزنیم ( ویرایش و حذف و... ) nextjs نیاد خطای اینو بده که " آقا دیتای سمت سرور فرق میکنه با کلاینت چرا؟!!?" حالا من توی این مقاله درباره Hydration توضیح دادم چیه اگه خواستین بیشتر بدونین بخونیدش بد نیست. حالا بعدش ReactQueryDevtools رو اضافه کردیم بخاطر اون مواردی که در بالا در خصوص DevTool خدمتتون عرض کردم و در آخر تمام ، نه نه نه صبر کنید ! کلا تموم نشد ، این بخش فقط تموم شد ?. فقط تا الان ما پروژه Nextjs رو آماده کردیم که از React Query بتونیم استفاده کنیم.

خب توی این مرحله ما میخوایم یه لیست ده تایی از پست ها رو دریافت کنیم ( با استفاده از React Query ) ، در وهله اول یه صفحه در پوشه pages با عنوان posts ایجاد میکنیم و بعد از اون یه تابع async برای get کردن پست هامون ایجاد میکنیم به این شکل

pages/posts
pages/posts

همونطر که مشاهده میکنیم من برای دریافت Fake Api از jsonplaceholder استفاده میکنم حالا شما از هر کدوم از Fake Api که دوست داشتین استفاده کنین . خب الان میخوایم دیتای این تابع مون رو با استفاده از ReactQuery استفاده کنیم . برای این کار باید از useQuery استفاده کنیم که یه custom hook برای React Query هست که کارش اینه که برامون عملیات Data fetching رو انجام میده، useQuery دو تا کلید میگیره اولی از نوع آرایه ای هست که نام query که میخوایم روی اون عملیات انجام بشه (Query key) و دومی تابع میگیره (Query function)، که در واقع تابعی هست که میخوایم براساس query ، برامون دیتای مورد نظر رو برگردونه . حالا امیدوارم حدس زده باشید که توی useQuery که میخوام بنویسم پارمتر اول و دوم چی هست . آفرین درست حدس زدین (هرچند که نمیدونم چی حدس زدین ?) پارامتر اولمون میشه "posts" و پارامتر دومون هم میشه تابع fetchPosts

const {data} = useQuery([&quotposts&quot],fetchPosts);

خب ما تونستیم با useQuery دیتا مون رو دریافت کنیم ، حالا بریم ازشون استفاده کنیم . همونطور که در کد بالا مشاهده میکنید useQuery بهمون کلید data رو برمیگردونه و ما میتونیم با استفاده از این کلید به دیتای مورد نظرمون دسترسی داشته باشیم و کافیه توی return صفحه مون (همون جا که JSX هارو مینویسیم) به این شکل عمل کنیم

use data from useQuery
use data from useQuery


این هم یه نمای کلی از کدی که نوشتیم


خب دوستان تبریک میگم شما تونستین عملیات get از api مورد نظرتون با استفاده از ReactQuery پیاده سازی کنین ? باور نمیکنین !!! دوشواری نداره که ? الان با مدرک بهتون ثابت میکنم . در وهله اول پروژه تون رو run کنید بعد از اینکه پروژه run شد ، پایین صفحه سمت چپ لوگوی ReactQuery رو مشاهده میکنید ( شبیه یه گل قرمز هست) بعد از اینکه روش کلیک کردید با همچین صحنه ای رو به رو میشید

روی posts کلیک کنید چیزی که به شما نشون میده به این شکل هست

توی قسمت Data Explorer که مشاهده میکنین ، میبینین که لیست پست هاتون رو آورده و این به این معنی هست که دریافت دیتا از Api با استفاده از ReactQuery موفقیت آمیز بوده دوستان من ?. دوستان ، ما حتی میتونیم مدیریت داشته باشیم برای مواقعی که data تا زمانی که به کاربر نمایش داده میشه ممکنه طول بکشه توی این مدت زمان بهش یک Loading نمایش بدیم یا برای زمانی که Api مون به مشکلی خورد و با خطا مواجه شد چه چیزی به کاربر نمایش بده.

همونطور که مشاهده میکنید با استفاده از کلید isLoading میتونیم مشخص کنیم برای زمانی که در حال دریافت data هستیم بهمون چی نشون بده یا اگه با خطا مواجه شدیم برامون چیکار کنه لیست کاملش رو میتونین از اینجا مشاهده کنید . شاید براتون سوال بشه فرق isLoading با isFetching توی چیه؟ ?

isLoading vs isFetching
isLoading vs isFetching

خب ببینید isFetching برای زمانی هست که میخوایم بررسی کنیم آیا api مون Refetch شده یا نه اگه بهمون true برگردوند یعنی Api مون Refetch شده ولی در حالت دیگه یعنی isLoading قضیه یه خورده فرق میکنه یعنی اینکه ما با Fetching کار نداریم موضوع دیگه برمیگرده به فاصله زمانی که اولین درخواست رو ما به Apiمون میدیم تا زمانی که جوابش بیاد ، که اینو رو با isLoading مدیریت میکنیم که اگه true بود یعنی در حال دریافت پاسخ درخواست داده شده هست که ما در کد بالا اومدیم با نمایش عبارت Loading توی یه تگ h3 اون رو مدیریت کردیم .

خب بریم سراغ بخش بعدی پروژه مون، میخوایم کاربر روی هر پستی که کلیک کرد کامنت مربوط به اون پست هم نمایش بده ، در قدم اول یک component باید بسازیم که تا دیتای مربوط به کامنت هامون رو توی اون نمایش بدیم (من نام component رو PostDetail گذاشتم )

خب داخل این component مون نیاز داریم که لیست کامنت ها رو براساس پست ها دریافت کنیم پس اول نیاز هست یه تابع بنویسیم که کامنت های مربوط به هر پست رو (براساس آی دی اون پست) بهمون بده یعنی به این شکل که مشاهده میکنید


و حالا داخل بدنه ی component میایم query میزنیم تا کامنت هامون رو برا اساس پست ها دریافت کنیم

خب بر میگردیم به صفحه مربوط به پست هامون همون جایی که لیست پست هارو نمایش میدادیم چرا برگردیم؟ به این خاطر که میخوایم خاصیت کلیک روی هر پست بزاریم که وقتی کلیک کرد کامنت مربطو به اون یا بهتر بگم کامپوننت PostDetail اجرا بشه . در وهله اول یه useState مینویسم که روی هر پستی که کلیک شد توی state مون ذخیره بشه و اون state پاس بدیم به props کامپوننت PostDetail مون ( که نام propsی بهش داده بودیم post بود) تا براساس اون آی دی پست رو بگیره و کامنت رو تحویل بده


خب برنامه رو run کنید ببینید چه اتفاقی الان افتاده . یه اتفاق عجیب درسته ؟ شما روی هر پستی که کلیک میکنید کامنت های اونا شبیه هم هست بزارید واضح بگم ، روی هر پستی که کلیک میکنید اصلا کامنت ها آپدیت نمیشن !! و صفحه برای بار اول که load میشه فقط کامنت مربوط به اون پستی که روش کلیک کردیم رو نمایش میده و بعد از اون روی هر پست دیگه ای که کلیک میکنیم همون کامنت رو نشون میده ? اینجاست که باید بگم " توجه .. توجه.. دیگر نگران ریزش مو .. اِاِاِ نه ببخشید اشتباه شد ?" .

این مسئله به این خاطر هست که ما به React Query مون نگفتیم که هر کامنت مون وابستگی داره به یَک یَک پست ها مون . یعنی چی ؟ یعنی این که کامنت هایی که داریم براساس post id هر پست نمایش داده میشن و تفکیک میشن ، حالا تکلیف چیه؟ فقط کافیه توی useQuery که نوشتیم و پارامتر اولی که comments رو بصورت آرایه بهش پاس دادیم ، آیتم دوم آرایه رو post.id بنویسیم.

const {data} = useQuery([&quotcomments&quot, post.id] , () => fetchComments(post.id) );

اینطوری به React Query مون میفهمونیم که عزیزم این کامنت ها وابسته هستن به پست حالا بر چه اساس؟ براساس آی دی پست ها ( یا همون post.id ). الان دیگه روی هر پستی کلیک کنید کامنت مربوط به اون پست رو مشاهده میکنید. ?

خب ما تا به اینجای کار تونستیم اطلاعات مورد نظرمون رو براساس useQuery دریافت و یا درواقع get کنیم حالا برای مابقی فرآیند ها چی؟ یعنی برای ایجاد، حذف و به روز رسانی، برای اون ها هم باید از همین روش استفاده کرد؟ در جواب باید گفت که خیر.

اینجا رَوش کمی متفاوت میشه. React Query میگه که شما وقتی دارید Data ی رو تغییر میدید و ماهیت اون تغییر میکنه پس در اصل شما اومدید اون رو Mutate کردید (یا به عبارت دیگه اون دیتا رو جهش دادید). پس باید از یه Custom hook به نام useMutation استفاده کنید ، Mutation برخلاف query ها، معمولاً برای ایجاد، به روز رسانی، حذف داده ها و یا انجام Server side-effects استفاده می شوند. برای این منظور React Query همونطور که در بالا عرض کردم میاد میگه که بیاد برای اینکار از useMutation استفاده کنید.

خب بریم باهم عملیات حذف پست رو با استفاده از useMutation پیاده سازی کنیم.

در وهله اول باید یه تابعی بنویسیم که عملیات حذف رو برامون انجام بده

توی بدنه تابع اصلی مون (یعنی همون PostDetail) یه const درست میکنیم و تابعی برای حذف نوشته بودیم رو به useMutation پاس میدیم به این شکل

const deleteMutation = useMutation((postId) => deletePost(postId));

حالا در قسمت return میایم یه دکمه حذف درست میکنیم و توی اون ، deleteMutaion رو پاس میدیم

<button ={() => deleteMutation.mutate(post.id)}>Delete</button>

حالا قسمت باحال ماجرا اینجاست که میتونید مدیریت کنید زمانی داره عملیات حذف انجام میشه چه چیزی نمایش بدید یا زمانی که با موفقیت یا با خطا رو به رو میشه چه فرآیندی انجام بشه

به همین راحتی ! یه useMutation مینویسی اون تابعی که میخوای رو هم بهش پاس میدی خودش بقیه کارارو میکنه به همین خفنی !?

حالا بریم برای update پست ها ، اینم مثه همینه فقط توی تعریف تابع باهم فرق میکنن

حالا پایین useMutation قبلی که برای حذف نوشته بودیم یه const دیگه تعریف میکنیم به این شکل

const updateMutation = useMutation((postId) => updatePost(postId));

توی قسمت return یه دکمه ایجاد میکنیم به منظور بروزرسانی و توی اون نام مغییر بالا که نوشتیم رو بهش پاس میدیم به این شکل

<button ={() => updateMutation.mutate(post.id)}>Update title</button>

و در نهایت به این شکل

خب دوستان پروژه مون داره به پایان خودش نزدیک میشه میرسیم به بخش نهایی که خیلی مهم هست که اونم Prefetching هست بزارید با یه مثال براتون توضیح بدم ، شده براتون اتفاق بیوفته مثلا توی صفحه تو Pagination داشته باشید و روی هر صفحه که کلیک میکنید تا زمانی که دیتا بیاد روی صفحه بشینه قبلش یه Loading یا Skeleton نمایش بدید تا به کاربر بگید در حال دریافت اطلاعات از سمت سرور هستید ! خب این دوستمون یعنی PrefetchQuery کار رو برامون راحت کرده ، همونطور که از اسمش مشخصه دیتا رو یه بار از قبل Fetch میکنه برامون بدون اینکه لازم باشه loading یا هرچیه دیگه قبلش نشون بدیم.

الان میخوام یه Pagination توی پروژه مون با اسفاده از همین ابزار پیاده سازی کنم .

خب در این بخش اول باید توی تابع دریافت پست هامون یه تغییر ریزی انجام بدیم یه کوئری به نام page به api مون اضافه کنیم و اون رو داینامیک کنیم یعنی بصورت زیر

فقط قبلش بیایم یه متغییر کلی بنویسیم که حداکثر تعداد صفحه هایی که میخوایم توی صفحه مون داشته باشیم رو بگیم.

const maxPostPage = 10;

توی بدنه تابع اصلی مون ( Posts ) یه useState مینویسیم برای موقعیت فعلی صفحه مون و مقدار پیش فرض اون هم عدد یک رو میدیم یعنی همون صفحه اول

const [currentPage, setCurrentPage] = useState(1);

حالا اون useQuery که نوشته بودیم رو به شکل زیر تغییر میدیم

const { data,isError,error,isLoading }= useQuery( [&quotposts&quot, currentPage ],() => fetchPosts(currentPage));

همونطور که مشاهده میکنید چون تابع fetchPosts آرگومان میگیره پس به نوعی به useQuery میگیم که query مون وابستگی داره پس به آیتم دوم آرایه مون اون state مون رو پاس می دیم

خب همونطور که در تصویر بالا مشاهده میکنید اول اومدم از یه Custom hooke استفاده کردم به نام useQueryClient تا بتونیم با استفاده از اون عملیات Prefetch صفحه هام رو انجام بدم این QueryClient کلی متد دیگه هم داره که میتونید از این اینجا مشاهده کنید. بعدش اومدیم یه useEffect نوشتیم داخلش شرطی مبنی بر اینکه اگه صفحه جاری مون کوچکتر از تعداد حداکثر کل صفحه هاتمون بود بیاد یه دونه به صفحه جاری اضافه کنه در قالب nextPage و به prefetchQuery پاسش میدیم تا برامون صفحه بعدی رو از قبل Fetch کنه ، همونطور که میبینید تعریف prefetchQuery فرقی با useQuery هایی که مینوشتیم نداره یعنی این هم یه QueryKey که ["posts", nextPage] هست میگیره و یه QueryFunction که fetchPosts(nextPage) هست رو میگیره و در نهایت currentPage, queryClient رو به Dependency array مربوط به useEffect مون میدیم تا هر موقع تغییر کردن عملیات Re-render مون اتفاق بیوفته.

خب حالا بریم سراغ UI مون

خب دوستان عزیزم این هم از Pagination و پروژه مون هم به پایان رسید . من باز فایل پروژه رو میزارم تا داشته باشین .

فایل پروژه

نتیجه گیری

خب دوستان ما تا الان با تعریف React Query و کاربرد اون آشنا شدیم تونستیم باهاش یه مینی پروژه رو CRUD کنیم. React Query گسترده تر از این حرفاس که بخوایم به عنوان یه ابزار معمولی بهش نگاه کنیم و من سعی کردم تا جای ممکن اون قسمت هایی که ممکنه توی پروژه های خودتون با اون به چالش بخورین رو تو این مینی پروژه پوشش بدم و حتما تاکید میکنم اگه میخواید Chaching سمت سرور داشته باشید این ابزار خیلی میتونه کمکتون کنه، بخصوص در زمان query زدن های تودرتو و اینکه React Query وقتی ببینه شما توی پروژه تون از چند تا api یکسان استفاده کردید ، میاد اون رو Cache میکنه تا الکی request اضافی سمت سرور نفرسته و در نهایت کلی خوبی های دیگه، این هم قبول دارم که هیچ ابزاری کامله کامل نیست باید دید نیاز و وسعت پروژمون در چه مقیاسی هست و به تبعه اون از اون ابزار و پکیج مورد نظر استفاده کرد.

امیدوارم مقاله مفیدی باشه براتون، از صمیم قلبم آرامش براتون آررزو میکنم.

react querynextjstypescript
دویی از خود بُرون کردم یکی دیدم دوعالم را / یکی جویم، یکی گویم، یکی دانم، یکی خوانم
شاید از این پست‌ها خوشتان بیاید