اپلیکیشن های فرانت اند بدون فراخوانی سرویس های سمت بک اند به وسیله API کامل نیستند . و از طرفی بخش زیادی از کد مربوط به فراخوانی یک API تکراری است. با ایجاد یک Custom hook می توان از این تکرار کد جلوگیری کرد. در ری اکت به طور معمول از fetch API و کتابخانه axios برای ارتباط با سرور استفاده می شود. axios به این دلیل که از قابلیت های بیشتری از جمله پشتیبانی از interceptor ها روش پیشنهادی ماست. در این مقاله از آموزش React می خواهیم یک custom hook به نام useAxios برای فراخوانی API تعریف کنیم.
تعریف useAxios hook در چند مرحله انجام می شود:
- در ابتدا فراخوانی API از طریق کتابخانه axios و در app component انجام می شود.
- state های response، loading و error را که تقریبا در هر بار فراخوانی API ها لازم هستند، تعریف می کنیم.
- یک custom hook برای فراخوانی API با استفاده از axios تعریف می کنیم.
فراخوانی API در App component
قبل از پیاده سازی custom hook اجازه دهید یک API را با روش معمول پیاده سازی کنیم تا ببینیم با چه مسائلی مواجه می شویم. به طور کلی همه API ها یک آدرس پایه یا base address دارند. ما ابتدا باید آدرس پایه را در axios تعریف کنیم تا هر بار نیاز به مقدار دهی این آدرس در هر API نباشد. در صورتی که اپلیکیشن شما نیازمند چندین آدرس API است به سادگی می توانید در axios و با استفاده از قابلیت instance اینکار را انجام دهید.
در App component ما فقط یک API را فراخوانی می کنیم که لیستی از پست ها را به عنوان خروجی به کلاینت ارسال می کند. برای این مثال از وب سایت jsonplaceholder و از posts API استفاده می کنیم. لیست زیر، کد مربوط به فراخوانی API در App component را نشان می دهد:
//App Component
import { useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const App = () => {
const fetchData = () => {
axios
.get('/posts')
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<div className='app'>
//do something
</div>
);
};
export default App;
افزودن state به اپلیکیشن
تا اینجا فقط نتیجه API را در کنسول لاگ کرده ایم. حال از state ها برای نگهداری نتیجه فراخوانی API و یا خطای دریافت شده استفاده می کنیم. همچنین از loading state برای نمایش لودینگ استفاده می کنیم. کد فوق را به شکل زیر تغییر می دهیم:
// App Component
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const App = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios
.get('/posts')
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<div className='app'>
//do something
</div>
);
};
export default App;
کد فوق کاملا واضح است. تغییراتی که در کد فوق داشته ایم صرفا تعریف چند state برای مدیریت نتیجه API است.
تعریف یک custom hook
شاید در نگاه اول custom hook ها پیچیده به نظر برسند اما اگر آن ها را به عنوان یک کامپوننت ساده در نظر بگیریم، مفهومشان ساده تر خواهد شد. در واقع custom hook ها عملکردی مشابه کامپوننت ها دارند با این تفاوت که به جای JSX یک مقدار را به عنوان خروجی بر می گردانند. این تعریف تا حدی باعث درک بهتر مفهوم custom hook ها می شود.
یک فایل به نام useAxios.js تعریف می کنیم و کدهایی که در App component تعریف کرده ایم را به شکل زیر به این فایل اضافه می کنیم:
// useAxios hook (first draft)
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const useAxios = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios
.get('/posts')
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, []);
// custom hook returns value
return { response, error, loading };
};
export default useAxios;
تنها تفاوت کد فوق با آنچه که قبلا در App component پیاده سازی شده است در این است که useAxios سه مقدار loading، response و error را به عنوان خروجی بر می گرداند.
تنها مسئله ای که در کد فوق وجود دارد این است که useAxios امکان فراخوانی API های مختلف را ندارد. چنانچه نیاز به تغییر آدرس API باشد یا تغییر نوع فراخوانی API از Get به Post، این امکان فراهم نیست. بنابراین باید useAxios را به گونه ای تغییر دهیم تا به سادگی برای هر درخواست API قابل استفاده باشد.
با توجه به توضیحات فوق می توانیم متغیری برای آدرس API تعریف کنیم و آن را از طریق props به useAxios ارسال کنیم. همچنین برای تعیین نوع فراخوانی API، یعنی GET، POST، PUT و DELETE از پارامتر method استفاده می کنیم. علاوه بر این دو متغیر، دو متغیر دیگر برای body و header هم در نظر می گیریم که البته اختیاری هستد و در صورتی که نوع فراخوانی POST یا PUT باشد از آن ها استفاده می کنیم. کد نهایی useAxios به شکل زیر است:
// useAxios hook
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const useAxios = ({ url, method, body = null, headers = null }) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios[method](url, JSON.parse(headers), JSON.parse(body))
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, [method, url, body, headers]);
return { response, error, loading };
};
export default useAxios;
اکنون آماده ایم تا از این custom hook در App component استفاده کنیم. کد زیر App component را پس از استفاده از useAxios hook نشان می دهد:
// App Component
const App = () => {
const { response, loading, error } = useAxios({
method: 'post',
url: '/posts',
headers: JSON.stringify({ accept: '*/*' }),
body: JSON.stringify({
userId: 1,
id: 19392,
title: 'title',
body: 'Sample text',
}),
});
const [data, setData] = useState([]);
useEffect(() => {
if (response !== null) {
setData(response);
}
}, [response]);
return (
<div className='App'>
<h1>Posts</h1>
{loading ? (
<p>loading...</p>
) : (
<div>
{error && (
<div>
<p>{error.message}</p>
</div>
)}
<div>{data && <p>{data.id}</p>}</div>
</div>
)}
</div>
);
};
export default App;
می بینیم که چقدر کد، خواناتر شد. اکنون بخش زیادی از کدهای تکراری که برای هر API لازم بود از ابتدا بنویسیم حذف شده است. چنانچه نظری در ارتباط با این مقاله دارید در بخش کامنت ها مطرح کنید. همچنین برای مطالعه بیشتر و عمیق تر ری اکت می توانید به بخش آموزش React مراجعه کنید.