زمانی که یک Effect مینویسید، ابزار Lint صحت آرایه وابستگیهای شما را بررسی میکند تا مطمئن شود تمام مقادیر واکنشپذیر (props یا state) که درون افکت خوانده شدهاند، در آرایه ذکر شده باشند. این هماهنگی تضمین میکند که افکت همیشه با آخرین دادهها همگام بماند.
اما مشکل از جایی شروع میشود که وابستگیهای غیرضروری باعث میشوند افکت شما بیش از حد اجرا شود، کارایی برنامه افت کند یا حتی پروژه در حلقههای بینهایت (Infinite Loops) گرفتار شود.
چطور حلقههای بینهایت ناشی از وابستگیها را بشکنیم؟
چطور بدون واکنش نشان دادن به یک استیت، آخرین مقدار آن را درون افکت بخوانیم؟
چرا اشیاء (Objects) و توابع (Functions) وابستگیهای خطرناکی هستند و چطور آنها را حذف کنیم؟
راهحلهای اصولی به جای خاموش کردن ابزار Linter چیست؟
بسیاری از برنامهنویسان تصور میکنند آرایه وابستگی دست خودشان است. اما در واقعیت، آرایه وابستگیها آینهای از کدهای درون افکت شماست. اگر میخواهید متغیری را از آرایه وابستگی حذف کنید، نمیتوانید صرفاً نام آن را پاک کنید؛ بلکه باید به ریآکت اثبات کنید که آن متغیر واکنشپذیر نیست.
به این تغییر ساختارها نگاه کنید:
function ChatRoom({ roomId }) { // 🔴 واکنشپذیر
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ الزاماً باید اینجا باشد
}
const serverUrl = 'https://localhost:1234';
const roomId = 'music'; // 🟢 دیگر واکنشپذیر نیست (ثابت است)
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ حالا آرایه خالی کاملاً درست است
}
بیایید چند تکنیک کاربردی برای اصلاح و حذف وابستگیهای ناخواسته را بررسی کنیم:
به این افکت نگاه کنید که با آمدن هر پیام جدید، آن را به لیست پیامهای قبلی اضافه میکند:
// ❌ ساختار نامناسب: ایجاد حلقه و ریمونت شدن چت با هر پیام
useEffect(() => {
const connection = createConnection();
connection.on('message', (receivedMessage) => {
setMessages([...messages, receivedMessage]); // خواندن خود استیت در افکت
});
return () => connection.disconnect();
}, [roomId, messages]); // messages باعث اجرای مجدد افکت میشود!
راهحل: به جای خواندن مستقیم استیت، از Updater Function در سِتاِستیت استفاده کنید تا وابستگی به کل حذف شود:
// ✅ ساختار بهینه: بدون وابستگی به messages
useEffect(() => {
const connection = createConnection();
connection.on('message', (receivedMessage) => {
setMessages(msgs => [...msgs, receivedMessage]); // ریآکت دیتای قبلی را خودش تزریق میکند
});
return () => connection.disconnect();
}, [roomId]); // چت دیگر با آمدن پیام جدید قطع و وصل نمیشود!
فرض کنید میخواهید موقع آمدن پیام جدید صدایی پخش شود، مگراینکه کاربر حالت بیصدا (isMuted) را فعال کرده باشد:
// ❌ ساختار نامناسب: با هر بار لغزش دکمه Mute، اتصال چت قطع و وصل میشود!
useEffect(() => {
const connection = createConnection();
connection.on('message', (receivedMessage) => {
if (!isMuted) playSound();
});
return () => connection.disconnect();
}, [roomId, isMuted]);
راهحل: بخش مربوط به صدا را به کمک useEffectEvent به یک منطق غیرواکنشپذیر تبدیل کنید:
// ✅ ساختار بهینه: استفاده از Effect Event
const onMessage = useEffectEvent(receivedMessage => {
setMessages(msgs => [...msgs, receivedMessage]);
if (!isMuted) playSound(); // همیشه آخرین مقدار را بدون ایجاد وابستگی میخواند
});
useEffect(() => {
const connection = createConnection();
connection.on('message', (receivedMessage) => {
onMessage(receivedMessage);
});
return () => connection.disconnect();
}, [roomId]); // دیگر به isMuted وابسته نیست
در جاوااسکریپت، اشیاء و توابع جدیدی که در هر رندر ساخته میشوند، حتی اگر ظاهر و دیتای کاملاً یکسانی داشته باشند، آدرس متفاوتی در حافظه میگیرند (Object.is(obj1, obj2) === false).
به این کد مخرب نگاه کنید:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
// 🚩 با هر بار تایپ کاربر در اینپوت، این شیء دوباره از نو در حافظه ساخته میشود!
const options = { serverUrl: serverUrl, roomId: roomId };
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ❌ با هر تک کلید در اینپوت، چت قطع و وصل میشود!
}
انتقال به بیرون از کامپوننت: اگر شیء یا تابع نیازی به خواندن پروپها و استیتها ندارد، آن را کاملاً به خارج از بدنه کامپوننت منتقل کنید.
انتقال به درون افکت (بهترین راهکار): اگر شیء شما به متغیری مثل roomId نیاز دارد، ساختار آن را دقیقاً به داخل بدنه افکت ببرید:
// ✅ ساختار اصلاحشده و پایدار
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
// تعریف شیء در داخل افکت، آن را از آرایه وابستگیها حذف میکند
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // 🟢 roomId یک String است و ریآکت مقدار آن را مقایسه میکند، نه آدرس حافظه را!
}
همین فرمول دقیقاً برای توابع نیز صادق است؛ اگر تابعی درون افکت صدا زده میشود، تعریف تابع را به داخل useEffect هدایت کنید تا وابستگی به آن تابع حذف شود و فقط متغیرهای ابتدایی (مثل رشتهها و اعداد) به عنوان وابستگی باقی بمانند.
آرایه وابستگی انتخاب شما نیست، بلکه بازتابی از کدهای درون افکت شماست.
برای حذف یک وابستگی، باید کد را طوری تغییر دهید که آن متغیر دیگر برای افکت «واکنشپذیر» نباشد.
استفاده از تابع بهروزرسانی استیت (setCount(c => c + 1)) به شما کمک میکند بدون خواندن خودِ استیت، مقدار آن را آپدیت کنید.
به کمک useEffectEvent میتوانید بدون ریمونت کردن افکت، آخرین مقادیر استیتها یا پروپهای کالبک را بخوانید.
از قرار دادن مستقیم اشیاء و توابع در آرایه وابستگی خودداری کنید؛ آنها را به داخل افکت منتقل کنید تا مقایسهها بر اساس مقادیر اولیه (Primitives) انجام شود.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn