React中使用Three.js的GLTFLoader加载3D模型后如何获取引用?
解决方案
你可以通过 React 提供的 useRef 或者 useState 来存储 gltf.scene 的引用,根据使用场景二选一即可:
方案1:使用 useRef 存储(推荐)
模型引用属于不需要触发组件重渲染的可变值,用 useRef 存储更符合性能要求,引用在组件生命周期内不会失效:
import { useRef, useEffect } from 'react'; import * as THREE from 'three'; import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; const Render = () => { // 声明存储模型的ref const modelRef = useRef(null); const canvasRef = useRef(null); useEffect(() => { const scene = new THREE.Scene(); // 此处省略相机、渲染器初始化逻辑 const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer({canvas: canvasRef.current}); renderer.setSize(window.innerWidth, window.innerHeight); const loader = new GLTFLoader(); loader.load('scene.gltf', function (gltf) { gltf.scene.scale.set(.1, .1, .1); gltf.scene.position.set(.1, 4.1, .1); scene.add(gltf.scene); // 把模型实例赋值给ref的current属性 modelRef.current = gltf.scene; }, undefined, function (error) { console.error(error); }); // 后续操作模型直接访问modelRef.current即可,比如动画循环逻辑 const animate = () => { requestAnimationFrame(animate); // 模型加载完成后再操作,避免空引用报错 if (modelRef.current) { modelRef.current.rotation.y += 0.01; } renderer.render(scene, camera); } animate(); // 组件卸载时清空ref、销毁实例避免内存泄漏 return () => { modelRef.current = null; renderer.dispose(); } }, []) return <canvas ref={canvasRef} /> }
如果需要把模型暴露给父组件操作,可以配合 forwardRef + useImperativeHandle 把引用转发出去:
// Render组件定义时用forwardRef包裹 const Render = forwardRef((props, ref) => { const modelRef = useRef(null); // 把内部模型引用同步到外部传入的ref useImperativeHandle(ref, () => ({ getModel: () => modelRef.current })) // 剩余初始化逻辑和上方一致 }) // 父组件调用示例 const App = () => { const renderRef = useRef(null); // 父组件内直接操作模型 const handleRotateModel = () => { const model = renderRef.current?.getModel(); if (model) model.rotation.x += Math.PI / 2; } return <> <Render ref={renderRef} /> <button onClick={handleRotateModel}>旋转模型</button> </> }
方案2:使用 useState 存储
如果你需要模型加载完成后触发组件重渲染(比如渲染加载状态、显示模型控制按钮),可以用 useState 存储:
import { useState, useEffect } from 'react'; const Render = () => { const [model, setModel] = useState(null); const canvasRef = useRef(null); useEffect(() => { const scene = new THREE.Scene(); // 省略相机、渲染器初始化逻辑 const loader = new GLTFLoader(); loader.load('scene.gltf', function (gltf) { gltf.scene.scale.set(.1, .1, .1); gltf.scene.position.set(.1, 4.1, .1); scene.add(gltf.scene); // 存储模型到state setModel(gltf.scene); }); // 操作模型直接用state即可,记得做空判断 const animate = () => { requestAnimationFrame(animate); if (model) { model.rotation.y += 0.01; } renderer.render(scene, camera); } animate(); }, [model]) // 要把model加入依赖数组 // 可以根据model是否存在渲染不同内容 return <> {!model && <div className="loading">模型加载中...</div>} <canvas ref={canvasRef} /> </> }
注意事项
- 所有操作模型的逻辑都要加空判断,避免模型还没加载完成时报空指针错误
- 组件卸载时要记得销毁模型、清空引用,避免内存泄漏
内容的提问来源于stack exchange,提问作者Icaro Amorim
相关产品推荐
相关产品推荐

