React Three Fiber:React 生态中的 3D 渲染方案
React Three Fiber(简称 R3F)是 Three.js 的 React 渲染器,它让你能够像写普通 React 组件一样,用声明式的 JSX 语法构建 3D 场景。与直接在 React 生命周期中手动操作 Three.js 对象的命令式写法相比,R3F 能让代码更清晰、可复用、易于维护。
一、核心概念:声明式 3D 编程
R3F 的本质是一个自定义 React 协调器,它能够将 JSX 元素转换成 Three.js 对象。当你写<mesh />时,背后实际执行的是new THREE.Mesh(),并将 props 作为属性应用到该实例上。
与传统的 Three.js 代码对比,差异一目了然:
命令式(纯 Three.js):
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); // 还需手动管理渲染循环、清理等...声明式(R3F):
const App = () => ( <Canvas camera={{ position: [0, 0, 5] }}> <ambientLight /> <mesh> <boxGeometry /> <meshStandardMaterial color="orange" /> </mesh> </Canvas> );<Canvas />组件是 R3F 的入口,它会自动创建场景、相机、渲染器,并处理渲染循环。你只需要关注"放什么"和"怎么动",而不是底层如何实现。
二、快速开始
1. 安装依赖:
npm install three @react-three/fiber@react-three/fiber依赖three作为对等依赖,必须一并安装。
2. 基础场景示例:
import { Canvas, useFrame } from '@react-three/fiber'; import { useRef, useState } from 'react'; function RotatingBox() { const meshRef = useRef(); const [hovered, setHover] = useState(false); const [clicked, setClick] = useState(false); // 每一帧执行:让立方体旋转 useFrame((state, delta) => { meshRef.current.rotation.x += delta; // 使用 delta 确保动画速度与帧率解耦 }); return ( <mesh ref={meshRef} scale={clicked ? 1.5 : 1} onClick={() => setClick(!clicked)} onPointerOver={() => setHover(true)} onPointerOut={() => setHover(false)} > <boxGeometry args={[1, 1, 1]} /> <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} /> </mesh> ); } export default function App() { return ( <Canvas> <ambientLight intensity={0.5} /> <pointLight position={[10, 10, 10]} /> <RotatingBox /> </Canvas> ); }⚠️注意:使用meshStandardMaterial或meshPhysicalMaterial等基于物理的材质时,必须添加光源才能看到物体,否则物体将显示为黑色。
3. TypeScript 支持:
R3F 提供了完整的 TypeScript 类型定义。你可以通过ThreeElements来为自定义组件声明 props 类型:
import { ThreeElements } from '@react-three/fiber'; function Box(props: ThreeElements['mesh']) { // ... }三、核心组件与 Hooks
1. Canvas 组件核心属性
Canvas 组件是 R3F 的根容器,支持以下常用配置属性:
属性 | 默认值 | 说明 |
|
| 相机配置 |
|
| 渲染模式: (持续)/ (按需)/ (停止) |
|
| 是否开启阴影映射 |
|
| 设备像素比范围 |
|
| 设为 时禁用色调映射 |
|
| 是否使用正交相机 |
2. 核心 Hooks 详解
Hook | 用途 | 典型场景 |
| 在每一帧动画中执行回调 | 连续动画(旋转、移动)、物理模拟、实时数据更新 |
| 访问 Three.js 核心对象(scene、camera、gl、size、viewport) | 获取相机位置、窗口尺寸、手动控制渲染 |
| 加载并缓存 3D 资源(模型、纹理等) | 加载 glTF 模型、图片纹理 |
| 处理加载后的 glTF 模型,分离场景与动画 | 复杂动画模型的操作 |
3. useFrame 使用示例:线速度与 lerp 缓动
import { useFrame } from '@react-three/fiber'; import { useRef } from 'react'; import * as THREE from 'three'; function AnimatedCube() { const meshRef = useRef(); useFrame((state, delta) => { // ✅ 正确:使用 delta 确保帧速率独立 meshRef.current.rotation.x += delta; meshRef.current.rotation.y += delta * 0.8; // ✅ lerp(线性插值):实现平滑跟随 // 目标位置随鼠标移动 meshRef.current.position.x = THREE.MathUtils.lerp( meshRef.current.position.x, state.mouse.x * 2, // 目标值 0.1 // 插值系数 ); }); return <mesh ref={meshRef}>{/* ... */}</mesh>; }使用 delta 的关键意义:如果直接用+= 0.01,在高刷新率屏幕(144Hz)上物体旋转会比低刷新率屏幕(60Hz)快得多。而delta是两帧之间的时间间隔(单位:秒),用它乘以固定速度值可确保物体无论在任何刷新率下都以相同的实际速度运动。
四、扩展工具链:@react-three/drei
@react-three/drei是 R3F 的官方扩展库,提供了大量预置的辅助组件,能大幅提升开发效率:
类别 | 组件 | 功能 |
相机控制 |
| 鼠标拖拽旋转/缩放场景 |
模型加载 |
| 一行代码加载并缓存 glTF 模型 |
辅助调试 |
、 、 | 性能面板/参考网格 |
3D 文字 |
| 在 3D 空间渲染文字 |
2D 元素 |
、 | 在 3D 空间中嵌入 HTML/2D 平面 |
光影增强 |
、 、 | 环境贴图/天空盒/高级灯光 |
后处理特效 |
、 | 辉光、景深、动态模糊等 |
模型优化 |
、 | 预加载和优化模型/纹理性能 |
物理引擎 |
| 集成 Rapier 物理引擎 |
五、状态管理
R3F 自身使用 Zustand 作为内部状态管理,因此两者天然契合。推荐使用 Zustand 管理需要跨组件共享或影响多个 3D 物体的状态:
// store.js import { create } from 'zustand'; const useStore = create((set) => ({ color: '#ff0000', setColor: (newColor) => set({ color: newColor }), rotationSpeed: 0.01, setRotationSpeed: (speed) => set({ rotationSpeed: speed }), })); // 在组件中使用 function ColoredBox() { const color = useStore((state) => state.color); const rotationSpeed = useStore((state) => state.rotationSpeed); const meshRef = useRef(); useFrame((state, delta) => { meshRef.current.rotation.x += rotationSpeed * delta; }); return ( <mesh ref={meshRef}> <boxGeometry /> <meshStandardMaterial color={color} /> </mesh> ); }设计原则:
- 分离 3D 场景逻辑与应用状态:将用户交互(如按钮、滑块)关联的状态用 Zustand 或 Context 管理,与 3D 渲染逻辑解耦,能显著降低主线程阻塞
- 状态更新方式的选择:需要触发 React 组件重新渲染的(如切换颜色),用 Zustand store;仅在每一帧动画中连续变化且不需要驱动 UI 更新的(如物体旋转角度),直接在
useFrame中用ref进行可变操作
六、性能优化最佳实践
在 R3F 中,Three.js 的世界与 React 的渲染机制需要协同工作。以下是最关键的优化准则:
1. 共享几何体和材质,减少创建开销
Three.js 对象的创建会涉及 GPU 资源分配和编译,代价较高。在组件内复用几何体和材质是首要优化手段。
// ✅ 正确:使用 useMemo 共享几何体和材质 const geometry = useMemo(() => new THREE.BoxGeometry(1, 1, 1), []); const material = useMemo(() => new THREE.MeshStandardMaterial({ color: 'blue' }), []); return ( <> {positions.map(pos => <mesh geometry={geometry} material={material} position={pos} />)} </> );2. 使用 instance 处理大量相同物体
需要渲染上百个相同形状的对象时,使用 InstancedMesh 替代多个独立 Mesh,可将绘制调用从 N 次减少为 1 次。
3. 避免在 useFrame 中使用 setState
频繁的 React 状态更新会触发组件重新渲染,带来不必要的性能开销。性能敏感的连续动画应直接通过ref.current进行可变更新。
// ❌ 错误:在 useFrame 中使用 setState const [x, setX] = useState(0); useFrame(() => setX(x => x + 0.1)); return <mesh position-x={x} />; // ✅ 正确:使用 ref 直接修改 const meshRef = useRef(); useFrame((state, delta) => { meshRef.current.position.x += delta; }); return <mesh ref={meshRef} />;4. 控制显隐优于挂载/卸载
频繁挂载/卸载 Three.js 对象会导致材质重新编译、几何体重新处理。用visible属性控制显隐开销低得多。
// ❌ 差:频繁挂载卸载 {isVisible && <mesh>...</mesh>} // ✅ 好:切换可见性 <mesh visible={isVisible}>...</mesh>5. 使用 useLoader 缓存资产
import { useLoader } from '@react-three/fiber'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; function Model() { // 自动缓存,多个组件加载同一模型只发一次网络请求 const gltf = useLoader(GLTFLoader, '/model.glb'); return <primitive object={gltf.scene} />; }useLoader实现了资产级别的缓存,无论组件加载多少次,模型和纹理都只下载并解析一次。
6. 性能监控
使用浏览器 DevTools Performance 面板监测帧率和绘制调用数,确保目标达到 60 FPS。
七、常见陷阱与解决
陷阱 | 原因 | 解决方案 |
模型全黑 | 使用 PBR 材质但未添加光源 | 添加 + |
动画速度异常 | 未使用 |
|
性能下降 | 在 中调用 | 用 + 直接修改属性 |
内存泄漏 | 组件卸载时未清理 Three.js 资源 | 使用 (自动缓存)或 返回 函数 |
纹理加载慢 | 大量高清纹理未压缩 | 使用 KTX2 格式 + 缓存 |
模型不显示 | 相机位置不当或模型尺寸过小 | 添加 和 辅助调试 |
z-index 不生效 | 在 3D 空间中使用了 CSS 概念 | 使用 组件或调整渲染顺序( ) |
八、总结
你的需求 | 推荐方案 |
快速搭建 3D 场景 | R3F + Canvas + Drei 的 |
需要状态管理 | Zustand(与 UI 层共享)+ ref 直接修改(动画变量) |
加载模型 |
或 Drei 的 |
需要物理效果 |
或 |
性能优化 |
共享几何体、避免 在 、切换 而非挂载 |
需要后期特效 | Drei 的 + 等 |
处理复杂模型动画 |
分离场景与动画轨道 |
需要调试辅助 | Drei 的 + + |
核心原则:
在 R3F 中,Three.js 的 3D 世界与 React 的声明式 UI 融合,但两者各有其性能模型——让 React 管理"是什么"和"何时变",让 Three.js 管理"如何画"。将长期运行的动画和频繁更新的属性交给useFrame+ref直接操作,将需要响应式更新的 UI 状态交给 Zustand 或 React state,你的应用就能既流畅又清晰。
⚠️ 最后提醒:
用 R3F 开发 Web3D 虽然降低了 Three.js 入门门槛,但仍需对 3D 基础概念(坐标系、矩阵、灯光类型、着色器)有基本理解,否则在排查性能或渲染异常时会遇到不小的阻力。R3F 官方文档中列出的各项陷阱和性能建议,建议在实际项目前仔细阅读。
