ریآکت به همراه چندین هوک داخلی قدرتمند مانند useState، useContext و useEffect عرضه میشود. با این حال، در پروژههای واقعی معمولاً به هوکهایی با اهداف اختصاصیتر نیاز پیدا میکنید؛ برای مثال هوکی برای واکشی دادهها (Data Fetching)، بررسی وضعیت آنلاین بودن کاربر یا مدیریت اتصال به یک سوکت چت.
با اینکه این هوکها به صورت پیشفرض در ریآکت وجود ندارند، شما میتوانید به سادگی هوکهای سفارشی (Custom Hooks) خود را متناسب با نیازهای برنامه خلق کنید.
هوک سفارشی چیست و چطور آن را بنویسیم؟
چگونه منطق و چرخههای حیات را بین چندین کامپوننت به اشتراک بگذاریم؟
قوانین نامگذاری و ساختاردهی به هوکهای سفارشی چیست؟
چه زمانی و چرا باید منطق برنامه را به یک هوک سفارشی منتقل کنیم؟
فرض کنید در حال توسعه برنامهای هستید که به شدت به شبکه وابسته است و میخواهید در صورت قطع شدن اینترنت، به کاربر هشدار دهید. برای این کار، به یک استیت برای ذخیره وضعیت شبکه (isOnline) و یک افکت برای گوش دادن به رویدادهای online و offline مرورگر نیاز دارید:
// درون یک کامپوننت مثل StatusBar یا SaveButton
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
اگر بخواهید همین منطق را در یک دکمه ذخیره (SaveButton) هم پیاده کنید تا در صورت قطع اینترنت غیرفعال شود، کپی-پیست کردن این کد ایده خوبی نیست. اینجاست که هوک سفارشی وارد عمل میشود.
ما تابعی به نام useOnlineStatus میسازیم و تمام این کدهای تکراری را به داخل آن منتقل میکنیم:
// useOnlineStatus.js
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline; // مقدار مورد نظر را برمیگردانیم
}
حالا کامپوننتهای ما بسیار تمیز و خوانا میشوند و به جای اینکه بگویند چطور کار را انجام میدهند، اعلام میکنند که چه هدفی دارند:
function StatusBar() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ متصل' : '❌ قطع ارتباط'}</h1>;
}
function SaveButton() {
const isOnline = useOnlineStatus();
return (
<button disabled={!isOnline}>
{isOnline ? 'ذخیره تغییرات' : 'در حال اتصال مجدد...'}
</button>
);
}
یک اشتباه رایج این است که فکر کنیم کامپوننتهای StatusBar و SaveButton اکنون از یک استیت مشترک استفاده میکنند. خیر! هوکهای سفارشی منطقِ حالتدار (Stateful Logic) را به اشتراک میگذارند، نه خودِ استیت (State) را.
هر بار که یک هوک سفارشی را صدا میزنید، تمام استیتها و افکتهای درون آن کاملاً از نو و به صورت مستقل برای همان کامپوننت ساخته میشوند. در مثال بالا، دلیل هماهنگ بودن دو کامپوننت این است که هر دوی آنها مستقل از هم دارند یک دیتای بیرونی واحد (رویداد مرورگر) را رصد میکنند.
ریآکت از روی نام توابع متوجه میشود که با یک تابع معمولی سر و کار دارد یا با یک ساختار ریآکتی.
نام کامپوننتها باید با حرف بزرگ شروع شود (StatusBar) و یک ساختار JSX برگرداند.
نام هوکها (چه داخلی و چه سفارشی) باید حتماً با پیشوند use و متعاقباً یک حرف بزرگ شروع شود (useOnlineStatus ،useState).
توابع هوک میتوانند هر نوع مقداری (رشته، عدد، آرایه، شیء یا هیچچیز) را برگردانند. فایده اصلی این قانون این است که ابزار لینتر (Linter) ریآکت میتواند کدهای شما را اسکن کند و مطمئن شود که قوانین هوکها (مانند عدم استفاده از هوک درون حلقهها یا شرطها) رعایت شده است. اگر نام هوک خود را به getOnlineStatus تغییر دهید، لینتر دیگر اجازه استفاده از useState و useEffect را در آن نخواهد داد.
از آنجا که هوکهای سفارشی همراه با کامپوننت شما در هر رندر دوباره اجرا میشوند، همیشه آخرین مقادیر استیتها و پروپها را دریافت میکنند. شما میتوانید خروجی یک هوک را به عنوان ورودی به هوک دیگری پاس دهید؛ درست مثل زنجیر کردن افکتهای صوتی در یک استودیو!
به این مثال از مدیریت اتاق چت نگاه کنید:
export default function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
// خروجی استیت serverUrl و پروپ roomId را به هوک سفارشی خود تزریق میکنیم
useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
return (
<>
<label>Server URL: <input value={serverUrl} onChange={e => setServerUrl(e.target.value)} /></label>
<h1>به اتاق {roomId} خوش آمدید!</h1>
</>
);
}
درون هوک سفارشی useChatRoom، افکت ما به تغییرات ورودیها واکنش نشان میدهد:
export function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = { serverUrl, roomId };
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // با تغییر هر کدام از ورودیها، افکت مجدداً همگامسازی میشود
}
اگر بخواهید یک رویداد مانند onReceiveMessage را از کامپوننت به هوک سفارشی پاس دهید، برای جلوگیری از قطع و وصل شدن ناخواسته چت (به دلیل تعویض آدرس تابع در رندرهای متوالی)، حتماً آن را درون هوک سفارشی با useEffectEvent بستهبندی کنید:
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
// تبدیل کالبک ورودی به یک Effect Event غیرواکنشپذیر
const onMessage = useEffectEvent(onReceiveMessage);
useEffect(() => {
const options = { serverUrl, roomId };
const connection = createConnection(options);
connection.on('message', (msg) => {
onMessage(msg); // فراخوانی امن
});
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]); // دیگر نیازی به اضافه کردن onReceiveMessage به وابستگیها نیست
}
نیازی نیست برای هر تکه کد کوچکی هوک سفارشی بسازید. برای مثال، ساختن هوک useFormInput برای پنهان کردن یک useState ساده معمولاً زیادهروی است.
اما هر زمان که یک useEffect مینویسید، جدیتر به بومیسازی آن در یک هوک سفارشی فکر کنید. افکتها در واقع دریچههای خروج (Escape Hatches) شما از دنیای ریآکت برای اتصال به سیستمهای بیرونی هستند. پنهان کردن آنها در یک هوک سفارشی خوانایی کامپوننت را بالا برده و جریان داده را کاملاً شفاف میسازد (آدرس را به هوک تحویل میدهید و دیتا را پس میگیرید).
علاوه بر این، اگر در آینده ریآکت قابلیت جدیدی معرفی کند که جایگزین بهتری برای افکت شما باشد (مثل جابجایی افکتهای شبکه با useSyncExternalStore)، شما نیازی به تغییر تکتک کامپوننتهای پروژه نخواهید داشت و فقط بدنه هوک سفارشی را بهروزرسانی میکنید.
هوکهای سفارشی به شما اجازه میدهند منطق حالتدار کامپوننتها را پنهان و بازاستفاده کنید.
نام هوک سفارشی حتماً باید با کلمه کوچک use شروع شده و کلمه بعدی با حرف بزرگ آغاز شود.
هر فراخوانی از یک هوک، یک استیت کاملاً ایزوله و مستقل درون کامپوننت میزبان ایجاد میکند.
میتوانید دیتای خروجی یک هوک را به عنوان ورودی به هوک دیگر پاس دهید تا یک جریان داده پویا بسازید.
کالبکهای ورودی هوکهای سفارشی را با useEffectEvent مهار کنید تا از اجرای ناخواسته افکتها جلوگیری شود.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn