React中如何在ref.current首次可用时执行初始化操作?
解决方案:优雅处理@react-three/fiber中CameraControls的初始化问题
当然可以实现这样的自定义Hook,同时还有几种适配@react-three/fiber场景的优雅方案,彻底替代定时器的临时 workaround:
方案1:自定义useRefWithInitialCallback Hook
这个Hook会在ref的current属性首次被赋值为非null值时,自动执行你传入的回调函数,完美适配React组件的ref赋值时机。
import { useRef, useState, useCallback } from 'react'; function useRefWithInitialCallback<T>(callback: (ref: T) => void) { const ref = useRef<T>(null); const [hasTriggered, setHasTriggered] = useState(false); const callbackRef = useCallback((instance: T | null) => { ref.current = instance; // 仅在首次赋值非null实例时执行回调 if (instance && !hasTriggered) { callback(instance); setHasTriggered(true); } }, [callback, hasTriggered]); return [ref, callbackRef] as const; }
在你的组件中使用:
const [cameraRef, cameraCallbackRef] = useRefWithInitialCallback<CameraControls>((controls) => { controls.setLookAt( ...cameraInitialPosition, ...cameraInitialTarget, true ); }); return ( <Canvas shadows> <CameraControls ref={cameraCallbackRef} /> {/* 其他3D内容 */} </Canvas> );
这里使用回调式ref替代普通ref,React会在CameraControls组件完成挂载、ref实例赋值时自动调用cameraCallbackRef,直接在回调内完成初始化逻辑,完全避开了useEffect时机不匹配的问题。
方案2:利用@react-three/fiber的useFrame
结合Three.js的渲染帧循环,在每帧检测ref是否就绪,一旦就绪就执行初始化并停止监听:
import { useFrame } from '@react-three/fiber'; const cameraRef = useRef<CameraControls>(null); const [isInitialized, setIsInitialized] = useState(false); useFrame(() => { if (!isInitialized && cameraRef.current) { cameraRef.current.setLookAt( ...cameraInitialPosition, ...cameraInitialTarget, true ); setIsInitialized(true); } }); return ( <Canvas shadows> <CameraControls ref={cameraRef} /> {/* 其他3D内容 */} </Canvas> );
useFrame会在Three.js场景的每帧渲染时执行,确保能精准捕捉到CameraControls实例就绪的时机,不会出现过早执行的问题。
方案3:监听CameraControls的ready事件
drei封装的CameraControls组件自带onReady事件,会在控件完全初始化完成后触发,这是最贴合组件本身的解决方案:
const cameraRef = useRef<CameraControls>(null); const handleReady = () => { if (cameraRef.current) { cameraRef.current.setLookAt( ...cameraInitialPosition, ...cameraInitialTarget, true ); } }; return ( <Canvas shadows> <CameraControls ref={cameraRef} onReady={handleReady} /> {/* 其他3D内容 */} </Canvas> );
内容的提问来源于stack exchange,提问作者Dusan
相关产品推荐
相关产品推荐

