React 状态管理:用 Zustand 切片化 store 并规避 selector 引起的重渲染
test12026-09-110 次阅读
为什么 Context 会在大型应用里变成性能陷阱
React 的 Context 用起来很直观,但它有个绕不开的问题:只要 Provider 的 value 发生变化,所有消费该 Context 的组件都会重新渲染,哪怕某个组件只用了 value 里的一小块数据。在购物车、用户信息、主题这类全局状态混杂在一起时,任意一个字段变化都会带动整棵消费子树刷新,页面越大越明显。
Zustand 的思路不同:组件通过 selector 精确地订阅自己关心的那部分状态,只有这部分变化时才重渲染。配合切片(slice)把 store 拆成逻辑独立的块,既能复用又能控制影响范围。
把 store 切成可组合的切片
下面把用户信息和购物车拆成两个 slice,在 create 里合并成一个 store。每个 slice 自己管自己的状态和改值函数,互不干扰。
import { create } from 'zustand'
type UserSlice = {
user: { id: number; name: string } | null
setUser: (u: UserSlice['user']) => void
}
type CartSlice = {
items: { sku: string; qty: number }[]
addItem: (sku: string, qty: number) => void
}
export const useStore = create<UserSlice & CartSlice>((set) => ({
user: null,
setUser: (user) => set({ user }),
items: [],
addItem: (sku, qty) =>
set((s) => ({ items: [...s.items, { sku, qty }] })),
}))
selector 用错会让你白优化
最常见的坑是 selector 返回新对象。下面这种写法每次渲染都会拿到一个新的对象引用,导致组件在任意状态变化时都重渲染,等于回到了 Context 的老路。
// 错误:每次都返回新对象
const badge = useStore((s) => ({ count: s.items.length }))
// 正确:返回基本类型,引用稳定
function CartBadge() {
const count = useStore((s) => s.items.reduce((n, i) => n + i.qty, 0))
return <span>{count}</span>
}
如果确实要选多个字段,用 useShallow 把比较改成浅比较,避免新对象引用触发刷新。
import { useShallow } from 'zustand/react/shallow'
const { user, items } = useStore(
useShallow((s) => ({ user: s.user, items: s.items })),
)
什么时候该上 Zustand
- 全局状态被很多互不相关的组件消费,且各自只关心其中一小块。
- 你不想为了状态拆分出一堆 Provider 嵌套。
- 组件重渲染已经能用 React DevTools 的火焰图看出明显浪费。
如果只是父传子两三层、或者状态只在一个页面内流转,Context 或 useState 足够,没必要为了用而用。状态管理的本质是控制重渲染的边界,工具只是手段。
T
test1
文章作者
为什么 Context 会在大型应用里变成性能陷阱 React 的 Context 用起来很直观,但它有个绕不开的问题...
- 分类
- 技术
- 发布时间
- 2026-09-11
- 字数
- 约 1588 字
- 阅读
- 0 次
