مقدمه

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

برای کنترل دقیق زمان اجرای یک افکت، React به ما اجازه می‌دهد تا یک «آرایه وابستگی‌ها» (Dependency Array) را به عنوان آرگومان دوم به useEffect پاس دهیم.

آرایه وابستگی‌ها (Dependency Array)

آرایه وابستگی‌ها به React می‌گوید که افکت شما به کدام مقادیر (props یا state) وابسته است. React تنها زمانی افکت را دوباره اجرا می‌کند که یکی از مقادیر موجود در این آرایه، بین دو رندر تغییر کرده باشد. این کار به ما اجازه می‌دهد تا از اجرای افکت در رندرهای غیرمرتبط صرف‌نظر کنیم.

۱. اجرای افکت تنها یک بار (هنگام Mount)

اگر یک آرایه خالی را به عنوان آرایه وابستگی‌ها پاس دهید، افکت شما تنها یک بار، درست پس از اولین رندر (زمانی که کامپوننت برای اولین بار به DOM اضافه یا mount می‌شود)، اجرا خواهد شد. این الگو معادل متد چرخه حیات componentDidMount در کامپوننت‌های کلاس‌محور است و برای کارهایی مانند دریافت داده‌های اولیه از سرور ایده‌آل است.

Copy Icon JAVASCRIPT (React)
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    console.log('Fetching user data...');
    fetch(`https://api.example.com/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, []); // <-- Empty dependency array

  if (!user) {
    return <p>Loading...</p>;
  }

  return <h1>Hello, {user.name}</h1>;
}

در این مثال، افکت دریافت داده تنها یک بار پس از رندر اولیه اجرا می‌شود. حتی اگر کامپوننت UserProfile به دلایل دیگری (مثلاً تغییر state در کامپوننت والد) دوباره رندر شود، این افکت دیگر اجرا نخواهد شد، زیرا آرایه وابستگی‌های آن خالی است و هرگز تغییر نمی‌کند.

۲. اجرای افکت در پاسخ به تغییرات خاص

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

Copy Icon JAVASCRIPT (React)
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    console.log(`Fetching data for user: ${userId}`);
    fetch(`https://api.example.com/users/${userId}`)
      .then(res => res.json())
      .then(data => setUser(data));
  }, [userId]); // <-- Effect depends on userId

  // ... return JSX
}

در این نسخه بهبودیافته، ما userId را به آرایه وابستگی‌ها اضافه کرده‌ایم. اکنون، افکت ما به صورت هوشمند عمل می‌کند:

  • پس از رندر اولیه اجرا می‌شود تا داده‌های کاربر اولیه را دریافت کند.
  • اگر کامپوننت با همان userId قبلی دوباره رندر شود، افکت اجرا نمی‌شود.
  • تنها زمانی که prop مربوط به userId تغییر کند (مثلاً کاربر در UI یک پروفایل دیگر را انتخاب کند)، افکت دوباره اجرا شده و داده‌های کاربر جدید را دریافت می‌کند.

یک قانون مهم: شما باید تمام مقادیر props و state را که در داخل تابع افکت خود استفاده می‌کنید، در آرایه وابستگی‌ها لیست کنید. ابزارهای linter مدرن معمولاً به صورت خودکار این قانون را بررسی کرده و در صورت فراموشی، به شما هشدار می‌دهند.

در این درس، با آرایه وابستگی‌ها به عنوان ابزار کلیدی برای کنترل زمان اجرای افکت‌ها در useEffect آشنا شدیم. دیدیم که چگونه می‌توان با پاس دادن یک آرایه خالی، افکت را تنها یک بار اجرا کرد و چگونه با مشخص کردن وابستگی‌ها، آن را تنها در پاسخ به تغییرات خاص اجرا نمود. این مهارت برای نوشتن کدهای React بهینه و کارآمد ضروری است. در درس بعدی، به «اجرای عملیات هنگام mount و unmount» خواهیم پرداخت و با فاز پاک‌سازی (cleanup) در useEffect آشنا خواهیم شد.