Menu
flag Ar iconالعربيةdown icon

الخطاف useEffect في React: الاعتماديات والتنظيف وأمثلة

يشغّل useEffect الكود بعد أن تحدّث React الشاشة، ليتزامن المكوّن مع شيء خارج React: مؤقت، أو اشتراك، أو مستمع أحداث. تعلّم الأشكال الثلاثة لمصفوفة الاعتماديات، ودالة التنظيف، والحلقات اللانهائية، ومتى لا تحتاج إلى تأثير أصلًا.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

يشغّل useEffect قطعة من الكود بعد أن تعرض React مكوّنك وتحدّث الشاشة. تستخدمه لإبقاء المكوّن متزامنًا مع شيء خارج React، مثل مؤقت أو اتصال شبكي أو مستمع أحداث، وتتحكم في متى يعمل من جديد بمصفوفة الاعتماديات.

افتح وحدة التحكم أسفل المعاينة: يسجّل التأثير مرة عندما يظهر المكوّن، ثم مرة أخرى بعد كل نقرة. غيّر [count] إلى [] وانقر مرة أخرى: يبقى التأثير صامتًا بعد السجل الأول.

الصيغة

useEffect(() => {
    // setup: runs after the render is on the screen
    return () => {
        // cleanup: runs before the next setup, and on unmount
    };
}, [dependencies]);
  • الوسيط الأول هو دالة الإعداد. يمكنها أن تُرجع دالة تنظيف، أو لا شيء.
  • الوسيط الثاني هو مصفوفة الاعتماديات: كل قيمة من المكوّن تقرؤها دالة الإعداد (props والحالة وأي شيء محسوب منهما).
  • استدعِ useEffect في المستوى الأعلى من المكوّن، مثل كل خطاف.

التأثيرات تعمل بعد العرض

العرض هو استدعاء React لدالة مكوّنك لتعرف ما يجب أن تعرضه الشاشة. تنتظر التأثيرات حتى ينتهي ذلك العمل ويتحدث DOM، فتكون الصفحة داخل التأثير تعرض القيم الجديدة بالفعل.

كل نقرة تسجّل العرض أولًا والتأثير ثانيًا، ويقرأ التأثير نص الزر الذي كتبته React للتو. لهذا تنتمي قراءة DOM أو بدء مؤقت أو الاتصال بخادم إلى تأثير لا إلى جسم المكوّن: يعمل الجسم أثناء العرض، قبل أن يكون أي شيء على الشاشة، وقد تستدعيه React أكثر مما تتوقع.

تعمل التأثيرات عادة بعد أن يرسم المتصفح الشاشة. وعندما يجب أن يقيس التأثير DOM قبل الرسم، استخدم useLayoutEffect بدلًا منه، الذي يعمل دائمًا قبل الرسم.

مصفوفة الاعتماديات: ثلاثة أشكال

تقرر مصفوفة الاعتماديات متى يعمل التأثير من جديد. هناك ثلاثة أشكال، وتستخدمها هذه الكتلة كلها لتراقب الفرق.

عند التحميل تسجّل التأثيرات الثلاثة. انقر a + 1 فيعمل اثنان منها: الذي بلا مصفوفة والذي يعتمد على a. وانقر b + 1 فلا يعمل إلا الذي بلا مصفوفة، لأن شيئًا في [] أو [a] لم يتغير.

مصفوفة الاعتمادياتمتى يعمل التأثير
لا شيءبعد كل عرض
[]مرة واحدة، بعد العرض الأول (عند التركيب)
[a, b]بعد العرض الأول، ثم كلما تغيّرت a أو b

تقارن React كل اعتمادية بقيمتها السابقة باستخدام Object.is. تُقارن الأرقام والنصوص والقيم المنطقية بالقيمة. أما الكائنات والمصفوفات والدوال فتُقارن بالهوية، وهذا مهم جدًا (انظر أدناه).

لا تحذف قيمة من المصفوفة لتجعل التأثير يعمل أقل. سيظل التأثير يستخدم القيمة من عرض قديم. تلتقط قاعدة الفحص react-hooks/exhaustive-deps هذا الخطأ؛ وهي جزء من إعداد ESLint الذي يتضمنه قالب React في Vite وإعداد ESLint في Next.js.

التنظيف

تبدأ تأثيرات كثيرة شيئًا يجب إيقافه: اتصالًا، أو اشتراكًا، أو مؤقتًا. أرجع دالة من التأثير فتستدعيها React في لحظتين: قبل أن يعمل التأثير من جديد باعتماديات جديدة مباشرة، وعندما يغادر المكوّن الشاشة (الإزالة).

اختر غرفة أخرى: تُظهر وحدة التحكم disconnect from general ثم connect to music. يُغلق الاتصال القديم دائمًا قبل أن يُفتح الجديد. انقر Hide فتعمل دالة التنظيف مرة أخيرة، لأن ChatRoom أُزيل.

الدالة connect هنا بديل عن عميل محادثة أو WebSocket حقيقي. والشكل نفسه لأي منها: أنشئه في الإعداد، وأغلقه في التنظيف.

مسح مؤقت

المؤقت الذي يبدأ في تأثير يظل يعمل بعد زوال المكوّن ما لم توقفه دالة التنظيف.

احذف السطر clearInterval(id);، ثم أخفِ المؤقت: يظل tick يصل إلى وحدة التحكم من مكوّن لم يعد موجودًا. وكل Show يبدأ مؤقتًا آخر.

لاحظ setSeconds((s) => s + 1). يعمل التأثير مرة واحدة، فستقرأ setSeconds(seconds + 1) العادية القيمة seconds من العرض الأول إلى الأبد وتتوقف عند 1. أما دالة التحديث فتحصل دائمًا على أحدث قيمة.

إزالة مستمع أحداث

يتبع مستمعو الأحداث على window أو document النمط نفسه. مرّر إلى removeEventListener الدالة نفسها التي مررتها إلى addEventListener:

useEffect(() => {
    function handleKeyDown(e) {
        if (e.key === 'Escape') onClose();
    }
    window.addEventListener('keydown', handleKeyDown);
    return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);

الحلقة اللانهائية

التأثير الذي يضبط قيمة حالة يعتمد عليها أيضًا يبدأ حلقة: تتغير الحالة، فتعرض React، وقد تغيّرت الاعتمادية، فيعمل التأثير ويضبط الحالة من جديد.

// Never stops: every render runs the effect, every effect causes a render
useEffect(() => {
    setCount(count + 1);
});

// Same loop: count changes, so the effect runs again
useEffect(() => {
    setCount(count + 1);
}, [count]);

يُعرض المكوّن بلا نهاية ويبقى التبويب مشغولًا. لا توقف React هذه الحلقة: في التطوير تسجّل خطأ "Maximum update depth exceeded" في وحدة التحكم، وفي الإنتاج لا تسجّل شيئًا. تشغّل هذه الكتلة النسخة الثانية بشرط توقف، لتراقب الحلقة دون تجميد الصفحة:

تسجّل وحدة التحكم من render 0 إلى render 5: ستة عروض لتركيب واحد، كل منها بدأه التأثير. والإصلاح يعتمد على ما قصدته:

  • أردته مرة واحدة. استخدم [] وصيغة دالة التحديث، setCount((c) => c + 1)، حتى لا تكون count اعتمادية.
  • تحسب قيمة من حالة أخرى. لا تخزّنها أصلًا؛ احسبها أثناء العرض، كما يظهر في القسم الأخير من هذه الصفحة.
  • تستجيب لإجراء من المستخدم. ضع الكود في معالج الحدث.

الكائنات والدوال كاعتماديات

الكائن أو المصفوفة أو الدالة التي تُنشأ في جسم المكوّن قيمة جديدة في كل عرض، فيعمل التأثير الذي يعتمد عليها بعد كل عرض، حتى عندما لا يتغير شيء بداخلها.

لا علاقة لتبديل الألوان بالبحث، ومع ذلك يسجّل كل تبديل search for react من جديد. انقل السطر options إلى داخل التأثير وغيّر المصفوفة إلى [query]: تصمت عمليات التبديل ولا يشغّل البحث إلا الكتابة في الحقل.

هذا هو الإصلاح العام. أنشئ الكائنات والدوال داخل التأثير الذي يستخدمها، واعتمد على القيم البسيطة التي تُبنى منها. وعندما يجب أن تعيش دالة خارج التأثير، يُبقي useCallback هويتها ثابتة بين مرات العرض.

لماذا تعمل التأثيرات مرتين في التطوير

مع تفعيل <StrictMode> (يغلّف تطبيق Vite الجديد الجذر به، ويفعّله App Router في Next.js افتراضيًا)، تجري React اختبارًا في التطوير: تركّب كل مكوّن، وتشغّل تأثيراته، وتشغّل دوال تنظيفها، ثم تشغّل التأثيرات مرة أخرى. بالنسبة لغرفة المحادثة أعلاه، تُظهر وحدة تحكم التطوير:

connect to general
disconnect from general
connect to general

يحدث هذا مرة واحدة، عند التركيب، وفي التطوير فقط. وهو موجود لكشف التأثيرات التي تنقصها دالة تنظيف: إذا كسر تشغيلان متتاليان شيئًا، فسيكسره المستخدم أيضًا عندما يغادر ويعود. والإصلاح ليس أبدًا منع التشغيل الثاني بمرجع؛ بل كتابة دالة التنظيف. تعمل المعاينة في هذه الصفحة مثل بناء الإنتاج، فلن ترى التشغيل المزدوج هنا.

قد لا تحتاج إلى تأثير

التأثيرات للمزامنة مع أنظمة خارج React. واستخدامان شائعان أفضل من دونها.

القيم المحسوبة من props أو الحالة. احسبها أثناء العرض. تخزينها في الحالة ومزامنتها بتأثير يكلّف عرضًا إضافيًا، وأول هذين العرضين يضع قيمة قديمة في DOM:

كل نقرة تعرض WithEffect مرتين، وعرضه الأول ما زال يُظهر الاسم القديم. أما Computed فيُعرض مرة واحدة بالقيمة الصحيحة.

الكود الذي يستجيب لإجراء من المستخدم. إرسال نموذج، أو عرض إشعار بعد عملية شراء، أو إرسال حدث تحليلات لنقرة ينتمي إلى معالج الحدث. هناك تعرف بالضبط ما حدث، بينما لا يعرف التأثير إلا أن قيمة ما تغيّرت.

useEffectEvent

أحيانًا يحتاج التأثير إلى أحدث قيمة لـ prop دون أن يعمل من جديد عندما تتغير. في غرفة المحادثة، ينبغي أن يستخدم الإشعار theme الحالية، لكن تبديل السمة ينبغي ألا يعيد الاتصال. تضيف React 19.2 الخطاف useEffectEvent لهذا:

import { useEffect, useEffectEvent } from 'react';

function ChatRoom({ roomId, theme }) {
    const onConnected = useEffectEvent(() => {
        showNotification('Connected!', theme); // always the latest theme
    });

    useEffect(() => {
        const connection = createConnection(roomId);
        connection.on('connected', () => onConnected());
        connection.connect();
        return () => connection.disconnect();
    }, [roomId]); // theme is not a dependency
}

الدالة المأخوذة من useEffectEvent مخصصة للاستدعاء داخل التأثيرات فقط. لا تمررها إلى مكوّنات أخرى ولا تدرجها في مصفوفة الاعتماديات.

الأسئلة الشائعة

متى يعمل useEffect؟

بعد أن تعرض React المكوّن وتحدّث DOM، وعادة بعد أن يرسم المتصفح الشاشة. من دون مصفوفة اعتماديات يعمل بعد كل عرض، ومع [] بعد العرض الأول فقط، ومع [a, b] بعد العرض الأول وبعد أي عرض تغيّرت فيه a أو b.

ما هي دالة التنظيف في useEffect؟

الدالة التي تُرجعها من التأثير. تستدعيها React قبل تشغيل التأثير من جديد باعتماديات جديدة، ومرة أخرى عندما يُزال المكوّن من الشاشة. استخدمها لمسح المؤقتات وإزالة مستمعي الأحداث وإغلاق الاشتراكات.

لماذا يعمل useEffect مرتين؟

في التطوير، يركّب <StrictMode> كل مكوّن، ويشغّل تأثيراته، وينظّفها، ثم يشغّلها مرة أخرى، ليكشف لك التأثيرات التي تنقصها دالة تنظيف. يحدث هذا مرة واحدة عند التركيب وفي التطوير فقط. أما بناء الإنتاج فيشغّل التأثير مرة واحدة.

لماذا يسبب useEffect حلقة لا نهائية؟

يضبط التأثير قيمة حالة موجودة أيضًا في مصفوفة اعتمادياته (أو ليس له مصفوفة أصلًا). ضبط الحالة يعيد العرض، وقد تغيّرت الاعتمادية، فيعمل التأثير من جديد. استخدم دالة تحديث واحذف الاعتمادية، أو أضف شرطًا، أو احسب القيمة أثناء العرض بدلًا من ذلك.

هل يمكن أن تكون دالة useEffect غير متزامنة (async)؟

لا. يجب أن تُرجع الدالة لا شيء أو دالة تنظيف، والدالة async تُرجع وعدًا. عرّف دالة غير متزامنة داخل التأثير واستدعِها، كما في صفحة جلب البيانات.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن