状态管理
状态管理

状态管理

r3f 内部使用 zustand 作为状态管理工具

基本流程

首先来定义内部状态的构成类型:
// 这里只列举部分 // 详细可参考 https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree export type RootState = { /** The instance of the renderer */ gl: THREE.WebGLRenderer /** Default camera */ camera: Camera & { manual?: boolean } /** Default scene */ scene: THREE.Scene /** Default raycaster */ raycaster: THREE.Raycaster /** Event layer interface, contains the event handler and the node they're connected to */ events: EventManager<any> /** Currently used controls */ controls: THREE.EventDispatcher | null /** Normalized event coordinates */ pointer: THREE.Vector2 /** @deprecated Normalized event coordinates, use "pointer" instead! */ mouse: THREE.Vector2 /** Render loop flags */ frameloop: 'always' | 'demand' | 'never' /** Adaptive performance interface */ performance: Performance /** Shortcut to frameloop flags */ setFrameloop: (frameloop?: 'always' | 'demand' | 'never') => void ... }
构建完 store 会同时给到当前 r3f 实例的 Context 中,同时提供 useThree hook 在应用中使用。
function Provider<TCanvas extends Canvas>({ store, children, onCreated, rootElement, }: { onCreated?: (state: RootState) => void store: UseBoundStore<RootState> children: React.ReactNode rootElement: TCanvas }) { useIsomorphicLayoutEffect(() => { const state = store.getState() // Flag the canvas active, rendering will now begin state.set((state) => ({ internal: { ...state.internal, active: true } })) // Notifiy that init is completed, the scene graph exists, but nothing has yet rendered if (onCreated) onCreated(state) // Connect events to the targets parent, this is done to ensure events are registered on // a shared target, and not on the canvas itself if (!store.getState().events.connected) state.events.connect?.(rootElement) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return <context.Provider value={store}>{children}</context.Provider> } export function useStore() { const store = React.useContext(context) if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!') return store } /** * Accesses R3F's internal state, containing renderer, canvas, scene, etc. * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree */ export function useThree<T = RootState>( selector: StateSelector<RootState, T> = (state) => state as unknown as T, equalityFn?: EqualityChecker<T>, ) { return useStore()(selector, equalityFn) }
在定义场景 Canvas 时,通过 props 来传入部分属性,比如 dpr ,在 render 时,通过 configure 方法传递给 store 进行初始化:
function render<TCanvas extends Canvas>( children: React.ReactNode, canvas: TCanvas, config: RenderProps<TCanvas>, ): UseBoundStore<RootState> { const root = createRoot(canvas) root.configure(config) return root.render(children) } configure(props: RenderProps<TCanvas> = {}) { let { frameloop = 'always', ... } = props; let state = store.getState(); // Check frameloop if (state.frameloop !== frameloop) state.setFrameloop(frameloop) ... }
 

useThree

useThree 是一个非常强大的工具,用于在函数组件中获取和操作 Three.js 的场景、渲染器、相机等对象。开发者可以直接访问底层的 Three.js 对象,执行更复杂的操作,同时仍然保持 React 组件的声明性和可维护性。
// 通过 selector 方式,获取鼠标信息,同时避免多余的 rerender const pointer = useThree((state) => state.pointer)
我们来看一个 @react-three/drei (一个封装了非常多工具类和方法的库)中的 useCamera 例子,可以修改某物体在 raycast 时的相机:
// https://github.com/pmndrs/drei/blob/master/src/core/useCamera.tsx export function useCamera(camera: Camera | React.MutableRefObject<Camera>, props?: Partial<Raycaster>) { const pointer = useThree((state) => state.pointer) const [raycast] = React.useState(() => { const raycaster = new Raycaster() /** * applyProps is an internal method of r3f and * therefore requires its first arg to be an * "Instance" a term used with the Reconciler * so we have an expect error to mask this */ // @ts-expect-error if (props) applyProps(raycaster, props, {}) return function (_: Raycaster, intersects: Intersection[]): void { raycaster.setFromCamera(pointer, camera instanceof Camera ? camera : camera.current) const rc = this.constructor.prototype.raycast.bind(this) if (rc) rc(raycaster, intersects) } }) return raycast } // 使用方式 <mesh raycast={useCamera(customCamera)} /> // demo: https://drei.pmnd.rs/?path=/story/misc-usecamera--use-camera-st

useFrame

不同于 useThree 是基于 store 所创建的 hookuseFrame 所提供的能力是在渲染帧前进行一些操作,一些常见的操作是创建一些动画效果比如:
useFrame(() => { // 旋转立方体 cube.rotation.x += 0.01; cube.rotation.y += 0.01; });
useFrame 中同样可以访问到完整的 state 以及时钟 delta,这样可以方便做一些状态的同步和自定义的更新。