React Hook useEffect has a missing dependency: 'x'. Either include it or remove the dependency array.يقوم معظم المطورين بإسكات هذا الأمر من خلال تعليق تعطيل eslint. يعمل ذلك حتى يتسبب في حدوث خطأ في البيانات القديمة يستغرق يومًا للعثور عليه. عادةً ما يكون التحذير صحيحًا، كما يوجد حل مناسب لكل موقف.
📋 Table of Contents
- لماذا يوجد التحذير
- الإصلاح 1: فقط قم بإضافة التبعية
- الإصلاح 2: حلقات التأثير بعد إضافتها
- الإصلاح 3: تبعيات الوظيفة – استخدم رد الاتصال
- الإصلاح 4: أنت بحاجة إلى أحدث قيمة دون إعادة التشغيل
- الإصلاح 5: تحديث الحالة من قيمتها السابقة
- عندما يكون تعطيل القاعدة صحيحًا بالفعل
- هل تحتاج حتى إلى تأثير؟
- دليل القرار
- الأسئلة المتداولة
- الخلاصة
لماذا يوجد التحذير
تخبر مصفوفة التبعية React بموعد إعادة تشغيل التأثير. إذا قرأ التأثير قيمة غير مدرجة، فإن التأثير يحتفظ بالقيمة من التجسيد الذي تم تشغيله فيه آخر مرة – وهو إغلاق قديم. يبدو الرمز صحيحًا ويعمل بشكل غير صحيح.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser);
}, []); // ⚠️ missing dependency: 'userId'
return <div>{user?.name}</div>;
}
مع مصفوفة فارغة، يتم تشغيل الجلب مرة واحدة. انتقل من المستخدم 1 إلى المستخدم 2 وسيستمر المكون في إظهار المستخدم 1 إلى الأبد. لقد تنبأ التحذير بهذا بالضبط.
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser);
}, [userId]); // ✅ refetches whenever userId changes
الإصلاح 1: فقط قم بإضافة التبعية
في معظم الحالات، يكون التحذير صحيحًا، كما أن إضافة التبعية هو الحل الكامل. افعل ذلك أولاً ولا تحقق أكثر إلا إذا تسبب في حدوث حلقة.
الإصلاح 2: حلقات التأثير بعد إضافتها
غالبًا ما تؤدي إضافة كائن أو مصفوفة أو وظيفة إلى مصفوفة التبعية إلى حدوث حلقة لا نهائية، لأنه يتم إعادة إنشائها في كل عرض ومقارنتها حسب المرجع.
function Search({ filters }) {
const [results, setResults] = useState([]);
useEffect(() => {
search(filters).then(setResults);
}, [filters]); // new object identity every render -> infinite loop
}
تعتمد على القيم البدائية بدلاً من الكائن.
useEffect(() => {
search({ query, category }).then(setResults);
}, [query, category]); // strings compare by value — stable
عندما يأتي الكائن حقًا من أحد الوالدين، احفظه هناك.
// Parent
const filters = useMemo(
() => ({ query, category }),
[query, category]
);
return <Search filters={filters} />;
الإصلاح 3: تبعيات الوظيفة – استخدم رد الاتصال
الوظيفة المعلنة في النص المكون هي قيمة جديدة في كل عملية تصيير، لذا فإن إدراجها يعيد تشغيل التأثير إلى ما لا نهاية.
function Dashboard({ userId }) {
// Recreated every render.
const loadData = async () => {
const res = await fetch(`/api/data/${userId}`);
return res.json();
};
useEffect(() => {
loadData().then(setData);
}, [loadData]); // loops
}
اثنين من الإصلاحات الصحيحة. إذا تم استخدام الوظيفة فقط من خلال التأثير، فانقلها إلى الداخل – وهو الخيار الأنظف، لأن التبعية تختفي تمامًا.
useEffect(() => {
async function loadData() {
const res = await fetch(`/api/data/${userId}`);
setData(await res.json());
}
loadData();
}, [userId]); // ✅ only the primitive is a dependency
إذا كانت الوظيفة مشتركة مع أجزاء أخرى من المكون، فقم بتغليفها فيuseCallback.
const loadData = useCallback(async () => {
const res = await fetch(`/api/data/${userId}`);
return res.json();
}, [userId]);
useEffect(() => {
loadData().then(setData);
}, [loadData]); // ✅ identity only changes when userId changes
الإصلاح 4: أنت بحاجة إلى أحدث قيمة دون إعادة التشغيل
في بعض الأحيان، يجب تشغيل التأثير مرة واحدة، لكن رد الاتصال بالداخل يحتاج إلى القيم الحالية. يحتوي المرجع على قيمة قابلة للتغيير ولا تشارك في التبعيات.
function Chat({ onMessage }) {
const onMessageRef = useRef(onMessage);
// Keep the ref current on every render.
useEffect(() => {
onMessageRef.current = onMessage;
});
useEffect(() => {
const socket = new WebSocket('wss://example.com');
socket.onmessage = (e) => onMessageRef.current(e.data);
return () => socket.close();
}, []); // ✅ connects once, always calls the latest handler
}
هذا هو النمط الصحيح للاشتراكات والمؤقتات ومستمعي الأحداث حيث قد تكون إعادة الاتصال عند كل تغيير في الدعامة أمرًا خاطئًا.
الإصلاح 5: تحديث الحالة من قيمتها السابقة
حالة القراءة لحساب الحالة التالية تجعل تلك الحالة تبعية، والتي عادةً ما تسبب حلقة. يقوم نموذج التحديث بإزالة التبعية.
// Loops: count changes -> effect re-runs -> count changes...
useEffect(() => {
const id = setInterval(() => setCount(count + 1), 1000);
return () => clearInterval(id);
}, [count]);
// ✅ No dependency on count at all.
useEffect(() => {
const id = setInterval(() => setCount(c => c + 1), 1000);
return () => clearInterval(id);
}, []);
عندما يكون تعطيل القاعدة صحيحًا بالفعل
هناك حالة واحدة مشروعة: تأثير التثبيت الحقيقي فقط حيث تريد القيمة الأولية عمدًا ولا شيء آخر. التحليلات عند العرض الأول هي المثال القياسي.
useEffect(() => {
analytics.track('page_view', { page: pageName });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Intentional: fire once with the initial pageName.
إذا قمت بتعطيل القاعدة، فاكتب تعليقًا يوضح سبب الإغفال المتعمد. تعليق التعطيل العاري لا يمكن تمييزه عن أي شخص لم يفهم التحذير، بما في ذلك لنفسك خلال ستة أشهر.
هل تحتاج حتى إلى تأثير؟
تأتي نسبة كبيرة من التحذيرات الشاملة من تأثيرات لا ينبغي أن تكون موجودة. حالتان شائعتان:
الحالة المشتقة. حساب القيمة من الخاصيات أو الحالة لا يحتاج إلى تأثير.
// Unnecessary effect, extra render, and a dependency warning.
const [fullName, setFullName] = useState('');
useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]);
// ✅ Just calculate it during render.
const fullName = `${first} ${last}`;
الرد على حدث ما. غالبًا ما ينتهي المنطق الذي ينتمي إلى معالج الأحداث في حالة مراقبة التأثير التي حددها المعالج.
// ✅ Do the work where the event happens.
function handleSubmit() {
submitForm(data);
analytics.track('form_submitted');
}
التأثيرات مخصصة للمزامنة مع أنظمة خارج React — الشبكة، والاشتراكات، وDOM، والمؤقتات. إذا لم يكن هناك أي شيء خارجي، فمن المحتمل أن يكون التأثير هو الأداة الخاطئة.
دليل القرار
| الوضع | اصلاح |
|---|---|
| تحذير أسماء بدائية | أضفه إلى المصفوفة |
| كائن أو صفيف يسبب حلقة | تعتمد على حقولها البدائية، أوuseMemo في المصدر |
| تؤدي الدالة إلى حدوث حلقة | حركه داخل التأثير أوuseCallback |
| تحتاج إلى أحدث قيمة، ولا يجب إعادة تشغيل | قم بتخزينه في المرجع |
| الحالة مشتقة من الحالة السابقة | استخدم نموذج وظيفة التحديث |
| يتم حساب القيمة من الدعائم أو الحالة | احذف التأثير واحسب أثناء العرض |
| حقيقي للتركيب فقط | تعطيل القاعدة بتبرير مكتوب |
الأسئلة المتداولة
س: لماذا يتم تشغيل تأثيري مرتين أثناء التطوير؟
ج: يقوم الوضع الصارم بتركيب المكونات وإلغاء تحميلها وإعادة تركيبها عمدًا على سطح وظائف التنظيف المفقودة. لا يحدث في الإنتاج. إذا أدى التشغيل المزدوج إلى كسر شيء ما، فإن التأثير الخاص بك يفتقد عملية التنظيف.
س: هل يمكنني إيقاف تشغيل قاعدة التخفيضات الشاملة؟
ج: يمكنك ذلك، وسوف تقوم بشحن أخطاء الإغلاق التي لا معنى لها والتي يصعب للغاية تتبعها. القاعدة تكتشف العيوب الحقيقية؛ استمر في ذلك وأصلح الأسباب.
س: هل useCallback في كل مكان يضر بالأداء؟
ج: قليلًا، فهو يكلف الذاكرة وعمل المقارنة. استخدمها عندما يكون استقرار الهوية مهمًا بالفعل، مثل صفائف التبعية والأبناء المحفوظة، وليس بشكل انعكاسي في كل وظيفة.
س: ماذا عن مترجم React؟
ج: يقوم بأتمتة الكثير من عمليات الحفظ التيuseCallback وuseMemo افعل ذلك يدويًا، مما يؤدي إلى إزالة فئة من هذه التحذيرات. إنه لا يلغي الحاجة إلى فهم القيم التي يعتمد عليها تأثيرك فعليًا.
س: يحتاج التأثير الخاص بي إلى قيمة ولكن لا ينبغي إعادة تشغيله عندما يتغير. هل المرجع اختراق؟
ج: لا، هذا هو النمط الموثق لتلك الحالة. الحفاظ على تحديث المرجع في تأثير منفصل وقراءة.current داخل التأثير طويل الأمد اصطلاحي.
الخلاصة
يعد التحذير الشامل بمثابة كاشف للأخطاء، وليس مصدر إزعاج. أضف التبعية أولاً؛ إذا تكرر ذلك، قم بإصلاح مشكلة الهوية مع العناصر الأولية،useMemoأوuseCallback; استخدم مرجعًا عندما تحتاج إلى القيم الحالية دون إعادة التشغيل؛ استخدم نموذج تحديث الحالة لكسر الحلقات المرجعية الذاتية؛ وحذف التأثير بالكامل عندما تكون القيمة مشتقة فقط. احتفظ بميزة eslint-disable للحصول على تأثيرات التثبيت الحقيقية فقط، واترك دائمًا تعليقًا يوضح السبب.
🔗 Share this article
✍️ Leave a Comment