useRef Hook - الوصول إلى DOM والقيم الثابتة
فهم المراجع في React
توفر المراجع طريقة للوصول إلى عقد DOM أو عناصر React التي تم إنشاؤها في طريقة العرض. إنها مفيدة عندما تحتاج إلى التفاعل مع عناصر DOM مباشرة أو الاحتفاظ بالقيم عبر العروض دون التسبب في إعادة العرض.
يُرجع useRef hook كائن مرجع قابل للتغيير يتم تهيئة خاصية .current الخاصة به إلى الوسيطة الممررة. يستمر الكائن المُرجع طوال عمر المكون بالكامل.
متى تستخدم المراجع
استخدم المراجع لـ: إدارة التركيز/تحديد النص/تشغيل الوسائط، تشغيل الرسوم المتحركة الإلزامية، التكامل مع مكتبات DOM من طرف ثالث، وتخزين القيم القابلة للتغيير التي لا تؤدي إلى إعادة العرض.
صيغة useRef الأساسية
إنشاء واستخدام مرجع أمر بسيط:
import React, { useRef } from 'react';
function TextInputFocus() {
// إنشاء مرجع لتخزين عنصر الإدخال
const inputRef = useRef(null);
const handleFocus = () => {
// الوصول إلى عنصر DOM والتركيز عليه
inputRef.current.focus();
};
return (
<div>
<input
ref={inputRef}
type="text"
placeholder="انقر على الزر للتركيز علي"
/>
<button onClick={handleFocus}>
التركيز على الإدخال
</button>
</div>
);
}
export default TextInputFocus;
في هذا المثال، يحتفظ inputRef.current بعنصر إدخال DOM الفعلي، مما يسمح لنا باستدعاء focus() عليه مباشرة.
الوصول إلى عناصر DOM
حالة الاستخدام الأكثر شيوعًا للمراجع هي الوصول إلى عناصر DOM لتنفيذ عمليات لا تتعامل معها React بشكل تصريحي:
import React, { useRef, useState } from 'react';
function VideoPlayer() {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const togglePlay = () => {
if (isPlaying) {
videoRef.current.pause();
} else {
videoRef.current.play();
}
setIsPlaying(!isPlaying);
};
const handleVolumeChange = (e) => {
videoRef.current.volume = e.target.value / 100;
};
return (
<div>
<video
ref={videoRef}
src="https://example.com/video.mp4"
width="400"
/>
<div>
<button onClick={togglePlay}>
{isPlaying ? 'إيقاف مؤقت' : 'تشغيل'}
</button>
<label>
الصوت:
<input
type="range"
min="0"
max="100"
defaultValue="50"
onChange={handleVolumeChange}
/>
</label>
</div>
</div>
);
}
export default VideoPlayer;
الاحتفاظ بالقيم دون إعادة العرض
على عكس الحالة، تحديث خاصية .current للمرجع لا يؤدي إلى إعادة العرض. هذا يجعل المراجع مثالية لتخزين القيم التي تحتاج إلى الاستمرار ولكن لا يجب أن تؤثر على واجهة المستخدم مباشرة:
import React, { useRef, useState, useEffect } from 'react';
function RenderCounter() {
const [count, setCount] = useState(0);
const renderCount = useRef(0);
// زيادة عدد العروض في كل عرض
useEffect(() => {
renderCount.current += 1;
});
return (
<div>
<h2>العدد: {count}</h2>
<p>تم عرض هذا المكون {renderCount.current} مرة</p>
<button onClick={() => setCount(count + 1)}>
زيادة العدد
</button>
</div>
);
}
export default RenderCounter;
useRef مقابل useState
استخدم useState عندما يجب أن يؤدي تغيير القيمة إلى إعادة العرض. استخدم useRef عندما تحتاج إلى تخزين قيمة تستمر بين العروض ولكن لا يجب أن تتسبب في إعادة العرض عند تغييرها.
تخزين القيم السابقة
المراجع ممتازة لتخزين القيمة السابقة للحالة أو الخصائص:
import React, { useRef, useState, useEffect } from 'react';
function PreviousValue() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
// تخزين العدد الحالي كسابق للعرض التالي
prevCountRef.current = count;
});
const prevCount = prevCountRef.current;
return (
<div>
<h2>العدد الحالي: {count}</h2>
<h3>العدد السابق: {prevCount ?? 'لا يوجد'}</h3>
<button onClick={() => setCount(count + 1)}>
زيادة
</button>
<button onClick={() => setCount(count - 1)}>
نقصان
</button>
</div>
);
}
export default PreviousValue;
تخزين معرفات المهلة والفاصل الزمني
المراجع مثالية لتخزين معرفات المؤقت التي تحتاج إلى مسحها لاحقًا:
import React, { useRef, useState } from 'react';
function StopwatchWithRef() {
const [time, setTime] = useState(0);
const [isRunning, setIsRunning] = useState(false);
const intervalRef = useRef(null);
const start = () => {
if (isRunning) return;
setIsRunning(true);
intervalRef.current = setInterval(() => {
setTime(prevTime => prevTime + 1);
}, 1000);
};
const stop = () => {
if (!isRunning) return;
setIsRunning(false);
clearInterval(intervalRef.current);
};
const reset = () => {
stop();
setTime(0);
};
return (
<div>
<h2>الوقت: {time} ثانية</h2>
<button onClick={start} disabled={isRunning}>
بدء
</button>
<button onClick={stop} disabled={!isRunning}>
إيقاف
</button>
<button onClick={reset}>
إعادة تعيين
</button>
</div>
);
}
export default StopwatchWithRef;
خطأ شائع: استخدام المراجع بدلاً من الحالة
لا تستخدم المراجع لتخزين القيم التي تؤثر على ما يظهر على الشاشة. إذا كان يجب أن يؤدي تغيير القيمة إلى تحديث واجهة المستخدم، استخدم الحالة بدلاً من ذلك. المراجع للقيم التي لا تؤثر بشكل مباشر على العرض.
مراجع الاستدعاء
بدلاً من تمرير كائن مرجع، يمكنك تمرير دالة (مرجع استدعاء) تتلقى عنصر DOM كوسيطة:
import React, { useState, useCallback } from 'react';
function MeasureDimensions() {
const [height, setHeight] = useState(0);
// مرجع استدعاء يقيس العنصر
const measuredRef = useCallback(node => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<div>
<h2 ref={measuredRef}>
هذا عنوان للقياس
</h2>
<p>ارتفاع العنوان هو: {height}px</p>
</div>
);
}
export default MeasureDimensions;
توجيه المراجع
في بعض الأحيان تحتاج إلى تمرير مرجع من مكون أب إلى مكون طفل. استخدم React.forwardRef لذلك:
import React, { useRef, forwardRef } from 'react';
// مكون طفل يقبل مرجعًا موجهًا
const CustomInput = forwardRef((props, ref) => {
return (
<input
ref={ref}
type="text"
className="custom-input"
{...props}
/>
);
});
// مكون أب يستخدم المرجع الموجه
function ParentComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<CustomInput
ref={inputRef}
placeholder="يمكن التركيز علي من الأب"
/>
<button onClick={focusInput}>
التركيز على الإدخال المخصص
</button>
</div>
);
}
export default ParentComponent;
useImperativeHandle Hook
استخدم useImperativeHandle مع forwardRef لتخصيص ما يمكن أن يصل إليه المكون الأب على الطفل:
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
// كشف طرق محددة فقط للأب
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
scrollIntoView: () => {
inputRef.current.scrollIntoView({ behavior: 'smooth' });
},
getValue: () => {
return inputRef.current.value;
}
}));
return <input ref={inputRef} {...props} />;
});
function FormWithFancyInput() {
const fancyInputRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const value = fancyInputRef.current.getValue();
console.log('قيمة الإدخال:', value);
fancyInputRef.current.focus();
};
return (
<form onSubmit={handleSubmit}>
<FancyInput
ref={fancyInputRef}
placeholder="اكتب شيئًا"
/>
<button type="submit">إرسال</button>
</form>
);
}
export default FormWithFancyInput;
مثال عملي: دردشة ذاتية التمرير
import React, { useRef, useEffect, useState } from 'react';
function ChatWindow() {
const [messages, setMessages] = useState([
{ id: 1, text: 'مرحبا!' },
{ id: 2, text: 'كيف حالك؟' }
]);
const [inputValue, setInputValue] = useState('');
const messagesEndRef = useRef(null);
// التمرير التلقائي إلى الأسفل عندما تتغير الرسائل
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const addMessage = (e) => {
e.preventDefault();
if (!inputValue.trim()) return;
setMessages(prev => [
...prev,
{ id: Date.now(), text: inputValue }
]);
setInputValue('');
};
return (
<div style={{ height: '300px', display: 'flex', flexDirection: 'column' }}>
<div style={{ flex: 1, overflow: 'auto', border: '1px solid #ccc', padding: '10px' }}>
{messages.map(msg => (
<div key={msg.id} style={{ marginBottom: '10px' }}>
{msg.text}
</div>
))}
<div ref={messagesEndRef} />
</div>
<form onSubmit={addMessage} style={{ display: 'flex', marginTop: '10px' }}>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="اكتب رسالة..."
style={{ flex: 1 }}
/>
<button type="submit">إرسال</button>
</form>
</div>
);
}
export default ChatWindow;
تمرين 1: عداد النقرات
أنشئ مكونًا يحسب عدد المرات التي تم فيها النقر على زر بدون التسبب في إعادة العرض. اعرض العدد فقط عند النقر على زر "عرض العدد".
// المتطلبات:
// 1. استخدم useRef لتخزين عدد النقرات
// 2. زر "انقر علي" يزيد المرجع دون إعادة العرض
// 3. زر "عرض العدد" يعرض العدد باستخدام alert أو console
// 4. يجب أن يعيد المكون العرض فقط عند النقر على "عرض العدد"
تمرين 2: مدير تركيز حقول النموذج
قم ببناء نموذج متعدد الحقول مع أزرار "التالي" التي تركز تلقائيًا على حقل الإدخال التالي.
// المتطلبات:
// 1. نموذج بحقول الاسم والبريد الإلكتروني والهاتف والعنوان
// 2. كل حقل لديه زر "التالي"
// 3. النقر على "التالي" يركز على الإدخال التالي
// 4. زر الحقل الأخير يرسل النموذج
// 5. استخدم المراجع لجميع المدخلات
تمرين 3: تطبيق رسم Canvas
أنشئ مكون رسم بسيط باستخدام عنصر HTML5 canvas و useRef.
// المتطلبات:
// 1. عنصر Canvas بحجم 400x300px
// 2. رسم خطوط عند الضغط على الماوس وتحريكه
// 3. زر "مسح Canvas" لإعادة التعيين
// 4. استخدم useRef للوصول إلى canvas والسياق
// 5. تتبع حالة الماوس دون التسبب في إعادة العرض
الملخص
useRefيُرجع كائنًا قابلاً للتغيير يستمر طوال عمر المكون- تغيير
ref.currentلا يؤدي إلى إعادة العرض - استخدم المراجع للوصول إلى DOM، والمؤقتات، والقيم السابقة، والعمليات الإلزامية
forwardRefيسمح بتمرير المراجع من الأب إلى مكونات الطفلuseImperativeHandleيخصص ما يمكن أن يصل إليه الأب عبر المرجع- مراجع الاستدعاء تتلقى عنصر DOM كوسيطة
- لا تستخدم المراجع للبيانات التي تؤثر على العرض - استخدم الحالة بدلاً من ذلك