如何基于对象变化依赖创建Hook,监听ThreeJS相机缩放更新UI滑块
问题原因分析
- 第一个报错
Variable 'camera' is used before being assigned.ts(2454):
你声明的顶层camera变量仅在组件挂载后执行的useEffect中才会赋值,而useCallback在组件首次渲染初始化时就会读取camera.zoom作为依赖,此时camera还未完成赋值;同时顶层let声明的变量会在组件每次重渲染时重置,之前存储的相机实例会直接丢失。 - 第二个报错
Cannot assign to 'current' because it is a read-only property.ts(2540):
你初始化CameraZoom这个ref时只传了null且未指定泛型,TS会自动推断其current属性为只读的null类型,不允许修改值;另外ref的current属性变化不会触发React的响应式更新,既不能作为useCallback的依赖项触发回调更新,也无法驱动UI滑块刷新。
正确实现方案
实现思路:
- 相机、渲染器、控制器这类持久化实例统一用
useRef存储,避免重渲染丢失 - 缩放值用
useState存储,天然支持响应式更新UI滑块 - 监听控制器变化或者在渲染循环中判断缩放值变动,触发回调更新
- 补全TS泛型定义避免类型报错
import { useRef, useEffect, useState, useCallback } from 'react' import * as THREE from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' // 如果你用轨道控制器控制缩放可引入 type Props = { // 补充你的组件入参定义 } export const SceneComponent = (props: Props): React.ReactElement => { const reactCanvas = useRef<HTMLCanvasElement>(null) // 存储相机实例,指定泛型避免类型报错 const cameraRef = useRef<THREE.PerspectiveCamera | null>(null) const rendererRef = useRef<THREE.WebGLRenderer | null>(null) const controlsRef = useRef<OrbitControls | null>(null) // 缩放值用state存储,驱动UI响应式更新,初始值和相机默认zoom保持一致 const [cameraZoom, setCameraZoom] = useState(1) // 缩放变化回调 const handleCameraZoomChanged = useCallback((newZoom: number) => { console.log('Camera zoom changed:', newZoom) // 这里更新你的缩放滑块组件的值即可 }, []) useEffect(() => { if (!reactCanvas.current) return const canvas = reactCanvas.current const width = canvas.clientWidth const height = canvas.clientHeight const scene = new THREE.Scene() // 初始化相机存入ref const camera = new THREE.PerspectiveCamera(75, width / height, 1, 10000) cameraRef.current = camera // 初始化渲染器 const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }) renderer.setSize(width, height) rendererRef.current = renderer // 若使用轨道控制器控制缩放,可直接监听控制器change事件 const controls = new OrbitControls(camera, renderer.domElement) controlsRef.current = controls controls.addEventListener('change', () => { // 仅当zoom实际变化时更新,避免多余渲染 if (camera.zoom !== cameraZoom) { setCameraZoom(camera.zoom) handleCameraZoomChanged(camera.zoom) } }) // 渲染循环 const renderScene = () => { requestAnimationFrame(renderScene) controls.update() renderer.render(scene, camera) // 如果不用轨道控制器、是手动修改相机zoom的场景,可在这里判断zoom变化 // if (camera.zoom !== cameraZoom) { // setCameraZoom(camera.zoom) // handleCameraZoomChanged(camera.zoom) // } } renderScene() // 组件卸载清理资源 return () => { controls.removeEventListener('change', () => {}) renderer.dispose() } }, [reactCanvas, handleCameraZoomChanged, cameraZoom]) return ( <div className='scene-component'> <Toast ref={toastReload} /> <PlayerBtn /> <SliderSpeed /> <SliderTimeline /> {/* 把缩放值传给滑块组件即可同步UI */} <SliderZoom value={cameraZoom} /> <canvas className='canvas-holder' ref={reactCanvas} {...props} /> </div> ) }
内容的提问来源于stack exchange,提问作者sancelot
相关产品推荐
相关产品推荐

