React: useRef والتلاعب بـ DOM

آخر تحديث: 2026-10-26

useRef إنه أشبه بـخزنة — يمكنك تخزين أي شيء بداخلها، وسيظل موجودًا عندما تخرجه. وعلى عكس useState، فإن تغيير قيمة ref لن يؤدي إلى إعادة عرض المكون.


1. ما ستتعلمه



2. قصة شريط البحث

الاستخدامان الرئيسيان لـ useRef

100%
flowchart LR
    subgraph "Uses1: DOMQuote"
        A[useRef] --> B["ref={inputRef}"]
        B --> C["inputRef.current.focus()"]
    end
    
    subgraph "Uses2: Variable-Value Storage"
        D[useRef] --> E[".current = value]
        E --> F[Does not trigger a repaint]
    end
    
    G[forwardRef] --> H[Passed from the parent componentref]
    H --> I[Child Component ExposureDOM]
    
    style A fill:#e3f2fd,stroke:#1565c0
    style D fill:#e8f5e9,stroke:#2e7d32
    style G fill:#fff3e0,stroke:#e65100

(1) المشكلة: يتطلب التركيز على حقل الإدخال التعامل مع DOM

أنشأ بوب صفحة بحث وأراد أن يتم توجيه التركيز تلقائيًا إلى مربع البحث عند تحميل الصفحة:

▶ جرّب الكود

يريد بوب استخدام useState لتنفيذ:

▶ جرّب الكود

(2) حل باستخدام useRef

▶ جرّب الكود

الناتج:

TEXT
الأزرار: Select All Text، Scroll to the bottom، Measure the height. الإدخال: حقل إدخال بتركيز تلقائي. يتولى useEffect إدارة الآثار الجانبية. يشير useRef إلى DOM

المزايا: تُرجع useRef كائنًا قابلًا للتعديل { current: null }، و.current هو عنصر DOM حقيقي. ولا حاجة إلى محددات أو getElementById — فهي متوافقة بشكل طبيعي مع React.



3. الاستخدامان الأساسيان لـ useRef

الغرض ما تشير إليه ref.current تؤدي إلى تحديث العرض السيناريوهات الشائعة
مرجع DOM عنصر DOM ❌ التركيز، التمرير، قياس الأبعاد
تخزين المتغيرات أي قيمة في جافا سكريبت ❌ معرّف المؤقت، القيمة السابقة، حالة عدم العرض

(1) حالة الاستخدام 1: مراجع DOM

الاستخدامات الأكثر شيوعًا — استرداد عناصر DOM، والتحكم في التركيز والحجم وموضع التمرير، وما إلى ذلك.

العملية الرمز الوصف
التركيز inputRef.current.focus() حقل إدخال التركيز التلقائي
تحديد النص inputRef.current.select() تحديد الكل في حقل الإدخال
التمرير إلى divRef.current.scrollIntoView() التمرير إلى عنصر معين
قراءة الأبعاد divRef.current.offsetHeight الحصول على ارتفاع العنصر
تشغيل الفيديو videoRef.current.play() التحكم في تشغيل الفيديو

▶ مثال: 5 عمليات شائعة في DOM

▶ جرّب الكود

الناتج:

TEXT
حقل إدخال بتركيز تلقائي، وزر "Select All Text"، وقائمة قابلة للتمرير من 20 سطرًا (مع "Scroll to bottom")، ومربع مع "Measure the height" → يعرض قيمة px

(2) حالة الاستخدام 2: تخزين قيم المتغيرات (دون إحداث إعادة رسم)

هذه هي الحيلة الخفية لـ useRef — فهي تخزن أي قيمة، ولن يتم إعادة عرض المكون عند تغير تلك القيمة.

▶ جرّب الكود

4. useRef مقابل useState

البعد useRef useState
قيمة الإرجاع { current: initial } [value, setter]
طريقة التعديل ref.current = newValue setState(newValue)
إعادة العرض بعد التعديل ❌ لا يؤدي إلى إعادة العرض ✅ يؤدي إلى إعادة العرض
عند قراءتها تُقرأ أحدث قيمة على الفور لا تُقرأ حتى عملية العرض التالية
حالات الاستخدام عمليات DOM، معرّفات المؤقتات، القيم السابقة البيانات المراد عرضها في واجهة المستخدم
▶ جرّب الكود

الناتج:

TEXT
يعرض: "DOM Example of Operation". الأزرار: Select All Text، Scroll to the bottom، Measure the height. الإدخال: حقل إدخال بتركيز تلقائي. يتولى useEffect إدارة الآثار الجانبية. يشير useRef إلى DOM/متغير

▶ مثال: استخدام useRef لحفظ القيمة السابقة

▶ جرّب الكود

الناتج:

TEXT
الحالي: 0، السابق: (first time). النقر على +1 → الحالي: 1، السابق: 0. كل نقرة تعرض القيمة الحالية والقيمة السابقة.


5. forwardRef: المكونات الأصلية تتحكم في DOM للمكونات الفرعية

بشكل افتراضي، لا تكشف مكونات الوظائف عن مراجع DOM الخاصة بها. ويتيح استخدام forwardRef للمكونات الفرعية الحصول على ref الخاص بالمكون الأصلي:

▶ جرّب الكود

6. useImperativeHandle: إتاحة طريقة معينة

إذا كنت لا ترغب في إتاحة عنصر DOM بالكامل، فاستخدم useImperativeHandle لإتاحة طرق محددة فقط:

▶ جرّب الكود

7. مثال كامل: محرر النص المنسق

▶ عرض المثال القابل للتشغيل (107 سطرًا)
▶ جرّب الكود

الناتج:

TEXT
الحالة: count (setter: setCount). زر: setCount(c => c + 1)}>+1. يتولى useEffect إدارة الآثار الجانبية. يشير useRef إلى DOM/متغير

الناتج المتوقع: محرر نص منسق يدعم الخط العريض والمائل والتسطير والقوائم والعناوين من النوع H2 وخيارات التنسيق الأخرى؛ ويمكن حفظ المحتوى أو تحميله أو مسحه خارجيًا عبر ref.


▶ المثال 3: استخدام useRef لإغلاق نافذة منبثقة عند النقر عليها من خارجها

▶ جرّب الكود

الناتج:

TEXT
زر "Menu ▾" → قائمة منسدلة (Profile، Settings، Logout). النقر في الخارج → إغلاق تلقائي عبر خطاف useClickOutside.

الناتج:

TEXT
زر التخفيف (throttle): النقرات السريعة المتتالية تُتجاهل (بحد أقصى مرة واحدة في الثانية). عداد نقرات + تغذية راجعة "Throttled!". يخزن useRef الطابع الزمني lastClick

▶ المثال 4: استخدام useRef لتخزين مؤقت من أجل تنفيذ زر محدود السرعة

▶ جرّب الكود

الناتج:

TEXT
النقر → "Clicked!" + عداد. النقر مرة أخرى خلال ثانية → "Too fast! Wait 1 second." (أحمر). التخفيف عبر الطابع الزمني في useRef.

الناتج:

TEXT
يعرض: "Click Me". الحالة: clicks (setter: setClicks)، feedback (setter: setFeedback). زر: Click Me. يشير useRef إلى DOM/متغير. سلوك قائم على مؤقت

▶ المثال 5: استخدام forwardRef لتغليف مكون Input القابل للتركيز

JSX
const FancyInput = React.forwardRef(function FancyInput({ label, error, ...props }, ref) {
  const internalRef = useRef(null)

  useImperativeHandle(ref, () => ({
    focus: () => internalRef.current?.focus(),
    selectAll: () => {
      const el = internalRef.current
      if (el) { el.focus(); el.select() }
    },
    scrollIntoView: () => internalRef.current?.scrollIntoView({ behavior: 'smooth' }),
  }))

  return (
    <div style={{ marginBottom: 12 }}>
      <label style={{ display: 'block', marginBottom: 4, fontSize: 14, fontWeight: 500 }}>{label}</label>
      <input
        ref={internalRef}
        style={{ width: '100%', padding: 8, borderRadius: 4, border: `1px solid ${error ? '#ff4d4f' : '#d9d9d9'}`, outline: 'none' }}
        {...props}
      />
      {error && <p style={{ color: '#ff4d4f', fontSize: 12, margin: '4px 0 0' }}>{error}</p>}
    </div>
  )
})

function FormWithFancyInput() {
  const emailRef = useRef(null)

  function handleSubmit(e) {
    e.preventDefault()
    emailRef.current?.focus()
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: 400, margin: '0 auto' }}>
      <h3>Sign Up</h3>
      <FancyInput ref={emailRef} label="Email" type="email" placeholder="you@example.com" />
      <FancyInput label="Password" type="password" placeholder="At least 6 characters" />
      <button type="submit" style={{ padding: '8px 24px', cursor: 'pointer' }}>Submit</button>
    </form>
  )
}
▶ جرّب الكود

الناتج:

TEXT
نموذج تسجيل: حقلا إدخال Email وPassword مع تسميات، وبنمط خطأ. عند الإرسال يتركز التركيز على email. يكشف عن الدوال focus() وselectAll() وscrollIntoView().


❓ أسئلة شائعة

س هل يجب استخدام forwardRef في كل مكون؟
ج لا. استخدمه فقط في الحالات التالية: ① عندما يحتاج المكون الأصلي إلى التعامل مباشرةً مع DOM لمكون تابع (مثل توجيه التركيز إلى حقل إدخال أو التحكم في تشغيل الوسائط)؛ ② عند تغليف مكونات النماذج القابلة لإعادة الاستخدام (حتى يتمكن المكون الأصلي من التحكم في التركيز أو التحديد). إذا كنت تقوم فقط بتمرير البيانات، فإن استخدام props يكفي.
س هل يمكن لـ useRef أن يحل محل useState؟
ج لا. التغييرات التي تطرأ على useRef لا تؤدي إلى إعادة عرض المكون، في حين أن setState يؤدي إلى ذلك. إذا كانت القيمة الخاصة بك تحتاج إلى أن تُعرض في واجهة المستخدم أو تؤثر على نتيجة العرض، فيجب عليك استخدام useState. إذا كنت تقوم ببساطة بتخزين «بيانات لا يلزم عرضها للمستخدم» (مثل معرّف الفاصل الزمني، أو القيمة من عملية العرض السابقة، أو مثيل WebSocket)، فاستخدم useRef. اعتبر useRef بمثابة «حاوية متغيرات لا تؤدي إلى إعادة عرض».

📖 ملخص


📝 تمارين

  1. تمرين أساسي (مستوى الصعوبة ⭐): أنشئ مكونًا باسم AutoFocusInput يتلقى التركيز تلقائيًّا عند تركيبه، وقم بتوفير طريقتين — focus() وclear() — ليتم استدعاؤهما من قبل المكون الأصلي.
  2. تمرين متقدم (مستوى الصعوبة ⭐⭐): أنشئ مكونًا باسم ClickCounter، واستخدم useRef لتتبع عدد النقرات (دون عرضها في واجهة المستخدم)، واستخدم useEffect لتسجيل عبارة "تم النقر X مرات" في وحدة التحكم عند كل عملية عرض.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون InfiniteScroll يستخدم useRef لرصد دخول العنصر الحارس (عنصر معين في أسفل الصفحة) إلى منطقة العرض، مما يؤدي إلى تشغيل عملية تحميل المزيد من البيانات. استخدم واجهة برمجة التطبيقات IntersectionObserver.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%