React-Three-Fiber中useRef异常:treeRefs持续增长致动画故障
问题分析
你的核心问题是每次组件渲染时都在往treeRefs.current中重复添加Tree的ref,导致ref数组长度每帧翻倍,最终引发内存占用过高、动画卡顿。使用ScrollControls后,滚动触发的重渲染会让这个问题更严重。
修复方案
1. 初始化固定长度的ref数组,避免重复添加
不要在渲染时动态push ref,而是在组件初始化时根据树的数量创建固定长度的ref数组:
import { useRef, useEffect } from 'react'; import { Tree } from './Tree'; const TREE_COUNT = 10; // 自定义树的数量 export const Forest = () => { // 初始化固定长度的ref数组,每个元素为独立ref const treeRefs = useRef(Array.from({ length: TREE_COUNT }, () => useRef(null))); useEffect(() => { const animationFrame = requestAnimationFrame(updateTreePositions); function updateTreePositions() { treeRefs.current.forEach((treeRef) => { if (!treeRef.current) return; // 更新Z轴位置 treeRef.current.position.z -= 0.1; // 超出范围后重置位置 if (treeRef.current.position.z <= 0) { treeRef.current.position.z = Math.random() * 50 + 20; treeRef.current.position.x = Math.random() * 40 - 20; } }); requestAnimationFrame(updateTreePositions); } return () => cancelAnimationFrame(animationFrame); }, []); return ( <group> {Array.from({ length: TREE_COUNT }, (_, index) => ( <Tree key={index} ref={treeRefs.current[index]} initialPosition={{ x: Math.random() * 40 - 20, z: Math.random() * 50 + 20 }} /> ))} </group> ); };
2. 确保Tree组件正确转发ref
Tree组件需要使用forwardRef接收父组件传递的ref,否则无法正确绑定到3D网格:
import { forwardRef, useEffect, useRef } from 'react'; import { Mesh } from '@react-three/fiber'; export const Tree = forwardRef(({ initialPosition }, ref) => { const meshRef = useRef(null); useEffect(() => { if (meshRef.current) { meshRef.current.position.x = initialPosition.x; meshRef.current.position.z = initialPosition.z; } }, [initialPosition]); // 将mesh的ref转发给父组件 useEffect(() => { if (ref) ref.current = meshRef.current; }, [ref]); return ( <Mesh ref={meshRef}> {/* 这里放置树的几何体和材质 */} </Mesh> ); }); Tree.displayName = 'Tree';
3. 配合ScrollControls的优化
如果使用ScrollControls,优先用R3F的useFrame钩子替代手动requestAnimationFrame,它会自动和ScrollControls的渲染周期同步:
import { useFrame } from '@react-three/fiber'; // 在Forest组件中替换useEffect为useFrame useFrame(() => { treeRefs.current.forEach((treeRef) => { if (!treeRef.current) return; treeRef.current.position.z -= 0.1; if (treeRef.current.position.z <= 0) { treeRef.current.position.z = Math.random() * 50 + 20; treeRef.current.position.x = Math.random() * 40 - 20; } }); });
关键注意点
- 禁止在组件顶层渲染代码(比如return的循环里)往ref数组push元素,这会导致每次渲染都新增ref。
- 必须用
forwardRef确保子组件的3D网格ref能正确传递给父组件。 - 优先使用R3F原生的
useFrame处理动画,避免和ScrollControls的渲染逻辑冲突。
内容的提问来源于stack exchange,提问作者Kaushal Panchal
相关产品推荐
相关产品推荐

