useImperativeHandle: کنترل دقیق آنچه از طریق Ref در دسترس قرار می‌گیرد

در درس قبلی دیدیم چگونه یک ref را به یک کامپوننت بدهیم تا به عنصر DOM داخلی آن دسترسی پیدا کنیم. در آن مثال‌ها، کل عنصر DOM (مثلاً کل input) در اختیار بیرون قرار می‌گرفت. اما گاهی می‌خواهیم کنترل دقیق‌تری داشته باشیم و فقط بخشی مشخص از قابلیت‌های یک کامپوننت را از طریق Ref در دسترس بگذاریم، نه کل عنصر DOM را. ابزار این کار، useImperativeHandle است.

مشکل: دسترسی کامل به DOM، بیش از حد نیاز است

فرض کنید یک کامپوننت SearchField داریم که علاوه بر یک input، منطق داخلی دیگری هم دارد (مثلاً پاک کردن خودکار مقدار پس از مدتی). اگر مستقیماً کل عنصر input را از طریق ref در دسترس بگذاریم، کد بیرونی می‌تواند هر متد DOM دلخواهی (مانند تغییر مستقیم value) را روی آن اجرا کند؛ حتی کارهایی که ممکن است با منطق داخلی کامپوننت ناهماهنگ شوند.

راه‌حل: نمایش یک واسط محدود و مشخص

useImperativeHandle به یک کامپوننت اجازه می‌دهد به‌جای نمایش کامل عنصر DOM داخلی‌اش، فقط یک آبجکت سفارشی و محدود، شامل توابعی که خودش تعیین می‌کند، در اختیار ref بیرونی قرار دهد:

function SearchField({ ref }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      clear() {
        inputRef.current.value = "";
      },
    };
  });

  return <input ref={inputRef} />;
}

در این کد، وقتی کامپوننت بیرونی از ref.current.focus() یا ref.current.clear() استفاده می‌کند، دقیقاً همان دو متدی که SearchField خودش تعریف کرده اجرا می‌شود؛ اما کد بیرونی هیچ راه مستقیمی برای دسترسی به کل عنصر input (مثلاً برای خواندن value به شکل دلخواه) ندارد.

استفاده از این واسط سفارشی

function App() {
  const searchRef = useRef(null);

  return (
    <div>
      <SearchField ref={searchRef} />
      <button onClick={() => searchRef.current.focus()}>فوکوس</button>
      <button onClick={() => searchRef.current.clear()}>پاک کردن</button>
    </div>
  );
}

چرا به این الگو Imperative گفته می‌شود

همان‌طور که در فصل اول دیدیم، مدل اصلی React توصیفی (Declarative) است: رابط کاربری بر اساس داده توصیف می‌شود. اما فراخوانی متدهایی مانند focus() یا clear() از بیرون یک کامپوننت، یک عمل دستوری (Imperative) است: «همین الان این کار مشخص را انجام بده». useImperativeHandle دقیقاً برای همین موارد استثنایی طراحی شده که انجام کاری به شکل توصیفی (مثلاً از طریق Props و State) طبیعی یا ممکن نیست.

این الگو باید استثنا باشد، نه قاعده

استفاده بیش‌ازحد از useImperativeHandle می‌تواند به تدریج کامپوننت‌ها را به سمت رفتار دستوری، شبیه به کدهای قدیمی پیش از React، سوق دهد. قبل از استفاده از آن، بهتر است بررسی شود که آیا همان نیاز، با ارسال یک Prop یا مدیریت State در سطح والد، به شکل توصیفی‌تری قابل حل است یا نه. موارد رایج و منطقی برای این Hook، معمولاً محدود به کارهایی مانند فوکوس کردن، اسکرول کردن یا پخش/توقف یک رسانه است.

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

نمایش یک واسط محدود با useImperativeHandle
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

useImperativeHandle به یک کامپوننت اجازه می‌دهد به‌جای نمایش کامل عنصر DOM داخلی خودش از طریق Ref، یک آبجکت سفارشی و محدود از توابع مشخص را در اختیار بیرون قرار دهد. این ابزار برای موقعیت‌های استثنایی که نیاز به عمل دستوری دارند مناسب است و نباید جایگزین رویکرد معمول توصیفی React شود. در درس بعدی به الگوهای رایج استفاده از Ref در برنامه‌های واقعی می‌پردازیم.