عند بناء تطبيقات ثلاثية الأبعاد تفاعلية باستخدام React وThree.js، تعمل خطافات React كجسر بين منطق الواجهة ومحرك العرض. يشرح هذا المقال الخطافات على ثلاث طبقات:
- المفهوم العام لخطافات React
- الاستخدام الحقيقي داخل مشروع Three.js، خطوة بخطوة
- أي الخطافات هي الأهم في التطبيقات الفعلية
🧠 1. نظرة سريعة على خطافات React
تساعد الخطافات المكونات الوظيفية على إدارة:
- 🟢 الحالة — البيانات التي تغيّر الواجهة
- 🔵 الآثار الجانبية — العمليات الخارجية
- 🟣 الحفظ المؤقت (Memoization) — تحسين الأداء
🟢 useState ← حالة الواجهة. للقيم التي تتغير وتؤثر في العرض: الكائن المحدد، اللون المحدد، عناصر التحكم بالحركة.
🔵 useEffect ← الآثار الجانبية. للعمليات خارج عرض React: تهيئة مشهد Three.js، ربط الأحداث، ومزامنة React ← Three.js.
🟣 useRef ← تخزين دائم. لتخزين عناصر DOM، ومشهد Three.js والكاميرا وأداة العرض، ومراجع الشبكات — دون التسبب في إعادة العرض.
🟡 useMemo ← تحسين الأداء. يتجنب الحسابات المكلفة المتكررة ويحفظ الهندسة أو القيم المشتقة.
🔴 useCallback ← دوال مستقرة. يمنع إعادة إنشاء الدوال؛ مفيد لمستمعي الأحداث مثل resize وclick.
🎮 2. خطافات React في مشروع Three.js حقيقي
لنطبّق كل شيء الآن على نظام ثلاثي الأبعاد حقيقي، خطوة بخطوة.
الخطوة 1: useRef ← طبقة محرك Three.js
نخزّن كائنات Three.js الأساسية خارج دورة عرض React:
const sceneRef = useRef<THREE.Scene | null>(null);
const cameraRef = useRef<THREE.PerspectiveCamera | null>(null);
const rendererRef = useRef<THREE.WebGLRenderer | null>(null);
const cubesRef = useRef<THREE.Mesh[]>([]);لماذا؟ لأن هذه الكائنات ليست حالة للواجهة، ولا يجب أن تسبب إعادة عرض، ويجب أن تبقى بين عمليات العرض.
الخطوة 2: useState ← طبقة الواجهة
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
const [selectedColor, setSelectedColor] = useState("#ff9900");
const [moveX, setMoveX] = useState(0);تحدّث هذه القيم الواجهة، وتتحكم بالتفاعل، وتطلق الآثار.
الخطوة 3: useMemo ← تحسين الهندسة
ثابتة (تُنشأ مرة واحدة) — الهندسة مكلفة، لذا تُنشأ مرة واحدة فقط:
const baseGeometry = useMemo(() => {
return new THREE.BoxGeometry(1, 1, 1);
}, []);ديناميكية (تعتمد على الحالة) — قيمة مشتقة من الحالة:
const dynamicOffset = useMemo(() => {
return moveX * 0.5;
}, [moveX]);الخطوة 4: useCallback ← دوال مستقرة
معالج تغيير الحجم (دون اعتماديات):
const handleResize = useCallback(() => {
const camera = cameraRef.current;
const renderer = rendererRef.current;
if (!camera || !renderer || !mountRef.current) return;
camera.aspect = mountRef.current.clientWidth / mountRef.current.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(mountRef.current.clientWidth, mountRef.current.clientHeight);
}, []);معالج النقر (يعتمد على الحالة) — سلوك النقر يعتمد على اللون الحالي:
const handleCubeClick = useCallback(
(mesh: THREE.Mesh) => {
const mat = mesh.material as THREE.MeshStandardMaterial;
mat.color.set(selectedColor);
setSelectedProduct(mesh.userData as Product);
},
[selectedColor]
);الخطوة 5: useEffect ← تهيئة المحرك
useEffect(() => {
if (!mountRef.current) return;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(/* ... */);
const renderer = new THREE.WebGLRenderer();
mountRef.current.appendChild(renderer.domElement);
}, []);يعمل مرة واحدة فقط ويهيّئ المحرك ثلاثي الأبعاد كاملًا: المشهد والكاميرا وأداة العرض والإضاءة والمكعبات.
الخطوة 6: نظام التفاعل (Raycaster)
const onMouseClick = (event: MouseEvent) => {
raycaster.setFromCamera(mouse, camera);
const hits = raycaster.intersectObjects(cubesRef.current);
if (hits.length > 0) {
handleCubeClick(hits[0].object as THREE.Mesh);
}
};يكتشف المكعب الذي نُقر عليه بتحويل موضع الفأرة الثنائي الأبعاد إلى شعاع ثلاثي الأبعاد.
الخطوة 7: حلقة الحركة
const animate = () => {
requestAnimationFrame(animate);
cubesRef.current.forEach((cube) => {
cube.rotation.y += 0.01;
cube.position.x += dynamicOffset * 0.001;
});
renderer.render(scene, camera);
};تعمل في كل إطار، وتحدّث العالم ثلاثي الأبعاد، وتعرض المشهد باستمرار.
الخطوة 8: التحديثات التفاعلية (useEffect)
مزامنة اللون:
useEffect(() => {
if (selectedMeshRef.current) {
(selectedMeshRef.current.material as THREE.MeshStandardMaterial).color.set(selectedColor);
}
}, [selectedColor]);تحديث الموضع:
useEffect(() => {
cubesRef.current.forEach((cube, i) => {
cube.position.x = i * 2 - 4 + moveX;
});
}, [moveX]);🚀 3. ما الذي يعلّمه هذا المشروع
- React = متحكم الواجهة
- Three.js = محرك العرض
- الخطافات = طبقة الجسر
⚡ 4. أي الخطافات أهم في المشاريع الحقيقية؟
🔥 الأهم: useRef (نواة المحرك، حاسم)، وuseEffect (إدارة دورة الحياة، حاسم)، وuseState (التحكم بالواجهة، مهم).
🟡 ثانوي: useMemo (تحسين الأداء) وuseCallback (استقرار الأحداث).
📌 الفكرة الختامية
في معمارية React + Three.js، يدير React الحالة، ويتولى Three.js العرض، وتربط الخطافات العالمين بكفاءة.
الكود الكامل: react-learning-journey على GitHub — راجع المكون Canvas3D.tsx.



