Performance pitfalls
Performance pitfalls
const geom = useMemo(() => new BoxGeometry(), [])
const mat = useMemo(() => new MeshBasicMaterial(), [])
return items.map(i => <mesh geometry={geom} material={mat} ...
如下面这样尽可能复用:
import * as THREE from "three";
import React, { useRef, useMemo, useState, useEffect } from "react";
import { Canvas, extend, useThree, useFrame } from "@react-three/fiber";
import niceColors from "nice-color-palettes";
import { Effects } from "@react-three/drei";
import { SSAOPass, UnrealBloomPass } from "three-stdlib";
extend({ SSAOPass, UnrealBloomPass });
const tempObject = new THREE.Object3D();
const tempColor = new THREE.Color();
const data = Array.from({ length: 1000 }, () => ({
color: niceColors[17][Math.floor(Math.random() * 5)],
scale: 1,
}));
export function App() {
return (
<Canvas
gl={{ antialias: false }}
camera={{ position: [0, 0, 15], near: 5, far: 20 }}
>
<color attach="background" args={["#f0f0f0"]} />
<Boxes />
<Post />
</Canvas>
);
}
function Boxes() {
const [hovered, set] = useState();
const colorArray = useMemo(
() =>
Float32Array.from(
new Array(1000)
.fill()
.flatMap((_, i) => tempColor.set(data[i].color).toArray())
),
[]
);
const meshRef = useRef();
const prevRef = useRef();
useEffect(() => void (prevRef.current = hovered), [hovered]);
useFrame((state) => {
const time = state.clock.getElapsedTime();
meshRef.current.rotation.x = Math.sin(time / 4);
meshRef.current.rotation.y = Math.sin(time / 2);
let i = 0;
for (let x = 0; x < 10; x++)
for (let y = 0; y < 10; y++)
for (let z = 0; z < 10; z++) {
const id = i++;
tempObject.position.set(5 - x, 5 - y, 5 - z);
tempObject.rotation.y =
Math.sin(x / 4 + time) +
Math.sin(y / 4 + time) +
Math.sin(z / 4 + time);
tempObject.rotation.z = tempObject.rotation.y * 2;
if (hovered !== prevRef.Current) {
(id === hovered
? tempColor.setRGB(10, 10, 10)
: tempColor.set(data[id].color)
).toArray(colorArray, id * 3);
meshRef.current.geometry.attributes.color.needsUpdate = true;
}
const scale = (data[id].scale = THREE.MathUtils.lerp(
data[id].scale,
id === hovered ? 2.5 : 1,
0.1
));
tempObject.scale.setScalar(scale);
tempObject.updateMatrix();
meshRef.current.setMatrixAt(id, tempObject.matrix);
}
meshRef.current.instanceMatrix.needsUpdate = true;
});
return (
<instancedMesh
ref={meshRef}
args={[null, null, 1000]}
onPointerMove={(e) => (e.stopPropagation(), set(e.instanceId))}
onPointerOut={(e) => set(undefined)}
>
<boxGeometry args={[0.6, 0.6, 0.6]}>
<instancedBufferAttribute
attach="attributes-color"
args={[colorArray, 3]}
/>
</boxGeometry>
<meshBasicMaterial toneMapped={false} vertexColors />
</instancedMesh>
);
}
function Post() {
const { scene, camera } = useThree();
return (
<Effects disableGamma>
<sSAOPass args={[scene, camera]} kernelRadius={0.5} maxDistance={0.1} />
<unrealBloomPass threshold={0.9} strength={0.75} radius={0.5} />
</Effects>
);
}
Avoid setState in loops
即使
❌ setState in loops is bad
const [x, setX] = useState(0)
useFrame(() => setX((x) => x + 0.1))
return <mesh position-x={x} />
❌ setState in fast intervals is bad
useEffect(() => {
const interval = setInterval(() => setX((x) => x + 0.1), 1)
return () => clearInterval(interval)
}, [])
❌ setState in fast events is bad
<mesh onPointerMove={(e) => setX((x) => e.point.x)} />
一般来说,你应该倾向于使用
const ref = useRef();
useFrame((state, delta) => (ref.current.position.x += delta));
return <mesh ref={ref} />;
// Same goes for events, use references.
<mesh onPointerMove={(e) => (ref.current.position.x = e.point.x)} />;
// If you must use intervals, use references as well, but keep in mind that this is not refresh-rate independent.
useEffect(() => {
const interval = setInterval(() => ref.current.position.x += 0.1), 1)
return () => clearInterval(interval)
}, [])
Handle animations in loops
帧循环是你应该放置你的动画的地方。例如使用
function Signal({ active }) {
const ref = useRef()
useFrame((state, delta) => {
ref.current.position.x = THREE.MathUtils.lerp(ref.current.position.x, active ? 100 : 0, 0.1)
})
return <mesh ref={ref} />
或者,使用动画库。
import { a, useSpring } from '@react-spring/three'
function Signal({ active }) {
const { x } = useSpring({ x: active ? 100 : 0 })
return <a.mesh position-x={x} />
Do not bind to fast state reactively
使用状态管理程序和选择性状态是可以的,但对于快速发生的更新来说,就不是这样了,原因和上面一样。
❌ Don't bind reactive fast-state
import { useSelector } from 'react-redux'
// Assuming that x gets animated inside the store 60fps
const x = useSelector((state) => state.x)
return <mesh position-x={x} />
而应该定期主动获取:
useFrame(() => (ref.current.position.x = api.getState().x));
return <mesh ref={ref} />;
Don’t mount indiscriminately
在
❌ Avoid mounting runtime
{
stage === 1 && <Stage1 />
}
{
stage === 2 && <Stage2 />
}
{
stage === 3 && <Stage3 />
}
✅ Consider using visibility instead
<Stage1 visible={stage === 1} />
<Stage2 visible={stage === 2} />
<Stage3 visible={stage === 3} />
function Stage1(props) {
return (
<group {...props}>
...
import { useTransition } from 'react'
import { Points } from '@react-three/drei'
const [isPending, startTransition] = useTransition()
const [radius, setRadius] = useState(1)
const positions = calculatePositions(radius)
const colors = calculateColors(radius)
const sizes = calculateSizes(radius)
<Points
positions={positions}
colors={colors}
sizes={sizes}
onPointerOut={() => {
startTransition(() => {
setRadius(prev => prev + 1)
})
}}
>
<meshBasicMaterial vertexColors />
</Points>
Don’t re-create objects in loops
尽量避免给垃圾收集器带来太多麻烦,在可以的情况下对对象进行重新分类。
❌ Bad news for the GC
useFrame(() => {
ref.current.position.lerp(new THREE.Vector3(x, y, z), 0.1)
})
✅ Better re-use object
function Foo(props)
const vec = new THREE.Vector()
useFrame(() => {
ref.current.position.lerp(vec.set(x, y, z), 0.1)
})
useLoader instead of plain loaders
❌ No re-use is bad for perf
function Component() {
const [texture, set] = useState();
useEffect(() => void new TextureLoader().load(url, set), []);
return texture ? (
<mesh>
<sphereGeometry />
<meshBasicMaterial map={texture} />
</mesh>
) : null;
}
✅ Cache and re-use objects
function Component() {
const texture = useLoader(TextureLoader, url)
return (
<mesh>
<sphereGeometry />
<meshBasicMaterial map={texture} />
</mesh>
)
}