React: useRef والتلاعب بـ DOM
آخر تحديث: 2026-10-26
useRefإنه أشبه بـخزنة — يمكنك تخزين أي شيء بداخلها، وسيظل موجودًا عندما تخرجه. وعلى عكسuseState، فإن تغيير قيمةrefلن يؤدي إلى إعادة عرض المكون.
1. ما ستتعلمه
- الاستخدام الأساسي لـ useRef (مراجع DOM)
- تخزن
useRefالقيم القابلة للتغيير (دون إحداث إعادة عرض) - forwardRef: تمرير مرجع إلى مكون تابع
- تعرض useImperativeHandle أساليب المكونات الفرعية
- الاختيار بين useRef و useState
2. قصة شريط البحث
الاستخدامان الرئيسيان لـ useRef
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
▶ جرّب الكود
الناتج:
الأزرار: 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
▶ جرّب الكودالناتج:
حقل إدخال بتركيز تلقائي، وزر "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، معرّفات المؤقتات، القيم السابقة | البيانات المراد عرضها في واجهة المستخدم |
الناتج:
يعرض: "DOM Example of Operation". الأزرار: Select All Text، Scroll to the bottom، Measure the height. الإدخال: حقل إدخال بتركيز تلقائي. يتولى useEffect إدارة الآثار الجانبية. يشير useRef إلى DOM/متغير
▶ مثال: استخدام useRef لحفظ القيمة السابقة
▶ جرّب الكود
الناتج:
الحالي: 0، السابق: (first time). النقر على +1 → الحالي: 1، السابق: 0. كل نقرة تعرض القيمة الحالية والقيمة السابقة.
5. forwardRef: المكونات الأصلية تتحكم في DOM للمكونات الفرعية
بشكل افتراضي، لا تكشف مكونات الوظائف عن مراجع DOM الخاصة بها. ويتيح استخدام forwardRef للمكونات الفرعية الحصول على ref الخاص بالمكون الأصلي:
6. useImperativeHandle: إتاحة طريقة معينة
إذا كنت لا ترغب في إتاحة عنصر DOM بالكامل، فاستخدم useImperativeHandle لإتاحة طرق محددة فقط:
7. مثال كامل: محرر النص المنسق
▶ عرض المثال القابل للتشغيل (107 سطرًا)
الناتج:
الحالة: count (setter: setCount). زر: setCount(c => c + 1)}>+1. يتولى useEffect إدارة الآثار الجانبية. يشير useRef إلى DOM/متغير
الناتج المتوقع: محرر نص منسق يدعم الخط العريض والمائل والتسطير والقوائم والعناوين من النوع H2 وخيارات التنسيق الأخرى؛ ويمكن حفظ المحتوى أو تحميله أو مسحه خارجيًا عبر
ref.
▶ المثال 3: استخدام useRef لإغلاق نافذة منبثقة عند النقر عليها من خارجها
▶ جرّب الكود
الناتج:
زر "Menu ▾" → قائمة منسدلة (Profile، Settings، Logout). النقر في الخارج → إغلاق تلقائي عبر خطاف useClickOutside.
الناتج:
زر التخفيف (throttle): النقرات السريعة المتتالية تُتجاهل (بحد أقصى مرة واحدة في الثانية). عداد نقرات + تغذية راجعة "Throttled!". يخزن useRef الطابع الزمني lastClick
▶ المثال 4: استخدام useRef لتخزين مؤقت من أجل تنفيذ زر محدود السرعة
▶ جرّب الكود
الناتج:
النقر → "Clicked!" + عداد. النقر مرة أخرى خلال ثانية → "Too fast! Wait 1 second." (أحمر). التخفيف عبر الطابع الزمني في useRef.
الناتج:
يعرض: "Click Me". الحالة: clicks (setter: setClicks)، feedback (setter: setFeedback). زر: Click Me. يشير useRef إلى DOM/متغير. سلوك قائم على مؤقت
▶ المثال 5: استخدام forwardRef لتغليف مكون Input القابل للتركيز
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>
)
}
الناتج:
نموذج تسجيل: حقلا إدخال Email وPassword مع تسميات، وبنمط خطأ. عند الإرسال يتركز التركيز على email. يكشف عن الدوال focus() وselectAll() وscrollIntoView().
❓ أسئلة شائعة
useRef أن يحل محل useState؟useRef لا تؤدي إلى إعادة عرض المكون، في حين أن setState يؤدي إلى ذلك. إذا كانت القيمة الخاصة بك تحتاج إلى أن تُعرض في واجهة المستخدم أو تؤثر على نتيجة العرض، فيجب عليك استخدام useState. إذا كنت تقوم ببساطة بتخزين «بيانات لا يلزم عرضها للمستخدم» (مثل معرّف الفاصل الزمني، أو القيمة من عملية العرض السابقة، أو مثيل WebSocket)، فاستخدم useRef. اعتبر useRef بمثابة «حاوية متغيرات لا تؤدي إلى إعادة عرض».📖 ملخص
useRefتعود إلى{ current: initialValue }؛ وتغيير.currentلا يؤدي إلى إعادة العرض- استخدامان: ① مراجع DOM (التركيز، الحجم، التمرير) ② تخزين قيم المتغيرات (معرف المؤقت، القيمة السابقة)
forwardRef: السماح لمكون أبوي بالتحكم في DOM لمكون تابع عبر مرجع (ref)useImperativeHandle: طريقة للحد من التعرض من خلال توفير واجهات برمجة التطبيقات (APIs) التي يحتاجها المكون الأصلي فقط- تحقق دائمًا من عدم وجود قيمة فارغة قبل إجراء أي عملية على ref:
ref.current?.focus()
📝 تمارين
- تمرين أساسي (مستوى الصعوبة ⭐): أنشئ مكونًا باسم
AutoFocusInputيتلقى التركيز تلقائيًّا عند تركيبه، وقم بتوفير طريقتين —focus()وclear()— ليتم استدعاؤهما من قبل المكون الأصلي. - تمرين متقدم (مستوى الصعوبة ⭐⭐): أنشئ مكونًا باسم
ClickCounter، واستخدمuseRefلتتبع عدد النقرات (دون عرضها في واجهة المستخدم)، واستخدمuseEffectلتسجيل عبارة "تم النقر X مرات" في وحدة التحكم عند كل عملية عرض. - التحدي (الصعوبة: ⭐⭐⭐): قم بإنشاء مكون
InfiniteScrollيستخدمuseRefلرصد دخول العنصر الحارس (عنصر معين في أسفل الصفحة) إلى منطقة العرض، مما يؤدي إلى تشغيل عملية تحميل المزيد من البيانات. استخدم واجهة برمجة التطبيقاتIntersectionObserver.