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 次