选择器与性能
Zustand 的选择器机制是其性能核心。它确保组件只在自己关心的状态片段变化时才重渲染。
选择器工作原理
import { create } from 'zustand';
const useStore = create((set) => ({
bears: 0,
fishes: 0,
increaseBears: () => set((s) => ({ bears: s.bears + 1 })),
}));
// 选择器:返回 bears 字段
const bears = useStore((state) => state.bears);
// 只有当 bears 的值变化时,这个组件才会重渲染
// fishes 变化不会影响这个组件
执行流程:
useStore(s => s.bears)
│
▼
1. getState() 获取当前状态
2. 调用选择器 s => s.bears,得到返回值
3. Object.is(新返回值, 旧返回值)
4. 如果不同 → 重渲染组件
5. 如果相同 → 跳过渲染🔬 深入原理:Zustand 使用
useSyncExternalStore(React 18+ 内置)或自实现的等价方案来订阅外部 store。选择器的返回值和上一次的相比,默认用Object.is(与===类似但NaN和+0/-0行为正确)。
选择器返回原始值的三种写法
// 写法 1:直接返回字段(最常用)
const bears = useStore((s) => s.bears);
// 写法 2:计算派生值
const total = useStore((s) => s.bears + s.fishes);
// 写法 3:返回字符串拼接
const label = useStore((s) => `Bears: ${s.bears}`);只要这些返回值是原始类型(string、number、boolean、null、undefined),Object.is 比较就能正确工作。
选择器返回对象 —— shallow 比较器
原始值比较天然正确,但对象就不一样了:
// ❌ 每次渲染都返回新对象 → 永远认为改变了 → 无限重渲染
const { bears, fishes } = useStore((s) => ({
bears: s.bears,
fishes: s.fishes,
}));
// { bears: 0, fishes: 0 } !== { bears: 0, fishes: 0 }(不同引用)
解决方案:shallow 比较器(浅比较对象字段值):
import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';
// ✅ 方式 1:useShallow(v4.5+,推荐)
const { bears, fishes } = useStore(
useShallow((s) => ({ bears: s.bears, fishes: s.fishes }))
);
// ✅ 方式 2:手动传入 shallow 比较器
import { shallow } from 'zustand/shallow';
const { bears, fishes } = useStore(
(s) => ({ bears: s.bears, fishes: s.fishes }),
shallow // 作为第二个参数
);💡 最佳实践:v4.5+ 推荐
useShallow,语义更清晰。v5 中shallow已从主入口移除,必须从zustand/shallow导入。
shallow 比较原理
深比较:递归检查对象的每一层 → 很慢,通常不需要
浅比较(shallow):只比较对象的顶层 key
{ a: 1, b: { c: 2 } }
vs
{ a: 1, b: { c: 2 } }
→ a 相同 (===) ✓
→ b 相同 (===) ✗ → 引用不同 → 认为不相等
→ 结论:不相等
✅ 适合:{ bears: number, fishes: number }(值都是原始类型)
❌ 不适合:{ items: [...] }(数组引用经常变)选择器返回数组 —— 同样需要 shallow
// ❌ 每次都是新数组引用
const [bears, fishes] = useStore((s) => [s.bears, s.fishes]);
// ✅ 用 useShallow 包裹
import { useShallow } from 'zustand/react/shallow';
const [bears, fishes] = useStore(
useShallow((s) => [s.bears, s.fishes])
);自定义比较器
如果 shallow 不够精确,可以传入自定义比较函数:
import { create } from 'zustand';
const useStore = create((set) => ({
items: [] as Item[],
filter: 'all' as string,
}));
// 自定义比较:只关心 items 的 length
function MyComponent() {
const count = useStore(
(s) => s.items.length, // ✅ 返回原始值 number,不需要 shallow
);
// 还是一种更好的方式:选择器直接返回原始值
}大部分场景不需要自定义比较器——让选择器直接返回原始值是更好的策略。
useShallow 的稳定引用
useShallow 返回一个稳定的函数引用:
const shallowSelector = useShallow((s) => ({
bears: s.bears,
fishes: s.fishes,
}));
// shallowSelector 的引用在多次渲染中保持稳定
这意味着你可以安全地把它作为依赖传给 useMemo / useCallback。
🚨 陷阱:不要把
useShallow用在条件或循环中——它内部也是一个 Hook,遵守 Hooks 规则(顶层调用)。
性能陷阱与最佳实践
陷阱 1:解构整个 Store
// ❌ 每次任何字段变化都会重渲染
const state = useStore();
const { bears, fishes } = state;
// ✅ 只订阅需要的字段
const bears = useStore((s) => s.bears);
const fishes = useStore((s) => s.fishes);陷阱 2:选择器返回新对象/数组
// ❌ 每次返回新对象 → 无限重渲染
const result = useStore((s) => ({
filtered: s.items.filter(i => i.active),
}));
// ✅ 方式 1:计算后返回原始值
const count = useStore((s) => s.items.filter(i => i.active).length);
// ✅ 方式 2:用 useShallow + 稳定引用(需配合 useMemo)
import { useMemo } from 'react';
const filterKey = useStore((s) => s.filter);
const filtered = useMemo(
() => items.filter(i => i.active),
[items, filterKey] // 依赖原始 items 而不是选择器结果
);陷阱 3:选择器返回 action 不需要 shallow
// ✅ action 的引用通常是稳定的(创建 store 时就确定了)
const increase = useStore((s) => s.increaseBears);
// 不需要 useShallow,因为 increase 是一个稳定的函数引用
💡 最佳实践:订阅 action 时选择器直接返回该 action 即可,无需 shallow。如果 action 是
set中动态创建的(不推荐),需要注意引用稳定性。
原子选择器模式
对于复杂的状态组合,可以提取选择器为独立函数:
// selectors.ts
import { useBearStore } from './useBearStore';
// 原子选择器
export const selectBears = (s: BearState) => s.bears;
export const selectFishes = (s: BearState) => s.fishes;
// 派生选择器
export const selectTotal = (s: BearState) => s.bears + s.fishes;
export const selectIsEndangered = (s: BearState) => s.bears < 5;
// 在组件中使用
function BearCounter() {
const bears = useBearStore(selectBears);
const total = useBearStore(selectTotal);
return <div>{bears} / {total}</div>;
}💡 最佳实践:将选择器提取为命名函数(原子选择器),提高复用性,也方便在 DevTools 中追踪。
组合选择器(带 shallow)
// ✅ 组合原子选择器时用 useShallow
import { useShallow } from 'zustand/react/shallow';
function Dashboard() {
const { bears, fishes, isEndangered } = useBearStore(
useShallow((s) => ({
bears: selectBears(s),
fishes: selectFishes(s),
isEndangered: selectIsEndangered(s),
}))
);
// ...
}性能全景对比
订阅方式 重渲染触发条件
─────────────────────────────────────────────────────────
useStore(s => s.field) field 值变化
useStore(s => ({a: s.a, b: s.b}), shallow) a 或 b 值变化
useStore(s => [s.a, s.b], shallow) a 或 b 值变化
useStore() 任何字段变化
useStore(s => s.actionFn) 几乎不会重渲染(引用稳定)| 选择器返回类型 | 比较方式 | 需 shallow? |
|---|---|---|
| 原始值 (number/string/boolean) | Object.is |
❌ |
对象 {a, b} |
Object.is(引用) |
✅ |
数组 [a, b] |
Object.is(引用) |
✅ |
| 函数 (action) | Object.is(稳定引用) |
❌ |
| null/undefined | Object.is |
❌ |