useRef — ما هو، خطاف المراجع والعمل مع DOM في React

المؤلف: IT Sectr نُشر: 2026-07-04 وقت القراءة: 9 دق

useRef هو خطاف React يُنشئ كائن ref قابل للتغيير بخاصية .current، يُحفظ بين تصييرات المكون. على عكس useState، تغيير .current لا يُسبب إعادة تصيير، مما يجعل useRef مثالياً لتخزين مراجع DOM، الموقتات وأي قيم يجب أن تستمر عبر التصييرات دون إعادة رسم واجهة المستخدم. وفقاً لوثائق React (2025)، يتم إنشاء كائن ref مرة واحدة خلال عمر المكون ولا يتغير بين التصييرات، مما يضمن استقرار المرجع.

الخلاصة

  • useRef — خطاف لإنشاء كائن ref قابل للتغيير بخاصية .current.
  • لا إعادة تصيير — تغيير .current لا يُسبب إعادة تصيير المكون.
  • مراجع DOM — يُمرر إلى prop ref الخاص بالعنصر للوصول المباشر إلى عقدة DOM.
  • تخزين القيم — مناسب للموقتات، القيم السابقة وأي بيانات بين التصييرات.
  • مرجع ثابت — كائن ref لا يتغير بين التصييرات، على عكس الإغلاقات.

ما هو useRef في React

useRef هو خطاف أُضيف في React 16.8 يُعيد كائن ref قابل للتغيير بخاصية واحدة .current. يتم تعيين القيمة الأولية إلى .current عند تركيب المكون. الفرق الرئيسي عن المتغير العادي هو أن كائن ref يبقى عبر إعادة التصيير: في التصييرات اللاحقة، لا يُعيد React إنشاء ref بل يُعيد نفس الكائن.

داخلياً، يستخدم useRef نفس آلية useState و useEffect — قائمة مرتبطة من الخطافات على عقدة fiber. ومع ذلك، على عكس useState، لا يتتبع React تغييرات كائن ref، ولا يُدرج التحديثات في قائمة الانتظار ولا يُخطط لإعادة التصيير. وهذا يجعل useRef خطافاً خفيفاً جداً بدون تأثير على الأداء حتى مع التغييرات المتكررة.

وفقاً لفريق React — الأسئلة الشائعة عن الخطافات (2025)، useRef هو في الأساس «صندوق» يخزن قيمة. سكر نحوي: const ref = useRef(قيمة_ابتدائية) يكافئ const ref = { current: قيمة_ابتدائية } مع فارق أن React يضمن أن كائن ref سيكون نفسه في كل تصيير. لا سحر — مجرد مرجع ثابت يُدار بواسطة React.

jsx
import { useRef } from 'react';

function Component() {
    const countRef = useRef(0);
    // countRef.current = 0 في البداية
    // countRef.current = 5 بعد التغيير
    // لا يحدث إعادة تصيير!
}

الوصول إلى عناصر DOM باستخدام useRef

حالة الاستخدام الأكثر شيوعاً لـ useRef هي الحصول على وصول مباشر إلى عنصر DOM. يُخصص React لـ elementRef.current مرجعاً لعقدة DOM بعد تركيب المكون. هذا ضروري لتركيز إدخال، قياس أبعاد العنصر، التكامل مع مكتبات الرسوم المتحركة والمكتبات التي لا تستخدم تصيير React.

يدير React تلقائياً مراجع ref على DOM: عند التركيب يُخصص عقدة DOM الفعلية، وعند إلغاء التركيب يُعيدها إلى null. هذا يضمن أن .current يحمل دائماً القيمة المحدثة ويمنع تسرب الذاكرة عبر المراجع المعلقة للعناصر المحذوفة. لا حاجة للتنظيف اليدوي.

jsx
function AutoFocusInput() {
    const inputRef = useRef(null);

    useEffect(() => {
        // تركيز الإدخال بعد تركيب المكون
        inputRef.current?.focus();
    }, []);

    return <input ref={inputRef} type="text" />;
}

وفقاً لـ React — وثائق Refs و DOM (2025)، لا تستخدم ref للعمليات التصريحية — فتح/إغلاق النوافذ المنبثقة، إدارة الرؤية. لهذه الأغراض يوجد state و props. الـ Refs مخصصة للعمليات الأمرية التي لا يمكن التعبير عنها بشكل تصريحي: التركيز، تحديد النص، التكامل مع مكتبات الطرف الثالث.

تخزين القيم القابلة للتغيير بدون إعادة تصيير

حالة الاستخدام الثانية من حيث الأهمية هي تخزين القيم العشوائية التي يجب أن تستمر بين التصييرات ولكن تغييرها لا يجب أن يُسبب إعادة تصيير. أمثلة نموذجية: معرفات الموقتات (setInterval/setTimeout)، علامات إلغاء الطلبات، قيم props السابقة للمقارنة وأي بيانات لا تؤثر على مخرجات واجهة المستخدم.

هذا مفيد بشكل خاص في useEffect: useRef يخزن معرف الموقت و cleanup يمسحه. إذا تم تخزين timerId في useState، كل استدعاء لـ setTimerId سيُسبب إعادة تصيير غير ضرورية ليست مطلوبة لا للمنطق ولا لواجهة المستخدم. useRef يحل هذه المشكلة بدون عبء وبدون إعادة تصيير غير ضرورية.

jsx
function Timer() {
    const intervalRef = useRef(null);

    const start = () => {
        intervalRef.current = setInterval(() => {
            console.log('دقة');
        }, 1000);
    };

    const stop = () => {
        clearInterval(intervalRef.current);
    };

    useEffect(() => stop, []);

    return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}

useRef مقابل useState: متى تستخدم كل منهما

المعضلة الرئيسية — الاختيار بين useRef و useState — تُحل بسؤال واحد: «هل هناك حاجة لإعادة تصيير عند تغيير القيمة؟». إذا نعم — useState. إذا لا — useRef. useState يخزن الحالة التي تؤثر على مخرجات المكون؛ useRef يخزن البيانات اللازمة للمنطق الداخلي ولكنها لا تؤثر على واجهة المستخدم.

عملياً، يستخدم المطورون غالباً useRef لتخزين دوال الاسترجاع لتجنب مشاكل الإغلاق. على سبيل المثال، إذا اشترك useEffect في حدث وكانت دالة الاسترجاع تحتاج إلى الحالة الحالية — خزّن دالة الاسترجاع في useRef. في كل تصيير، حدّث ref.current بدالة جديدة، وسيستدعي التأثير دائماً دالة الاسترجاع الجديدة دون إعادة اشتراك.

المعيارuseRefuseState
إعادة التصييرلا يُسبب عند التغييريُسبب في كل setState
التغييرمباشر: ref.current = قيمةعبر setter: setState(قيمة)
الاستخدام في JSXلا يُستخدم (بدون تأثير)يُستخدم في مخرجات المكون
مثالموقتات، مراجع DOM، قيم سابقةبيانات نموذج، حالة واجهة المستخدم، علامات

يوجد نمط مضاد: استخدام useRef للبيانات المطلوبة في JSX ولكن تغييرها لا يجب أن يُسبب إعادة تصيير. هذا يؤدي إلى عدم التزامن — تُظهر واجهة المستخدم بيانات قديمة بينما ref.current جديد بالفعل. إذا كانت القيمة تُعرض في واجهة المستخدم — استخدم useState. إذا كانت تُستخدم داخلياً فقط — useRef.

useRef مع useEffect

مزيج useRef + useEffect هو النمط القياسي لتتبع القيم السابقة للـ props. خزّن القيمة السابقة في ref، قارنها مع القيمة الحالية في useEffect واتخذ القرارات بناءً على الفرق. هذا مفيد بشكل خاص في الرسوم المتحركة عندما تحتاج إلى معرفة القيمة قبل التغيير.

jsx
function PriceDisplay({ price }) {
    const prevPriceRef = useRef(price);

    useEffect(() => {
        const prevPrice = prevPriceRef.current;
        if (price > prevPrice) {
            animateUp();
        } else if (price < prevPrice) {
            animateDown();
        }
        prevPriceRef.current = price;
    }, [price]);

    return <span>${price}</span>;
}

وفقاً لوثائق React — الأسئلة الشائعة عن الخطافات (2025)، يُسمى هذا النمط «نمط القيمة السابقة». يعمل لأن useRef يحفظ القيمة بين التصييرات، ويتم تنفيذ useEffect بعد تأكيد التغييرات. أولاً يتم تحديث DOM بالسعر الجديد، ثم يقارنه useEffect بالسابق (الذي لا يزال في ref) ويُحدّث ref.current إلى القيمة الحالية.

الأخطاء الشائعة مع useRef

الخطأ الأكثر شيوعاً هو قراءة ref.current خلال مرحلة التصيير لحساب JSX. نظراً لأن تغيير ref.current لا يُسبب إعادة تصيير، قد يستخدم المكون قيمة قديمة. إذا كان .current يشارك في مخرجات واجهة المستخدم — استخدم useState. إذا كنت بحاجة لمزامنة ref و state، استخدم useEffect لتحديث state من ref.

  • Ref في JSX — قراءة ref.current داخل دالة التصيير تؤدي إلى عدم تزامن واجهة المستخدم والبيانات.
  • Ref كاعتماد لـ useEffect — ref.current لا يجب أن يكون في مصفوفة التبعيات؛ React لا يتتبع تغييراته.
  • نسيان التحقق من null — عند إلغاء التركيب يصبح ref.current null؛ تحقق منه قبل الوصول.
  • تخزين الدوال في ref — حدّث ref.current في كل تصيير إذا كنت تستخدم نمط دالة الاسترجاع الجديدة.
jsx
// ❌ لا تقرأ ref.current أثناء التصيير للعرض
function BadComponent() {
    const valRef = useRef(0);
    return <p>{valRef.current}</p>; // لن يتم التحديث عند التغيير
}

// ✅ استخدم state للعرض و ref للمنطق
function GoodComponent() {
    const [val, setVal] = useState(0);
    const valRef = useRef(0);
    return <p>{val}</p>;
}

خطأ شائع آخر هو استخدام useRef كطريقة وحيدة لتخزين الحالة في مكون عندما تؤثر البيانات فعلياً على واجهة المستخدم. يختار المطورون useRef لتجنب إعادة التصيير «غير الضرورية»، لكنهم ينتهي بهم الأمر بواجهة مستخدم لا تتحدّث. النهج الصحيح: استخدم useState لبيانات واجهة المستخدم و useRef فقط للقيم المساعدة غير المشاركة في التصيير.

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

هل يمكن استخدام useRef لتخزين الحالة السابقة؟

نعم، هذا نمط شائع — نمط الحالة السابقة. أنشئ ref وحدّثه في useEffect في كل مرة تتغير فيها القيمة المتتبعة. بين التصيير والتأثير، يحتوي ref.current على القيمة السابقة التي يمكن مقارنتها بالحالية. هذا لا يتطلب إعادة تصيير إضافية ويعمل مع أي نوع بيانات.

لماذا ref.current يساوي null في أول تصيير؟

يُخصص React ref.current قيمة عقدة DOM فقط بعد أن يتم تصيير العنصر وإضافته إلى DOM الحقيقي. أثناء التصيير الأول، المكون ليس مركباً بعد، لذلك ref.current يساوي القيمة الأولية (null). الوصول إلى DOM عبر ref.current ممكن فقط في useEffect أو في معالجات الأحداث التي تُستدعى بعد التركيب.

ما الفرق بين useRef و createRef؟

createRef يُنشئ كائن ref جديد في كل تصيير — يجب استخدامه فقط في مكونات الفئة. useRef يُنشئ ref مرة واحدة ويُعيد نفس الكائن في جميع التصييرات اللاحقة. في المكونات الدالية، استخدم حصراً useRef؛ createRef سيؤدي إلى فقدان القيمة عند إعادة التصيير لأن الكائن سيُعاد إنشاؤه.

هل يمكن تمرير useRef عبر props؟

نعم، يمكن تمرير كائن ref كـ prop عادي: <Child inputRef={inputRef} />. المكون الابن يستخدمه عبر inputRef.current. ومع ذلك، لتمرير ref مباشرة إلى عنصر DOM لمكون ابن، استخدم forwardRef — HOC الذي يسمح بإعادة توجيه refs عبر props. بدون forwardRef، لن يتم ربط ref تلقائياً بعنصر DOM داخل المكون الابن.

كيف أقيس حجم عنصر باستخدام useRef؟

اربط ref بالعنصر، ثم في useEffect اقرأ ref.current.getBoundingClientRect() أو ref.current.offsetWidth / offsetHeight. للتتبع التفاعلي لتغييرات الحجم، استخدم ResizeObserver داخل useEffect: أنشئ observer، اشترك في التغييرات وحدّث state بالأبعاد الجديدة. لا تنسَ فصل observer في الـ cleanup.

الملخص

  • useRef — خطاف لإنشاء كائن ref قابل للتغيير يُحفظ بين التصييرات دون إحداث إعادة تصيير.
  • الوصول إلى DOM — prop ref يُمرر إلى عنصر للوصول المباشر إلى عقدة DOM؛ متاح بعد التركيب.
  • تخزين البيانات — الموقتات، دوال الاسترجاع، العلامات وأي قيم لا تؤثر على واجهة المستخدم تُخزن في ref.
  • useRef مقابل useState — اختر useState إذا كان التغيير يجب أن يُعيد تصيير واجهة المستخدم؛ useRef إذا لا.
  • نمط القيمة السابقة — خزّن القيمة السابقة في ref، حدّثها في useEffect للمقارنة.
  • Ref في JSX — لا تقرأ ref.current في مرحلة التصيير؛ هذا يؤدي إلى عدم تزامن واجهة المستخدم والبيانات.
  • forwardRef — استخدمه لتمرير ref عبر props إلى مكون ابن.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا