useInsertionEffect: Hookی برای نویسندگان کتابخانه‌های استایل

سومین API مدرن این فصل، useInsertionEffect است. این Hook از بقیه Hookهای این فصل متفاوت است: مخاطب اصلی آن نویسندگان کتابخانه‌هاست (به‌خصوص کتابخانه‌های CSS-in-JS)، نه برنامه‌نویسی که صفحه و فرم می‌سازد. دیدن آن کمک می‌کند ترتیب دقیق اجرای Effectها در Commit Phase را بهتر بفهمیم، و بدانیم چرا بیشتر ما هرگز مجبور نمی‌شویم مستقیم از آن استفاده کنیم.

یادآوری: Effect بعد از Commit اجرا می‌شود

در فصل ششم و یازدهم دیدیم که useEffect پس از اعمال تغییرات روی DOM اجرا می‌شود. React یک Hook دیگر به نام useLayoutEffect هم دارد که آن هم بعد از تغییر DOM اجرا می‌شود، اما پیش از اینکه مرورگر صفحه را روی صفحه نمایش بکشد (Paint). useInsertionEffect حلقه سوم این زنجیره است و زودتر از هر دو اجرا می‌شود.

ترتیب کامل اجرا در یک به‌روزرسانی

html
۱. Render Phase: تابع کامپوننت اجرا می‌شود
۲. useInsertionEffect: پیش از هر تغییری در DOM
۳. React تغییرات را روی DOM اعمال می‌کند (Commit)
۴. useLayoutEffect: پس از تغییر DOM، پیش از Paint
۵. مرورگر صفحه را رسم می‌کند (Paint)
۶. useEffect: معمولاً پس از Paint

نکته اصلی این است که useInsertionEffect در لحظه‌ای اجرا می‌شود که هنوز DOM تغییر نکرده است. همین ویژگی، دلیل وجود آن است.

مشکلی که این Hook حل می‌کند

کتابخانه‌های CSS-in-JS (که استایل را از داخل جاوااسکریپت و بر اساس Props تولید می‌کنند) باید تگ style مناسب را در head صفحه قرار دهند. انتخاب زمان این کار مشکل‌ساز است:

  • داخل Render: React ممکن است یک Render را چند بار اجرا یا دور بریزد (فصل بیست‌ودوم). تزریق استایل در حین Render، یعنی تغییر یک چیز بیرونی در Render Phase، که طبق چیزی که در فصل یازدهم دیدیم نباید انجام شود.
  • داخل useLayoutEffect: این Effect پس از تغییر DOM اجرا می‌شود. در آن لحظه عناصر جدید از قبل در صفحه هستند، اما استایل‌شان هنوز تزریق نشده. مرورگر ممکن است ابتدا Layout را بدون آن استایل محاسبه کند و بعد، پس از رسیدن استایل، دوباره محاسبه کند.

useInsertionEffect این دو مشکل را با هم حل می‌کند: خارج از Render Phase اجرا می‌شود، اما پیش از تغییر DOM، پس وقتی عناصر جدید به صفحه می‌رسند استایل‌شان آماده است.

نحو و محدودیت‌ها

jsx
import { useInsertionEffect } from "react";

function useStyle(css) {
  useInsertionEffect(() => {
    const el = document.createElement("style");
    el.textContent = css;
    document.head.appendChild(el);
    return () => el.remove();
  }, [css]);
}

نحو آن مثل useEffect است: یک تابع، یک Dependency Array اختیاری، و امکان برگرداندن Cleanup. اما چند محدودیت مهم دارد:

  • داخل آن نمی‌توان State را به‌روزرسانی کرد (فراخوانی set مجاز نیست).
  • در آن لحظه Refها هنوز به عناصر DOM وصل نشده‌اند، پس ref.current قابل اتکا نیست.
  • روی سرور اجرا نمی‌شود؛ فقط در مرورگر.

آیا باید از آن استفاده کنم؟

تقریباً هرگز در کد معمولی برنامه. مستندات رسمی React صراحتاً آن را برای نویسندگان کتابخانه‌های CSS-in-JS معرفی می‌کند. اگر در پروژه‌تان از یک کتابخانه استایل مبتنی بر جاوااسکریپت استفاده می‌کنید، آن کتابخانه خودش پشت صحنه این Hook را صدا می‌زند. برای تغییر استایل بر اساس State در برنامه‌های عادی، همان روش‌های معمول (className، متغیرهای CSS یا استایل‌های مرسوم) کافی و ساده‌تر است.

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

در این مثال، یک Hook ساده استایل را در head تزریق می‌کند. جعبه با اولین نمایش، همان لحظه استایل‌دار دیده می‌شود. این فقط نمونه‌ای از الگوی کتابخانه‌هاست، نه الگویی برای کد معمولی برنامه.

تزریق استایل با useInsertionEffect
import { useInsertionEffect } from "react";

function useStyle(css) {
  useInsertionEffect(() => {
    const el = document.createElement("style");
    el.textContent = css;
    document.head.appendChild(el);
    return () => el.remove();
  }, [css]);
}

function App() {
  useStyle(".demo-box { padding: 12px; border: 2px solid teal; border-radius: 8px; }");

  return <div className="demo-box">این کادر با استایل تزریق‌شده نمایش داده می‌شود.</div>;
}
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

useInsertionEffect پیش از هر تغییر در DOM اجرا می‌شود و برای کتابخانه‌های CSS-in-JS ساخته شده تا استایل را در لحظه مناسب تزریق کنند، بدون اینکه در Render Phase چیزی بیرونی را تغییر دهند و بدون اینکه مرورگر مجبور به محاسبه دوباره Layout شود. در آن نمی‌توان State را تغییر داد و Refها هنوز آماده نیستند. ترتیب کلی این است: Render، سپس useInsertionEffect، سپس تغییر DOM، سپس useLayoutEffect، سپس Paint و در نهایت useEffect. در کد معمولی برنامه، به این Hook نیازی نیست. در درس بعدی، به useImperativeHandle برمی‌گردیم و آن را در جایگاه APIهای مدرن مرور می‌کنیم.