Composition در Custom Hookها

در درس‌های قبل، Custom Hookهای کوچک و مستقلی مثل useToggle و useWindowWidth ساختیم. قدرت واقعی Hookها وقتی دیده می‌شود که آن‌ها را مثل قطعات لگو کنار هم بگذاریم. چون Custom Hook در نهایت یک تابع است که Hook فراخوانی می‌کند، هیچ مانعی وجود ندارد که یک Custom Hook از Custom Hookهای دیگر هم استفاده کند.

یک Hook داخل Hook دیگر

قانون Hookها که در فصل پنجم دیدیم می‌گوید Hookها را فقط باید از داخل کامپوننت‌ها یا Custom Hookها صدا زد. پس صدا زدن یک Custom Hook از داخل Custom Hook دیگر کاملاً مجاز است:

function useIsMobile() {
  const width = useWindowWidth();
  return width < 768;
}

در اینجا useIsMobile خودش هیچ Effect یا رویدادی ندارد؛ فقط خروجی useWindowWidth را می‌گیرد و یک تصمیم ساده روی آن می‌سازد. کامپوننتی که از useIsMobile استفاده می‌کند، لازم نیست چیزی درباره عرض پنجره یا رویداد resize بداند.

خروجی یک Hook، ورودی Hook دیگر

الگوی رایج دیگر این است که مقدار برگشتی یک Hook به‌عنوان پارامتر Hook بعدی استفاده شود. برای مثال، در یک فیلد جست‌وجو می‌خواهیم مقدار تایپ‌شده با کمی تأخیر برای فیلتر استفاده شود تا با هر کاراکتر، کار سنگینی اجرا نشود:

function useDebouncedValue(value, delay) {
  const [debounced, setDebounced] = useState(value);

  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);

  return debounced;
}

function useSearch(items) {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebouncedValue(query, 300);

  const results = items.filter((item) => item.includes(debouncedQuery));

  return { query, setQuery, results };
}

useSearch از دو Hook استفاده می‌کند: useState برای متن فیلد و useDebouncedValue برای ساختن نسخه با تأخیر همان متن. هر Hook فقط یک مسئولیت دارد و ترکیبشان، یک قابلیت کامل می‌سازد. همچنین useDebouncedValue را می‌توان در جاهای دیگر برنامه هم جداگانه استفاده کرد.

هر فراخوانی، State مستقل خودش را دارد

همان اصلی که در درس اول این فصل دیدیم در ترکیب Hookها هم برقرار است: اگر useSearch را در دو کامپوننت مختلف صدا بزنید، هرکدام query و debouncedQuery جداگانه‌ای دارند. Composition منطق را به اشتراک می‌گذارد، نه داده را.

با هر رندر دوباره اجرا می‌شود

Custom Hookها بخشی از بدنه کامپوننت هستند. یعنی با هر رندر، همه Hookهای تودرتو دوباره از اول تا آخر و به همان ترتیب صدا زده می‌شوند. این همان مدل ذهنی است که در فصل پنجم درباره Hookها ساختیم و ترکیب آن‌ها چیزی را تغییر نمی‌دهد.

اصول ترکیب خوب

  • هر Hook یک کار مشخص انجام دهد؛ Hookهای بزرگ و چندمنظوره را به چند Hook کوچک‌تر بشکنید.
  • وابستگی‌ها روشن باشند: اگر Hook دوم به مقدار Hook اول نیاز دارد، آن را به‌صورت پارامتر بگیرد، نه اینکه پنهانی از جای دیگر بخواند.
  • لایه‌های تودرتو را بی‌دلیل عمیق نکنید؛ اگر ردیابی اینکه یک مقدار از کجا آمده سخت شد، ساختار بیش از حد پیچیده شده است.

مثال قابل اجرا

در این مثال، useSearch از useDebouncedValue استفاده می‌کند. در فیلد تایپ کنید و ببینید فیلتر لیست با کمی تأخیر اعمال می‌شود.

ترکیب useDebouncedValue در useSearch
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

Custom Hookها می‌توانند یکدیگر را صدا بزنند و خروجی یکی می‌تواند ورودی دیگری باشد. با ترکیب Hookهای کوچک و متمرکز، منطق‌های بزرگ‌تر ساخته می‌شود، بدون اینکه State مشترک شود و بدون اینکه قوانین Hookها تغییر کند. در درس بعدی به مدیریت State و Effect در داخل Custom Hookها می‌پردازیم.