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
相关产品推荐
相关产品推荐

