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

React+Babylon.js中useState变量在useEffect事件更新后未变化

问题:React + Babylon.js 中无法更新 cameraPosition 状态变量

我正在使用TypeScript、React和Babylon.js尝试更新状态变量cameraPosition,但状态似乎未被更新,监听状态变化的useEffect也没有输出。相关代码如下:

主组件代码:

const camera = scene?.cameras[0];
const prevPositionRef = useRef<Nullable<Vector3>>(null);


const [cameraPosition, setCameraPosition] = useState<Vector3>(
  new Vector3(0, 0, 0)
);

useEffect(() => {
  if (camera) {
    const onAfterInput = () => {
      if (
        !prevPositionRef.current ||
        !prevPositionRef.current.equals(camera.position)
      ) {
        console.log("change") // 相机位置变化时正常打印

        setCameraPosition(camera.position); // 无法更新状态

        prevPositionRef.current = camera.position.clone();
      }
    };

    camera.onAfterCheckInputsObservable.add(() => {
      onAfterInput();
    });

    return () => {
      camera.onAfterCheckInputsObservable.removeCallback(() => {
        onAfterInput();
      });
    };
  }
}, [camera]);

console.log(cameraPosition) // 不打印更新后的位置

监听状态变化的代码:

useEffect(() => {
  console.log("cameraPosition updated:", cameraPosition);
}, [cameraPosition]);

第一个console.log能在相机位置变化时正常打印,但第二个监听状态的console.log无输出,我希望能在其他组件中使用cameraPosition,请问问题出在哪里?


问题原因

  1. 引用类型的浅比较机制:
    Babylon.js的Vector3是引用类型,camera.position始终指向同一个对象实例。React的useState更新时会进行浅比较,当你传递camera.position给setCameraPosition时,状态的引用没有发生变化,React会认为状态未更新,因此不会触发重渲染,也不会执行依赖cameraPosition的useEffect。

  2. 清理函数的错误:
    在useEffect的清理函数中,你创建了一个新的匿名函数() => { onAfterInput(); }来移除回调,但这个函数和之前添加的回调不是同一个引用,导致回调无法被正确移除,可能引发内存泄漏,但这不是状态不更新的直接原因。


解决方案

1. 传递克隆后的Vector3对象

调用setCameraPosition时,传入camera.position.clone(),这样每次都会创建一个新的Vector3实例,React能检测到引用变化,从而触发状态更新和重渲染:

setCameraPosition(camera.position.clone());

2. 修复清理函数

保存添加的回调函数引用,移除时直接使用这个引用,确保回调能被正确清理:

useEffect(() => {
  if (camera) {
    const onAfterInput = () => {
      if (
        !prevPositionRef.current ||
        !prevPositionRef.current.equals(camera.position)
      ) {
        console.log("change");
        setCameraPosition(camera.position.clone());
        prevPositionRef.current = camera.position.clone();
      }
    };

    // 保存回调引用
    const callback = () => onAfterInput();
    camera.onAfterCheckInputsObservable.add(callback);

    return () => {
      // 使用保存的引用移除回调
      camera.onAfterCheckInputsObservable.removeCallback(callback);
    };
  }
}, [camera]);

修改后的完整代码示例

import { useState, useEffect, useRef } from 'react';
import { Vector3, Nullable } from '@babylonjs/core';

// 假设scene是已获取的Babylon.js场景实例
const YourComponent = ({ scene }) => {
  const camera = scene?.cameras[0];
  const prevPositionRef = useRef<Nullable<Vector3>>(null);

  const [cameraPosition, setCameraPosition] = useState<Vector3>(
    new Vector3(0, 0, 0)
  );

  useEffect(() => {
    if (camera) {
      const onAfterInput = () => {
        if (
          !prevPositionRef.current ||
          !prevPositionRef.current.equals(camera.position)
        ) {
          console.log("change");
          // 传递克隆后的Vector3
          setCameraPosition(camera.position.clone());
          prevPositionRef.current = camera.position.clone();
        }
      };

      // 保存回调引用
      const callback = () => onAfterInput();
      camera.onAfterCheckInputsObservable.add(callback);

      // 使用引用移除回调
      return () => {
        camera.onAfterCheckInputsObservable.removeCallback(callback);
      };
    }
  }, [camera]);

  // 监听状态变化
  useEffect(() => {
    console.log("cameraPosition updated:", cameraPosition);
  }, [cameraPosition]);

  return (
    // 你的组件内容
    <div>Camera Position: {cameraPosition.x}, {cameraPosition.y}, {cameraPosition.z}</div>
  );
};

export default YourComponent;

内容的提问来源于stack exchange,提问作者pcace

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 17:44:59