You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 13:27:29