Skip to content
选择器与性能

选择器与性能

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