Hooks是react-three-fiber的核心

Hooks 钩子

useThree

Hooks 允许你对组件进行特定信息的请求或者绑定。例如,想要参与渲染循环的组件可以使用 useFrame,需要了解 three.js 运行信息或者获取相关功能引用,可以使用 useThree 等等。所有hooks都会在组件卸载后自行清理。

❌你不能像下面代码这样来写:

import { useThree } from '@react-three/fiber'

function App() {
  const { size } = useThree() // This will just crash
  return (
    <Canvas>
      <mesh>

Hooks只能用在Canvas组件内部,因为它们依赖于上下文环境!

✅应该像这样,useThree要放在组件中,然后把组件放到Canvas元素中:

function Foo() {
  const { size } = useThree()
  ...
}

function App() {
  return (
    <Canvas>
      <Foo />

这个Hook能帮你获取到一个对象,这个对象包含了默认的渲染器、场景、你的相机以及等等。它也能给你当前在画面的中canvas的储存还有视口的坐标信息等等。

import { useThree } from '@react-three/fiber'

function Foo() {
  const state = useThree()

这个Hook是响应式的,例如,如果调整浏览器大小,将获得最新的测量结果,同样适用于可能更改的任何状态对象。

State properties

PROP

DESCRIPTION

TYPE

gl

Renderer 渲染器

THREE.WebGLRenderer

scene

Scene 场景

THREE.Scene

camera

Camera 相机

THREE.PerspectiveCamera

raycaster

Default raycaster 射线

THREE.Raycaster

pointer

Contains updated, normalized,centric pointer coordinates 设备(比如鼠标)的坐标信息

THREE.Vector2

mouse

已经废弃,使用pointer代替

THREE.Vector2

clock

Running system clock 运行的系统时钟

THREE.Clock

linear

True when the colorspace is linear 当色彩空间是线性的时候为true

boolean

flat

如果toneMapping被设置使用,那么就是true

boolean

legacy

通过设置THREE.ColorManagement来禁用全局的颜色管理

boolean

frameloop

渲染的模式:always, demand,never

always, demand, never

performance

system regression 系统性能

{ current: number, min: number, max: number, debounce: number, regress: () => void }

size

Canvas画布的尺寸

{ width: number, height: number, top: number, left: number, updateStyle?: boolean }

viewport

以threejs中的单位度量的视窗尺寸

{ width: number, height: number, initialDpr: number, dpr: number, factor: number, distance: number, aspect: number, getCurrentViewport: (camera?: Camera, target?: THREE.Vector3, size?: Size) => Viewport }

xr

XR相关的接口,管理WebXR的渲染

{ connect: () => void, disconnect: () => void }

set

可以让你设置任何状态属性

(state: SetState<RootState>) => void

get

可以获取任何非响应式的状态属性

() => GetState<RootState>

invalidate

是否需要重新渲染,在设置frameloop === ‘demand’才会起作用

() => void

advance

可以手动控制一次渲染,需要设置为frame==='never'才能起作用

(timestamp: number, runGlobalEffects?: boolean) => void

setSize

设置canvas尺寸

(width: number, height: number, updateStyle?: boolean, top?: number, left?: number) => void

setDpr

设置pixel-ratio

(dpr: number) => void

setFrameloop

渲染方式设置的快捷方式

(frameloop?: 'always', 'demand', 'never') => void

setEvents

设置事件的快捷方式

(events: Partial<EventManager<any>>) => void

onPointerMissed

设置当设备(比如鼠标)点击画布中但是没有点中目标

() => void

events

设置Pointer-Events

{ connected: TargetNode, handlers: Events, connect: (target: TargetNode) => void, disconnect: () => void }

Selector

// 只有当默认相机改变的时候才会触发重新渲染
const camera = useThree((state) => state.camera)
// 只有窗口大小发生改变的时候才会触发重新渲染
const viewport = useThree((state) => state.viewport)
// ❌ 像这种的话就不会触发了,因为这是需要的是camera内部属性,不会有响应式的效果
const zoom = useThree((state) => state.camera.zoom)

可以为组件选择属性,这样可以避免那些仅关心特定内容的组件进行不必要的重新渲染。但是需要注意的是,响应性并不包含相应属性的内部的属性。

Reading state from outside of the component cycle

function Foo() {
  const get = useThree((state) => state.get)
  ...
  get() // 在任何地方获取你想要的数据

Exchanging defaults

function Foo() {
  const set = useThree((state) => state.set)
  ...
  useEffect(() => {
    set({ camera: new THREE.OrthographicCamera(...) })
  }, [])

useFrame

这个hook允许你在每一帧渲染时执行代码,例如运行特效、更新控制等等。你会收到state对象(与 useThree 相同)和一个时钟增量。你的回调函数会在每一帧渲染之前被调用。当组件卸载时,它会自动从渲染循环中取消订阅。

import { useFrame } from '@react-three/fiber'

function Foo() {
  useFrame((state, delta, xrFrame) => {
    // 这个函数依据显示设备的刷新率在每次渲染前执行
  })

useFrame 中要小心你做的事情!你不应该在其中使用 setState!你的计算应该尽量简单,同时要注意所有通常与循环处理有关的常见陷阱,例如变量的重复使用等等。

Taking over the render-loop 接手循环渲染

如果你需要对循环渲染有更多的控制,你可以传递一个数字渲染优先级值。这将导致 React Three Fiber 完全禁用自动渲染。现在,渲染将由你负责,这在你处理特效合成器、抬头显示等内容时非常有用。

function Render() {
  // 当设置了渲染优先级参数,那么需要你自己设置相关代码进行渲染
  useFrame(({ gl, scene, camera }) => {
    gl.render(scene, camera)
  }, 1)

function RenderOnTop() {
  // 这个会在Render中的useFrame之后执行
  useFrame(({ gl, ... }) => {
    gl.render(...)
  }, 2)

回调函数将按升序优先级值执行(从最低到最高),类似于 DOM 的 z-order

Negative indices 负数优先级

当把优先级数字设置为负数时,自动的渲染行为不会被禁用,但是可以让你管理在整个组件树种所有useFrame的执行顺序。

function A() {
  // This will execute first
  useFrame(() => ..., -2)

function B() {
  // This useFrame will execute *after* A's
  useFrame(() => ..., -1)

useLoader

这个Hook加载资源并暂停,以便更容易地处理回退和错误。它可以将任何three.js的加载器作为其第一个参数:GLTFLoader,OBJLoader,TextureLoader,FontLoader等。它基于React.Suspense,因此回退处理和错误处理发生在父层级别。

import { Suspense } from 'react'
import { useLoader } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

function Model() {
  const result = useLoader(GLTFLoader, '/model.glb')
  // 不用担心模型加载后的展现问题
  // 我们会保证模型会被正确加载并展示
  return <primitive object={result.scene} />
}

function App() {
  return (
    <Suspense fallback={<FallbackComponent /> /* or null */}>
      <Model />
    </Suspense>
  )
}

通过useLoader加载的资源默认是会被缓存的。根据资源的url来作为资源的缓存标识。这让你能够方便的在组件的任何地方重复使用数据。

要注意小心的处理mutating(改变)或者disposing(销毁),特别是你需要去重用它们的情况下。你可以参考文档中automatic disposal的部分内容。

Loader extensions 加载扩展

可以在使用第三个参数,传入一个回调函数,来对你的loader进行一个配置和额外处理。

import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'

useLoader(GLTFLoader, url, (loader) => {
  const dracoLoader = new DRACOLoader()
  dracoLoader.setDecoderPath('/draco-gltf/')
  loader.setDRACOLoader(dracoLoader)
})

Loading multiple assets at once 同时加载多个资源

可以同时加载多个资源

const [bumpMap, specMap, normalMap] = useLoader(TextureLoader, [url1, url2, url2])

Loading status 加载状态

你可以通过第四个参数,传入一个回调来获取加载的状态。不过请考虑使用替代方案,如THREE.DefaultLoadingManager或者更好的Drei的加载辅助工具。

useLoader(loader, url, extensions, (xhr) => {
  console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
})

Special treatment of GLTFLoaders and all loaders that return a scene prop

如果Hook能找到result.scene属性,则该Hook将自动创建对象和材质集合:{nodes, materials}。这使您可以有选择地构建不可变的场景图。您还可以具体地修改数据,而无需遍历它。GLTFJSX特别依赖于这些数据。

useLoader(loader, url, extensions, (xhr) => {
  console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
})

Pre-loading assets 预加载资源

您可以在全局空间中预加载资源,以便在组件树中安装之前可以预先加载模型。

import { useLoader, useGraph } from '@react-three/fiber'

function Model(url) {
  const scene = useLoader(OBJLoader, url)
  const { nodes, materials } = useGraph(scene)
  return <mesh geometry={nodes.robot.geometry} material={materials.metal} />
}

useGraph

一个可以通过Object3D数据方便地创建一个可记忆(不会在组件重新执行时重复创建)的、命名的对象/材质集合。

import { useLoader, useGraph } from '@react-three/fiber'

function Model(url) {
  const scene = useLoader(OBJLoader, url)
  const { nodes, materials } = useGraph(scene)
  return <mesh geometry={nodes.robot.geometry} material={materials.metal} />
}