آشنایی با React Hooks در یک پروژه واقعی Three.js (راهنمای کاربردی)

هنگام ساخت اپلیکیشن‌های سه‌بعدی تعاملی با React و Three.js، هوک‌های React نقش پل بین منطق رابط کاربری و موتور رندر را دارند. این مقاله هوک‌ها را در سه لایه توضیح می‌دهد:

  1. مفهوم کلی هوک‌های React
  2. استفاده واقعی در یک پروژه Three.js، قدم به قدم
  3. کدام هوک‌ها در اپ‌های واقعی بیشترین اهمیت را دارند

🧠 ۱. مرور سریع هوک‌های React

هوک‌ها به کامپوننت‌های تابعی کمک می‌کنند این‌ها را مدیریت کنند:

  • 🟢 State — داده‌ای که رابط کاربری را تغییر می‌دهد
  • 🔵 Side effect — عملیات بیرونی
  • 🟣 Memoization — بهینه‌سازی پرفورمنس

🟢 useState ← state رابط کاربری. برای مقادیری که تغییر می‌کنند و روی رندر اثر دارند: آبجکت انتخاب‌شده، رنگ انتخاب‌شده، کنترل‌های حرکت.

🔵 useEffect ← اثرات جانبی. برای عملیات بیرون از رندر React: راه‌اندازی صحنه Three.js، اتصال رویدادها، همگام‌سازی React ← Three.js.

🟣 useRef ← ذخیره‌سازی ماندگار. برای نگه داشتن عناصر DOM، صحنه، دوربین و رندرر Three.js و ارجاع به مش‌ها — بدون ایجاد رندر دوباره.

🟡 useMemo ← بهینه‌سازی پرفورمنس. از محاسبات پرهزینه تکراری جلوگیری می‌کند و هندسه یا مقادیر مشتق‌شده را کش می‌کند.

🔴 useCallback ← توابع پایدار. از ساخته شدن دوباره توابع جلوگیری می‌کند؛ برای Event Listenerهایی مثل resize و click مفید است.

🎮 ۲. هوک‌های React در یک پروژه واقعی Three.js

حالا همه چیز را روی یک سیستم سه‌بعدی واقعی، قدم به قدم پیاده کنیم.

قدم ۱: 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[]>([]);

چرا؟ چون این آبجکت‌ها state رابط کاربری نیستند، نباید رندر دوباره ایجاد کنند و باید بین رندرها باقی بمانند.

قدم ۲: useState ← لایه رابط کاربری

const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
const [selectedColor, setSelectedColor] = useState("#ff9900");
const [moveX, setMoveX] = useState(0);

این‌ها رابط کاربری را به‌روز می‌کنند، تعامل را کنترل می‌کنند و افکت‌ها را فعال می‌کنند.

قدم ۳: useMemo ← بهینه‌سازی هندسه

ثابت (یک بار ساخته می‌شود) — هندسه پرهزینه است، پس فقط یک بار ساخته می‌شود:

const baseGeometry = useMemo(() => {
  return new THREE.BoxGeometry(1, 1, 1);
}, []);

پویا (وابسته به state) — مقداری که از state به دست می‌آید:

const dynamicOffset = useMemo(() => {
  return moveX * 0.5;
}, [moveX]);

قدم ۴: 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);
}, []);

هندلر کلیک (وابسته به state) — رفتار کلیک به رنگ فعلی بستگی دارد:

const handleCubeClick = useCallback(
  (mesh: THREE.Mesh) => {
    const mat = mesh.material as THREE.MeshStandardMaterial;
    mat.color.set(selectedColor);
    setSelectedProduct(mesh.userData as Product);
  },
  [selectedColor]
);

قدم ۵: 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);
}, []);

فقط یک بار اجرا می‌شود و کل موتور سه‌بعدی را راه می‌اندازد: صحنه، دوربین، رندرر، نورها و مکعب‌ها.

قدم ۶: سیستم تعامل (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);
  }
};

با تبدیل موقعیت دوبعدی ماوس به یک اشعه سه‌بعدی، مکعب کلیک‌شده را تشخیص می‌دهد.

قدم ۷: حلقه انیمیشن

const animate = () => {
  requestAnimationFrame(animate);

  cubesRef.current.forEach((cube) => {
    cube.rotation.y += 0.01;
    cube.position.x += dynamicOffset * 0.001;
  });

  renderer.render(scene, camera);
};

در هر فریم اجرا می‌شود، دنیای سه‌بعدی را به‌روز می‌کند و صحنه را پیوسته رندر می‌کند.

قدم ۸: به‌روزرسانی‌های واکنشی (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]);

🚀 ۳. این پروژه چه چیزی یاد می‌دهد

  • React = کنترل‌کننده رابط کاربری
  • Three.js = موتور رندر
  • هوک‌ها = لایه پل

⚡ ۴. کدام هوک‌ها در پروژه‌های واقعی مهم‌ترند؟

🔥 مهم‌ترین: useRef (هسته موتور، حیاتی)، useEffect (مدیریت چرخه عمر، حیاتی)، useState (کنترل رابط کاربری، مهم).

🟡 ثانویه: useMemo (بهینه‌سازی پرفورمنس) و useCallback (پایداری رویدادها).

📌 نکته نهایی

در معماری React + Three.js، React state را مدیریت می‌کند، Three.js رندر را انجام می‌دهد و هوک‌ها این دو دنیا را به شکلی کارآمد به هم وصل می‌کنند.

کد کامل: react-learning-journey در GitHub — کامپوننت Canvas3D.tsx را ببینید.