ساخت Hook های سفارشی خودتان در React

با ساخت Hook های سفارشی خودتان خواهید توانست منطق کامپوننت را استخراج کرده و در توابع قابل استفادۀ مجدد به کار بندید.

وقتی استفاده از Effect Hook را یاد می‌گرفتیم، ما چنین کامپوننتی را از یک برنامۀ چت دیدیم پیغامی را نمایش می‌داد که آیا دوست ما آنلاین است یا نه:

import { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

حال فرض کنید برنامۀ چت ما یک لیست مخاطبین هم دارد و ما قصد داریم اسامی افراد آنلاین را با رنگ سبز رندر کنیم. ما می‌توانیم منطقی مشابه منطق ذکر شده را در کامپوننت FriendListItem خود کپی و پیست کنیم اما این راهکاری ایده‌آل نیست:

import { useState, useEffect } from 'react';

function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

در مقابل، تمایل داریم این منطق را بین FriendStatus و FriendListItem به اشتراک بگذاریم.

به طور معمول در ری اکت برای انجام این کار دو راه متداول جهت به اشتراک گذاشتن منطق حالت‌دار بین کامپوننت ها داریم: render props و کامپوننت های مرتبۀ بالاتر. می‌خواهیم نگاهی بیندازیم تا ببینیم Hook ها چگونه چنین مشکلاتی را بدون مجبور کردن شما در اضافه کردن کامپوننت های بیشتر به درخت، حل می‌کند.

استخراج کردن یک Hook سفارشی

وقتی می‌خواهیم یک منطق را بین دو تابع جاوا اسکپریت به اشتراک بگذاریم، آن را در یک تابع سومی استخراج می‌کنیم. هردوی کامپوننت ها و Hook ها، تابع هستند در نتیجه این عمل در Hook ها نیز قابل انجام است.

یک Hook سفارشی (custom) یک تابع جاوا اسکپریت است که نام آن با use شروع شده و ممکن است Hook های دیگری را فراخوانی کند. برای مثال، useFriendStatus که در زیر آمده است، اولین Hook سفارشی ماست:

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

داخل آن هیچ چیز جدیدی وجود ندارد و منطق نیز از کامپوننت های بالا درون آن کپی شده است. درست مانند کامپوننت، مطمئن شوید که فراخوانی Hook های دیگر را بدون اعمال شرط و در سطوح بالای Hook سفارشی خود انجام می‌دهید.

برخلاف یک کامپوننت React، یک Hook سفارشی نیاز ندارد که یک نشانۀ خاص داشته باشد. ما می‌توانیم تصمیم بگیریم که این Hook چه چیزی را به عنوان آرگومان بگیرد و در صورت نیاز چه چیزی را برگرداند. به عبارت دیگر، درست مانند یک تابع معمولی است. نام آن همیشه باید با use شروع شود و در نتیجه شما با یک نگاه به یاد خواهید آورد که قوانین Hook ها باید برای آن رعایت گردد.

هدف از درست کردن هوک useFriendStatus این است که وضعیت دوستمان را برای ما به اشتراک بگذارد. به همین دلیل friendID را به عنوان یک آرگومان می‌گیردو اینکه ایا آن دوست آنلاین است یا خیر را برمی‌گرداند:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  return isOnline;
}

حال بیایید ببینیم چطور می‌توانیم از Hook سفارشی خود استفاده کنیم.

استفاده از Hook سفارشی

در ابتدا، هدف ما این بود که منطق تکرار شده را از کامپوننت های FriendStatus و FriendListItem حذف کنیم. هردوی آنها می‌خواهند بدانند که دوست ما آنلاین است یا خیر.

اکنون که این منطق را درون یک هوک useFriendStatus استخراج کرده‌ایم، می‌توانیم به راحتی از آن استفاده کنیم:

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

آیا این کد دقیقا برابر با مثال‌های اصلی است؟ بله، دقیقا به همان شیوه کار می‌کند. اگر به دقت بررسی کنید، متوجه خواهید شد که ما هیچ تغییری را در رفتار آن به وجود نیاوردیم. تمام کاری که انجام دادیم این بود که برخی کد مشترک میان دو تابع را درون یک تابع مجزا استخراج کردیم. Hook های سفارشی قراردادی هستند که به طور طبیعی از طراحی Hook ها پیروی می‌کنند نه یک قابلیت ری اکت.

آیا حتما باید نام Hook های سفارشی خود را با use شروع کنم؟ لطفا این کار را بکنید. این قرارداد خیلی مهم است. بدون این قرارداد، ما نمی‌توانیم به طور اتوماتیک تخلف از قوانین Hook ها را کنترل کنیم زیرا نمی‌توانیم بگوییم که آیا یک تابع خاص شامل فراخوانی‌هایی به Hook های داخل آن هست یا نه.

آیا دو کامپوننت که در حال استفاده از Hook یکسانی هستند، حالت را به اشتراک می‌گذارند؟ خیر. Hook های سفارشی مکانیزمی جهت استفادۀ مجدد از منطق حالت‌دار است، اما هربار که از یک Hook سفارشی استفاده می‌کنید، تمام حالت‌ها و effect های داخل آن به طور کامل ایزوله شده است.

چگونه یک Hook سفارشی حالت ایزوله شده‌ای پیدا می‌کند؟ هر فراخوانی یک Hook یک حالت ایزوله پیدا می‌کند. چون ما useFriendStatus را به طور مستقیم فراخوانی می‌کنیم، از دید ری اکت، کامپوننت ما useState و useEffect را فراخوانی می‌کند. و همانطور که قبلا یاد گرفته‌ایم، می‌توانیم useState و useEffect را چندین بار در یک کامپوننت فراخوانی کنیم و این فراخوانی‌ها کاملا از هم مستقل خواهند بود.

نکته: انتقال اطلاعات بین Hook ها

از آن جایی که Hook ها تابع هستند، ما می‌توانیم اطلاعات را بین آن منتقل کنیم.

جهت تبیین این موضوع، ما از کامپوننت دیگری از مثال چت فرضی خودمان استفاده می‌کنیم. این یک گیرندۀ پیام چت است که نشان می‌دهد آیا دوستی که اکنون انتخاب شده است آنلاین است یا خیر:

const friendList = [
  { id: 1, name: 'Phoebe' },
  { id: 2, name: 'Rachel' },
  { id: 3, name: 'Ross' },
];

function ChatRecipientPicker() {
  const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />
      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}

ما آی‌دی دوست انتخاب شده را در متغیر حالت recipientID نگه می‌داریم و هنگامی که کاربر دوست دیگری را از <select> انتخاب کرد، آن را آپدیت می‌کنیم.

از آن جایی که فراخوانی هوک useState آخرین مقدار متغیر حالت recipientID را به ما می‌دهد، ما می‌توانیم آن را به عنوان یک آرگومان به هوک سفارشی useFriendStatus خود منتقل کنیم:

const [recipientID, setRecipientID] = useState(1);
  const isRecipientOnline = useFriendStatus(recipientID);

این به ما اجازه می‌دهد متوجه شویم که آیا دوستی که اکنون انتخاب شده است آنلاین است یا خیر. اگر دوست دیگری را انتخاب کنیم و متغیر حالت recipientID را آپدیت کنیم، هوک useFriendStatus ما از دوست انتخاب شدۀ قبلی unsubscribe کرده و به وضعیت دوست انتخاب شدۀ جدید subscribe می‌کند.

useYourImagination()

Hook های سفارشی انعطاف پذیری به اشتراک گذاری منطق را که قبلا در کامپوننت های ری اکت میسر نبود را برایمان فراهم می‌کنند. شما می‌توانیم Hook های سفارشی‌ای را بنویسید که محدوده وسیعی از موارد کاربرد را پوشش دهد مانند مدیریت کردن، انیمیشین، تایمرها، تعریف اشتراکات و احتمالا بسیاری موارد دیگر که ما آنها را در نظر نگرفتیم. همینطور می‌توانید Hook هایی بسازید که کار کردن با آنها به سادگی ویژگی‌های آمادۀ ری اکت باشد.

سعی کنید از اضافه کردن مفاهیم در ابتدا خودداری کنید. اکنون که کامپوننت های تابعی می‌توانند کارهای بیشتری انجام دهند، احتمالا میانگین کامپوننت های تابعی کد شما طولانی‌تر خواهد شد. این طبیعی است، این احساس را نداشته باشید که باید فورا آنها را به Hook ها تبدیل کنید. اما شما را ترغیب می‌کنیم تا مواردی را پیدا کنید که در آنها استفاده از Hook های سفارشی می‌تواند یک منطق پیچیده را پشت یک رابط ساده پنهان کند و یا گره‌های یک کامپوننت شلوغ را باز کند.

برای مثال، شاید شما کامپوننتی داشته باشید که شامل حالت محلی زیادی باشد که به روشی ad-hoc مدیریت شده است. استفاده از useState ممکن است متمرکز کردن منطق را چندان راحت‌تر نکند، در نتیجه شاید شما نوشتن آن را به عنوان یک کاهش دهندۀ Redux ترجیح دهید:

function todosReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, {
        text: action.text,
        completed: false
      }];
    // ... other actions ...
    default:
      return state;
  }
}

کاهش دهنده‌ها (Reducers) جهت تست به صورت ایزوله مناسب هستند و همینطور جهت بیان کردن منطق آپدیت پیچیده. همچنین می‌توانید آنها را در کاهش دهنده‌هایی کوچکتر در صورت نیاز تقسیم کنید. هرچند ممکن است از مزیت‌های استفاده از حالت محلی ری اکت استفاده ببرید و یا نخواهید کتابخانۀ دیگری نصب کنید.

خب اگر می‌توانستیم یک هوک useReducer که به ما اجازه می‌دهد حالت محلی کامپوننت خود را با یک کاهش دهنده مدیریت کنیم چطور؟ یک ورژن سادۀ آن می‌توانید به این شکل باشد:

function useReducer(reducer, initialState) {
  const [state, setState] = useState(initialState);

  function dispatch(action) {
    const nextState = reducer(state, action);
    setState(nextState);
  }

  return [state, dispatch];
}

حال می‌توانیم از آن در کامپوننت خود استفاده کنیم و به کاهش دهنده نیز اجازه دهیم مدیریت حالت خود را انجام دهد:

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  function handleAddClick(text) {
    dispatch({ type: 'add', text });
  }

  // ...
}

نیاز به مدیریت حالت محلی با یک کاهش دهنده در داخل یک کامپوننت پیچیده، آنقدر رایج بود که یک هوک useReducer را درون ری اکت بسازیم. شما آن را همراه با Hook های دیگر از پیش ساخته شده در مرجع Hooks API پیدا خواهید کرد.

1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 5٫00 out of 5)
Loading...

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

counter customizable free hit