Synchronizing with External Systems: مدل ذهنی درست Effectها

در دو درس قبل، با نحو useEffect، Dependency Array و Cleanup آشنا شدیم. در این درس، یک قدم به عقب برمی‌داریم و به مدل ذهنی درست‌تری برای Effectها نگاه می‌کنیم: Effectها ابزاری برای همگام‌سازی (Synchronization) با سیستم‌های خارج از React هستند، نه یک جایگزین کلی برای «هر کاری که بعد از رندر باید انجام شود».

Effect یک Event Handler نیست

یکی از رایج‌ترین اشتباهات ذهنی تازه‌کارها، استفاده از Effect برای واکنش به یک عمل خاص کاربر است؛ کاری که در واقع باید در یک Event Handler انجام شود. برای مثال:

// نادرست: استفاده از Effect برای واکنش به یک کلیک
function Form() {
  const [submitted, setSubmitted] = useState(false);

  useEffect(() => {
    if (submitted) {
      sendAnalyticsEvent("فرم ارسال شد");
    }
  }, [submitted]);

  function handleSubmit() {
    setSubmitted(true);
  }
}

در این کد، ارسال رویداد آماری، در واقع پاسخ مستقیم به یک عمل کاربر (کلیک روی دکمه ارسال) است؛ پس باید مستقیماً داخل همان Event Handler نوشته شود، نه به‌صورت غیرمستقیم از طریق یک Effect که به تغییر State واکنش نشان می‌دهد:

// درست: انجام مستقیم در Event Handler
function Form() {
  function handleSubmit() {
    setSubmitted(true);
    sendAnalyticsEvent("فرم ارسال شد");
  }
}

قاعده کلی این است: اگر یک کد باید دقیقاً به‌خاطر یک تعامل خاص کاربر (کلیک، تایپ، ارسال فرم) اجرا شود، جایش در Event Handler است. اگر باید هر بار که کامپوننت با یک مقدار خاص روی صفحه است اجرا شود (صرف‌نظر از این‌که چه تعاملی باعث آن مقدار شده)، جایش در Effect است.

Effect یعنی «تا این مقدار این‌جاست، این کار انجام شود»

یک مثال خوب برای این مدل ذهنی، اتصال به یک اتاق چت است: تا وقتی کاربر داخل یک اتاق خاص است، باید به آن اتاق متصل باشیم؛ و به‌محض تغییر اتاق یا خروج از صفحه، باید اتصال قبلی قطع شود:

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    return () => {
      connection.disconnect();
    };
  }, [roomId]);

  return <p>اتصال به اتاق: {roomId}</p>;
}

در این مثال، Effect دقیقاً بیان می‌کند: «تا وقتی roomId این مقدار است، باید به همین اتاق متصل باشیم». این جمله، یک توصیف از وضعیت پایدار برنامه است، نه واکنش به یک رویداد خاص؛ درست همان‌طور که JSX توصیفی از ظاهر رابط کاربری بر اساس داده فعلی است.

هر Effect باید یک هدف هم‌گام‌سازی مستقل داشته باشد

اگر یک کامپوننت چند کار مستقل از هم را با یکدیگر همگام می‌کند (مثلاً هم عنوان صفحه را تنظیم می‌کند و هم به یک سرویس چت متصل می‌شود)، بهتر است هرکدام در یک useEffect جداگانه نوشته شوند، نه در یک Effect بزرگ و ترکیبی:

useEffect(() => {
  document.title = `اتاق: ${roomId}`;
}, [roomId]);

useEffect(() => {
  const connection = createConnection(roomId);
  connection.connect();
  return () => connection.disconnect();
}, [roomId]);

این تفکیک باعث می‌شود هر Effect، مسئولیت مشخص و مستقل خودش را داشته باشد؛ دقیقاً همان اصلی که در فصل دوم، درباره مسئولیت واحد کامپوننت‌ها هم دیدیم.

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

Effect به‌عنوان همگام‌سازی، نه واکنش به رویداد
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

Effect ابزاری برای همگام‌سازی کامپوننت با یک سیستم خارجی است، بر اساس این‌که کامپوننت در حال حاضر با چه مقادیری روی صفحه است؛ نه ابزاری برای واکنش به یک رویداد خاص، که جای آن در Event Handler است. هر Effect بهتر است یک هدف همگام‌سازی مستقل داشته باشد. در درس بعدی به چرخه اجرای دقیق Effect در طول Mount، Update و Unmount کامپوننت می‌پردازیم.