You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Three.js点击按钮增删3D托盘时添加后立即消失问题求助

修复后的完整代码示例

import { useRef, useState, useEffect } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

// 补全createCube函数(如果你的实现不同,替换成自己的即可)
const createCube = (width, height, depth) => {
  const geometry = new THREE.BoxGeometry(width, height, depth);
  const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
  return new THREE.Mesh(geometry, material);
};

export default function PalletScene() {
  const canvasRef = useRef(null);
  const containerSize = { width: 6.1, height: 2.4, depth: 2.6 };
  const palletEx = { width: containerSize.width / 2, height: 0.2, depth: containerSize.depth };
  const palletTypeA = { id: 'typeA', width: 1.1, height: 0.15, depth: 1.1 };
  const [palletState, setPalletState] = useState(0);
  const [pallet, setPallet] = useState(null);
  const rendererRef = useRef(null);
  const sceneRef = useRef(null);
  const [pallets, setPallets] = useState([]);
  // 用useRef存储camera,避免闭包导致的引用失效
  const camera = useRef(new THREE.PerspectiveCamera(50, 1.6, 0.1, 1000)).current;

  useEffect(() => {
    const canvas = canvasRef.current;
    const container = canvas.parentElement;
    const scene = new THREE.Scene();
    sceneRef.current = scene;

    scene.background = new THREE.Color(0xFFFFFF);

    const renderer = new THREE.WebGLRenderer({
      canvas,
      antialias: true,
    });
    rendererRef.current = renderer;

    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(container.clientWidth, container.clientHeight);

    const controls = new OrbitControls(camera, renderer.domElement);
    camera.position.x = -1;
    camera.position.y = 3;
    camera.position.z = 25;

    // 添加容器和示例托盘
    const containerCube = createCube(containerSize.width, containerSize.height, containerSize.depth);
    scene.add(containerCube);
    containerCube.position.set(0, 0, 0);

    const palletExCube = createCube(palletEx.width, palletEx.height, palletEx.depth);
    scene.add(palletExCube);
    palletExCube.position.set(0, 0, 0);

    // 处理窗口大小变化,保证场景自适应
    const handleResize = () => {
      renderer.setSize(container.clientWidth, container.clientHeight);
      camera.aspect = container.clientWidth / container.clientHeight;
      camera.updateProjectionMatrix();
    };
    window.addEventListener('resize', handleResize);

    // 动画循环
    const animate = () => {
      requestAnimationFrame(animate);
      renderer.render(scene, camera);
    };
    animate();

    // 组件卸载时清理资源,避免内存泄漏
    return () => {
      window.removeEventListener('resize', handleResize);
      renderer.dispose();
    };
  }, [camera]);

  const addPallet = () => {
    const newPallet = createCube(palletTypeA.width, palletTypeA.height, palletTypeA.depth);
    sceneRef.current.add(newPallet);
    newPallet.position.set(Math.random() * 10, Math.random() * 10, Math.random() * 10);
    // 函数式更新获取最新状态,避免状态滞后
    setPallets(prev => [...prev, newPallet]);
  };

  const removeLastPallet = () => {
    setPallets(prev => {
      if (prev.length === 0) return prev;
      // 复制原数组并删除最后一项,保证状态不可变
      const newPalletList = [...prev];
      const removedPallet = newPalletList.pop();
      sceneRef.current.remove(removedPallet);
      return newPalletList;
    });
  };

  return (
    <div>
      <button onClick={addPallet}>添加托盘</button>
      <button onClick={removeLastPallet}>删除最后一个托盘</button>
      <canvas ref={canvasRef} style={{ width: '100%', height: '80vh' }} />
    </div>
  );
}

核心修复说明

  • 按钮事件绑定:直接在React的JSX按钮上绑定onClick,无需手动操作DOM元素,符合React的状态驱动开发模式。
  • 状态更新规范:删除托盘时使用函数式更新,通过复制原数组再修改的方式保证状态不可变性,让React能正确识别状态变化并触发组件更新。
  • Camera引用优化:用useRef存储camera,避免闭包导致的引用失效问题,确保动画渲染时camera的引用始终正确。
  • 资源清理与自适应:添加窗口大小变化监听和组件卸载时的资源清理逻辑,避免内存泄漏,同时保证场景在窗口尺寸变化时正常显示。

内容的提问来源于stack exchange,提问作者lucas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 18:07:00