چه زمان و چگونه از React Query استفاده کنیم؟ — به زبان ساده
یکی از چالشهایی که در زمان ساخت اپلیکیشنها با ریاکت مواجه میشویم، تعیین یک الگوی کد برای واکشی دادهها از سرور است. رایجترین روش برای مدیریت واکشی دادهها در ریاکت، استفاده از حالت گلوبال به عنوان یک مکانیسم برای تعیین حالت کنونی عملیات واکشی است. در این مقاله در مورد این موضوع توضیح میدهیم که استفاده از React Query چه زمان و به چه صورت مناسب است.
در ادامه مثالی از واکشی دادهها از یک API به نام را مشاهده میکنید:
1import React, {useState, useEffect} from 'react';
2import axios from 'axios';
3// regular fetch with axios
4function App() {
5 const [isLoading, setLoading] = useState(false)
6 const [isError, setError] = useState(false)
7 const [data, setData] = useState({});
8
9 useEffect(() => {
10 const fetchData = async () => {
11 setError(false);
12 setLoading(true);
13
14 try {
15 const response = await axios('http://swapi.dev/api/people/1/');
16
17 setData(response.data);
18 } catch (error) {
19 setError(true);
20 }
21 setLoading(false);
22 };
23 fetchData()
24 }, []);
25return (
26 <div className="App">
27 <h1>React Query example with Star Wars API</h1>
28 {isError && <div>Something went wrong ...</div>}
29
30 {isLoading ? (
31 <div>Loading ...</div>
32 ) : (
33 <pre>{JSON.stringify(data, null, 2)}
34 </pre>
35 )}
36 </div>
37 );
38}
39export default App;
کد فوق نیازمند هر دو قلاب useState و useEffect است و از سه حالت مختلف برای ذخیره دادهها و تعیین این که آیا اپلیکیشن دادهها را واکشی میکند یا پاسخ خطایی از سوی API دریافت شده است، استفاده میکند. این الگو بارها و بارها در اغلب اپلیکیشنها برای پیادهسازی منطق واکشی دادهها تکرار شده است. البته موضوع به همین جا ختم نمیشود. رایجترین مشکلات در زمان واکشی کردن دادهها به شرح زیر هستند:
- دادهها روی همه وهلههای اپلیکیشن به اشتراک گذاشته میشوند و ممکن است افراد دیگر آنها را تغییر دهند.
- دادها ممکن است قدیمی باشند و نیاز به رفرش باشد.
- نیاز به مدیریت کش کردن و اعتبارزدایی دادهها برای بهینهسازی عملیات درخواست وجود دارد.
در نهایت یک مشکل نیز در مورد حالت لوکال وجود دارد که به طور معمول ترجیحهای کاربر مانند theme و پیکربندی نوار کناری را همراه با حالت ریموت که دادههای واکشی شده از API را ذخیره کرده است، نگهداری میکند:
1//what global state commonly look like nowadays
2const state = {
3 theme: "light",
4 sidebar: "off",
5 followers: [],
6 following: [],
7 userProfile: {},
8 messages:[],
9 todos:[],
10}
اکنون اگر بتوانیم حالت لوکال را از حالت ریموت جدا کنیم چه اتفاقی میافتد؟ همچنین آیا بهتر نیست مقدار کد قالبی مورد نیاز برای نوشتن واکشی دادهها را کاهش بدهیم؟
یک راهحل میتواند ایجاد قلاب سفارشی خاص برای مدیریت واکشی و مدیریت دادهها باشد. این یک راهحل کاملاً معتبر است. همچنین میتوانید این قلاب را به صورت یک کامپوننت در هابهایی مانند Bit.dev به اشتراک بگذارید تا بتوانید در همه پروژههایی که روی آنها کار میکنید از آن بهره بگیرید.
راهحل دیگر که در این مقاله آن را تشریح خواهیم کرد، استفاده از React Query (+) است. این کتابخانه به شما کمک میکند که دادههای ریموت خود را واکشی، همگامسازی، بهروزرسانی و کش کنید و در عین حال حجم کدی که لازم است نوشته شود را با ارائه کد و قلاب و یک تابع کاربردی کاهش میدهد.
برای این که این مقاله را به روش بهتری دنبال کنید، پیشنهاد میکنیم این ریپوی ساده (+) را دانلود کنید.
این ریپوی ساده کوچک یک آرایه از رشتهها را از مسیر API با استفاده از axios بازیابی میکند. شما میتوانید یک رشته جدید را با استفاده از فرم ارائه شده درون آرایه قرار دهید. همچنین یک بخش React Query DevTools باز میشود که میتوانید دادههای کششده را به صورت آنی مشاهده کنید.
قلاب useQuery
قلاب useQuery تابعی است که برای ثبت کد واکشی دادهها در کتابخانه «ریاکت کوئری» مورد استفاده قرار میگیرد. این قلاب یک کلید اختیاری و یک تابع ناهمگام برای واکشی دادهها و بازگشت مقادیر مختلف میگیرد که میتوان از آن برای اطلاعرسانی به کاربران در مورد حالت کنونی اپلیکیشن استفاده کرد.
برای نمونه در ادامه تلاش میکنیم مثال قبلی API جنگ ستارگان را با استفاده از این قلاب بازسازی کنیم:
1import React from 'react';
2import axios from 'axios';
3import {useQuery} from 'react-query';
4// react-query fetch with axios
5function App() {
6 const { isLoading, error, data } = useQuery('fetchLuke', () =>
7 axios('http://swapi.dev/api/people/1/'))
8return (
9 <div className="App">
10 <h1>React Query example with star wars API</h1>
11 {error && <div>Something went wrong ...</div>}
12
13 {isLoading ? (
14 <div>Retrieving Luke Skywalker Information ...</div>
15 ) : (
16 <pre>{JSON.stringify(data, null, 2)}
17 </pre>
18 )}
19 </div>
20 );
21}
22export default App;
توجه کنید که ما در این کد از قلابهای معمولی useState و useEffect استفاده نکردهایم. دلیل این امر آن است که useQuery از قبل مقادیر مختلفی مانند isLoading، پاسخ error و data بازگشتی دارد که میتوانیم درون اپلیکیشن مورد استفاده قرار دهیم.
اگر به ریپوی ساده خود بازگردیم، میبینیم که یک لیست todo با استفاده از همین تابع useQuery درون فایل /pages/index.js از api واکشی شده است:
1const { status, data, error, isFetching } = useQuery('todos', async () => {
2 const { data } = await axios.get('/api/data')
3 return data
4})
تفاوت در اینجا است که در این کد از isFetching نیز استفاده کردهایم که مشخص میسازد آیا کوئری در حال حاضر دادهها واکشی شدهاند یا خیر. در ادامه اهمیت این موضوع را توضیح خواهیم داد. فعلاً با شیوه ویرایش دادههای ریموت آشنا میشویم.
قلاب useMutation
قلاب useMutation (+) به طور معمول برای ایجاد/بهروزرسانی/ حذف دادههای ریموت مورد استفاده قرار میگیرد. این تابع دو تابع ناهمگام برای بهروزرسانی دادهها دریافت کرده و یک تابع mutate بازگشت میدهد که میتوان برای آغاز تغییر فراخوانی کرد.
1const [mutate] = useMutation(
2 text => axios.post('/api/data', { text }),
3)
4mutate("Learn about React Query")
همچنین میتوانید تابعهای اختیاری را به آن ارسال کنید که تنها زمانی آغاز میشوند که تابع mutation نتایج مشخصی از قبیل onSuccess و onError بازگشت دهد. در این ریپوی ساده میبینیم که از تابع mutation برای ارسال دادههای جدید به API در زمان تحویل شدن فرم استفاده شده است. همچنین زمانی که درخواست post موفق بود، ورودی متنی خالی میشود:
1const [mutatePostTodo] = useMutation(
2 text => axios.post('/api/data', { text }),
3 {
4 onSuccess: () => {
5 // Query Invalidations
6 // queryCache.invalidateQueries('todos')
7 setText('')
8 },
9 }
10)
اکنون اگر تلاش کنید متن جدیدی را درون اپلیکیشن دمو قرار دهید، خواهید دید که لیست todo رفرش نمیشود. برای این که به ریاکت کوئری اعلام کنیم که لیست todo را رفرش کند، باید کد بالای تابع setText را از کامنت خارج کنیم:
1{
2 onSuccess: () => {
3 // Query Invalidations
4 queryCache.invalidateQueries('todos')
5 setText('')
6 },
7}
در این صورت queryCache.invalidateQueries اعتبارزدایی و با کلید todo کش شده و دوباره واکشی ریاکت کوئری روی دادهها اجرا میشود.
ابزار queryCache
همچنان که در DevTools دمو دیدیدم، ریاکت کوئری دادههای بازیابی شده تحت کلید todo را کش میکند، اما به صورت خودکار پس از آن که آنها را کش کرد از کار میافتد، مگر این که staleTime را پیکربندی کنید.
queryCache یک ابزار است که تابعهای زیادی دارد و با استفاده از آن میتوانید کوئریها را هر چه بیشتر دستکاری کنید. در این مثال دیدیم که تابع queryCache.invalidateQueries برای ارسال درخواست جدید از سوی ریاکت کوئری جهت واکشی دادههای لیست todo استفاده میشود. برای مشاهده فهرست کامل متدهای queryCache به این صفحه (+) مراجعه کنید.
سخن پایانی
React Query یک کتابخانه قلاب عالی برای مدیریت درخواستهای دادهای است که موجب میشود نیاز ما به قرار دادن دادههای ریموت درون حالت گلوبال به کلی مرتفع شود. کافی است که به کتابخانه اعلام کنید که در کجا باید دادهها را واکشی کنید تا به صورت خودکار عملیات کش کردن، بهروزرسانی پسزمینه و منسوخ کردن دادهها را بدون هیچ نوع کد یا پیکربندی اضافی انجام دهد.
ریاکت کوئری نیاز به استفاده از قلابهای useState و useEffect را از میان برمیدارد و به جای آنها چند خط از منطق React Query قرار میدهد. این کتابخانه در بلندمدت به افزایش قابلیت نگهداری، پاسخگویی و سرعت اپلیکیشن شما کمک میکند.