React Three Fiber懒加载模型组件触发无限重渲染问题求助
问题描述
在React Three Fiber中尝试懒加载JSX模型组件,这些组件通过drei库的useGLTF()加载实际的.gltf/.glb模型。使用ModelsLoader组件按需懒加载时,即便使用了React的memo()或useMemo(),组件仍持续触发无限重渲染,不确定该实现方式是否可行,寻求解决方法。
模型组件示例
import React, { useRef } from 'react'; import { useGLTF } from '@react-three/drei'; export default function OCF155(props) { const { nodes } = useGLTF('models/EXAMPLE_MODEL.glb'); const ref = useRef(); return ( <group {...props} ref={ref} dispose={null} type='TopParent'> <mesh name='EXAMPLE_MODEL' castShadow receiveShadow geometry={nodes.EXAMPLE_MODEL.geometry} material={nodes.EXAMPLE_MODEL.material} rotation={[Math.PI / 2, 0, 0]} scale={1} /> </group> ); }
当前使用memo()的ModelsLoader组件代码
import React, { memo, lazy } from 'react'; import Selection from './helpers/Selection'; const ModelsLoader = memo(function ModelsLoader({ models }) { return ( <> {models && models.length > 0 && models.map((model, index) => { const ModelComponent = lazy(() => import(`./models/${model.name}`)); return ( <Selection key={index}> <ModelComponent position={model.position} rotation={model.rotation} /> </Selection> ); })} </> ); }); export default ModelsLoader;
原本预期只要models列表在渲染过程中不发生变化,memo()就能让组件仅渲染一次,但实际结果不符合预期。
解决方案
1. 核心问题定位
你在map循环的渲染阶段动态执行lazy(() => import(...)),这会导致每次渲染时ModelComponent都是全新的组件引用。React会将其识别为不同的组件,进而销毁旧组件、重新挂载新组件,最终触发无限重渲染循环。
2. 分步修复
步骤1:缓存懒加载组件,避免重复创建
不要在渲染阶段动态生成lazy组件,而是通过缓存对象存储已加载的组件,确保同一模型的组件引用始终稳定:
import React, { memo, lazy, useMemo } from 'react'; import Selection from './helpers/Selection'; // 缓存已加载的模型组件,避免重复创建lazy引用 const modelComponentCache = {}; const ModelsLoader = memo(function ModelsLoader({ models }) { // 用useMemo缓存处理后的模型组件列表,仅当models变化时重新计算 const processedModels = useMemo(() => { if (!models || models.length === 0) return []; return models.map(model => { // 优先从缓存取组件,无则创建并缓存 if (!modelComponentCache[model.name]) { modelComponentCache[model.name] = lazy(() => import(`./models/${model.name}`)); } return { ...model, Component: modelComponentCache[model.name] }; }); }, [models]); return ( <> {processedModels.map((model) => ( {/* 优先用模型name作为key,避免index导致的组件复用问题 */} <Selection key={model.name}> <model.Component position={model.position} rotation={model.rotation} /> </Selection> ))} </> ); }); export default ModelsLoader;
步骤2:优化模型组件的稳定性
给模型组件包裹memo,避免props未变化时的不必要重渲染(useGLTF本身有缓存,但组件层面的优化更稳妥):
import React, { useRef, memo } from 'react'; import { useGLTF } from '@react-three/drei'; const OCF155 = memo(function OCF155(props) { const { nodes } = useGLTF('models/EXAMPLE_MODEL.glb'); const ref = useRef(); return ( <group {...props} ref={ref} dispose={null} type='TopParent'> <mesh name='EXAMPLE_MODEL' castShadow receiveShadow geometry={nodes.EXAMPLE_MODEL.geometry} material={nodes.EXAMPLE_MODEL.material} rotation={[Math.PI / 2, 0, 0]} scale={1} /> </group> ); }); export default OCF155;
步骤3:确保models props的引用稳定
如果父组件传递的models是每次渲染都重新创建的数组(比如直接在render中定义models=[{...}]),memo会因为数组引用变化而失效。需要在父组件中用useMemo缓存models:
// 父组件示例 import { useMemo } from 'react'; import ModelsLoader from './ModelsLoader'; function ParentComponent() { // 用useMemo缓存models数组,确保引用稳定 const models = useMemo(() => [ { name: 'OCF155', position: [0, 0, 0], rotation: [0, 0, 0] }, // 其他模型配置 ], []); return <ModelsLoader models={models} />; }
3. 额外注意事项
- 必须配合
Suspense使用懒加载组件,在ModelsLoader的父组件外层添加加载状态:
import { Suspense } from 'react'; // 父组件中 <Suspense fallback={<div>模型加载中...</div>}> <ModelsLoader models={models} /> </Suspense>
- 避免使用
index作为组件key,优先使用模型唯一标识(如name),防止列表顺序变化时组件错误复用。
内容的提问来源于stack exchange,提问作者Jakub Pabis
相关产品推荐
相关产品推荐

