React FC中THREE.js SideBottleScene实例内存泄漏修复
解决React + Three.js路由切换内存泄漏问题
1. 正确持久化实例引用并绑定卸载清理
用useRef持有SideBottleScene实例,避免因状态更新导致引用丢失,同时确保useEffect的清理函数在组件卸载时触发:
import { useEffect, useRef } from 'react'; import SideBottleScene from './SideBottleScene'; const SideBottle: React.FC = () => { // 用useRef持久化实例,TypeScript指定允许null const sceneInstanceRef = useRef<SideBottleScene | null>(null); useEffect(() => { // 创建实例并赋值给ref sceneInstanceRef.current = new SideBottleScene(/* 传入容器、配置等参数 */); // 清理函数:组件卸载时销毁实例 return () => { if (sceneInstanceRef.current) { sceneInstanceRef.current.destroy(); // 清空引用,帮助GC回收 sceneInstanceRef.current = null; } }; }, []); // 空依赖确保只在挂载/卸载时执行 return <div ref={/* 绑定Three.js渲染容器的ref */}></div>; }; export default SideBottle;
2. 完善SideBottleScene的destroy方法(彻底清理Three.js资源)
确保destroy方法完整释放GL上下文、3D资源和事件监听,TypeScript类型问题通过可选属性或类型断言处理:
import * as THREE from 'three'; class SideBottleScene { // 将实例属性设为可选类型,允许赋值为null private scene?: THREE.Scene; private renderer?: THREE.WebGLRenderer; private mesh?: THREE.Mesh; private keyboardListener?: (e: KeyboardEvent) => void; constructor(container: HTMLElement) { // 初始化场景、渲染器、模型等逻辑 this.scene = new THREE.Scene(); this.renderer = new THREE.WebGLRenderer({ antialias: true }); container.appendChild(this.renderer.domElement); // 示例:绑定键盘事件 this.keyboardListener = (e) => { /* 事件逻辑 */ }; window.addEventListener('keydown', this.keyboardListener); } public destroy(): void { // 移除事件监听 if (this.keyboardListener) { window.removeEventListener('keydown', this.keyboardListener); this.keyboardListener = undefined; } // 清理场景中的3D对象 if (this.scene) { // 遍历并销毁所有子对象的几何体、材质 this.scene.traverse((obj) => { if (obj instanceof THREE.Mesh) { obj.geometry.dispose(); // 材质可能是数组或单个对象 if (Array.isArray(obj.material)) { obj.material.forEach(material => material.dispose()); } else { obj.material.dispose(); } } }); // 清空场景 this.scene.clear(); this.scene = null; } // 销毁渲染器并释放GL上下文 if (this.renderer) { // 从DOM中移除canvas元素 this.renderer.domElement.remove(); // 释放WebGL资源 this.renderer.dispose(); // 强制丢失GL上下文(关键:彻底释放GPU资源) (this.renderer as THREE.WebGLRenderer).forceContextLoss(); this.renderer = null; } // 清空模型引用 this.mesh = null; } } export default SideBottleScene;
3. 验证组件卸载逻辑(确保路由切换时组件被卸载)
如果清理函数仍未执行,检查路由配置:
- 确认没有使用缓存组件的逻辑(如自定义
keep-alive) - 可通过在组件中添加监听验证卸载:
若路由切换时未打印日志,说明组件未被卸载,需调整路由或组件挂载逻辑。useEffect(() => { return () => { console.log('SideBottle组件已卸载'); }; }, []);
4. 路由切换时强制清理(兜底方案)
如果组件因某些原因未被卸载,可监听路由变化手动触发销毁:
import { useLocation, useHistory } from 'react-router-dom'; const SideBottle: React.FC = () => { const location = useLocation(); const history = useHistory(); const sceneInstanceRef = useRef<SideBottleScene | null>(null); useEffect(() => { // 初始化实例... // 监听路由变化,当离开当前路由时销毁实例 const handleRouteChange = () => { // 替换为当前组件对应的路由路径 if (location.pathname !== '/bottle-list' && sceneInstanceRef.current) { sceneInstanceRef.current.destroy(); sceneInstanceRef.current = null; } }; const unsubscribe = history.listen(handleRouteChange); return () => { unsubscribe(); handleRouteChange(); }; }, [location.pathname, history]); // ... };
内容的提问来源于stack exchange,提问作者nok
相关产品推荐
相关产品推荐

