React状态管理方案对比:Zustand与Jotai实战选型指南

React状态管理方案在2026年呈现轻量化趋势。Redux的全局store模式逐渐被Zustand和Jotai替代。Zustand采用单一store配合选择器订阅,API极简;Jotai基于原子化模型,自底向上组合状态。本文通过TypeScript实战代码对比两种方案的使用模式、性能特征和适用场景,为前端工程化选型提供参考。

Zustand快速上手与Store设计

安装Zustand:

npm install zustand

创建全局store,TypeScript类型安全定义:

import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

interface UserState {
  user: { id: string; name: string; role: string } | null
  token: string | null
  login: (user: UserState['user'], token: string) => void
  logout: () => void
  updateUser: (patch: Partial<NonNullable<UserState['user']>>) => void
}

export const useUserStore = create<UserState>()(
  devtools(
    persist(
      (set, get) => ({
        user: null,
        token: null,
        login: (user, token) => set({ user, token }),
        logout: () => set({ user: null, token: null }),
        updateUser: (patch) =>
          set((state) => ({
            user: state.user ? { ...state.user, ...patch } : null,
          })),
      }),
      { name: 'user-storage' }
    )
  )
)

组件中使用store,选择器精确订阅避免不必要重渲染:

import { useUserStore } from './stores/userStore'

function UserProfile() {
  // 只订阅user字段,token变化不触发重渲染
  const user = useUserStore((state) => state.user)
  const updateUser = useUserStore((state) => state.updateUser)

  if (!user) return <LoginGate />

  return (
    <div>
      <h1>{user.name}</h1>
      <span>角色: {user.role}</span>
      <button onClick={() => updateUser({ name: '新名称' })}>
        修改名称
      </button>
    </div>
  )
}

跨组件通信无需Context Provider包裹,直接在任意组件中调用useUserStore

Zustand异步操作与中间件

异步请求封装在store内部,配合loading状态管理:

interface ProductState {
  products: Product[]
  loading: boolean
  error: string | null
  fetchProducts: () => Promise<void>
}

export const useProductStore = create<ProductState>()(
  devtools((set, get) => ({
    products: [],
    loading: false,
    error: null,
    fetchProducts: async () => {
      set({ loading: true, error: null })
      try {
        const resp = await fetch('/api/products')
        const data = await resp.json()
        set({ products: data, loading: false })
      } catch (err) {
        set({ error: err.message, loading: false })
      }
    },
  }))
)

使用shallow比较优化多字段订阅:

import { shallow } from 'zustand/shallow'

function ProductList() {
  const { products, loading } = useProductStore(
    (state) => ({ products: state.products, loading: state.loading }),
    shallow
  )

  if (loading) return <Skeleton />

  return (
    <ul>
      {products.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  )
}

Jotai原子化状态模型

安装Jotai:

npm install jotai

Jotai的核心概念是atom,每个atom是一个独立的状态单元:

import { atom, useAtom, useAtomValue } from 'jotai'

// 基础atom
const countAtom = atom(0)
const nameAtom = atom('')

// 派生atom(只读)
const doubleCountAtom = atom((get) => get(countAtom) * 2)

// 派生atom(可读写)
const resetCountAtom = atom(
  (get) => get(countAtom),
  (get, set) => set(countAtom, 0)
)

// 异步派生atom
const userDataAtom = atom(async (get) => {
  const token = get(tokenAtom)
  const resp = await fetch('/api/user', {
    headers: { Authorization: `Bearer ${token}` }
  })
  return resp.json()
})

组件中使用atom:

function Counter() {
  const [count, setCount] = useAtom(countAtom)
  const doubleCount = useAtomValue(doubleCountAtom)

  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {doubleCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  )
}

Jotai原子组合与依赖追踪

Jotai的原子可以相互依赖,形成状态依赖图。当依赖的atom变化时,派生atom自动重新计算:

// 购物车原子组合
const cartAtom = atom<CartItem[]>([])

const cartTotalAtom = atom((get) => {
  const cart = get(cartAtom)
  return cart.reduce((sum, item) => sum + item.price * item.qty, 0)
})

const cartCountAtom = atom((get) => {
  const cart = get(cartAtom)
  return cart.reduce((sum, item) => sum + item.qty, 0)
})

const discountAtom = atom(0.9) // 9折

const finalPriceAtom = atom((get) => {
  const total = get(cartTotalAtom)
  const discount = get(discountAtom)
  return Math.round(total * discount * 100) / 100
})

组件按需订阅不同的派生atom,互不影响:

function CartSummary() {
  const finalPrice = useAtomValue(finalPriceAtom)
  const count = useAtomValue(cartCountAtom)

  return <div>共{count}件商品,合计¥{finalPrice}</div>
}

function CartTotal() {
  const total = useAtomValue(cartTotalAtom)
  // 只关心原始总价,不关心折扣
  return <div>原价:¥{total}</div>
}

性能对比与渲染优化

Zustand和Jotai在渲染性能上有不同特征:

Zustand:选择器函数在每次store变化时执行,返回值通过Object.is比较。对象类型返回值需要配合shallow比较,否则每次都触发重渲染。

// 问题:每次都返回新对象,导致重渲染
const { a, b } = useStore((state) => ({ a: state.a, b: state.b }))
// 修复:使用shallow
const { a, b } = useStore((state) => ({ a: state.a, b: state.b }), shallow)

Jotai:atom级别的依赖追踪,只有订阅的atom变化才触发重渲染。派生atom的缓存机制避免重复计算。

// Jotai自动优化,无需手动shallow比较
const a = useAtomValue(aAtom)
const b = useAtomValue(bAtom)
// aAtom变化只重渲染订阅aAtom的组件

使用React DevTools Profiler对比两种方案在1000条列表数据更新时的渲染次数。Zustand的单一store模式下,未使用选择器的组件也会被通知(虽然不重渲染但需要执行选择器)。Jotai的原子模型天然实现了最小化订阅范围。

选型决策与迁移方案

选择Zustand的场景

  • 全局状态集中管理,如用户信息、主题配置、权限数据
  • 团队熟悉Redux模式,需要平缓的学习曲线
  • 需要中间件支持(persist、devtools、immer)
  • 状态间关联性强,集中管理更清晰

选择Jotai的场景

  • 状态分散在不同组件中,粒度细
  • 存在大量派生状态和计算属性
  • 需要精细的渲染优化,避免不必要的重渲染
  • 组件库设计,每个组件维护独立状态

从Redux迁移到Zustand的成本较低,store结构可以一对一映射。从Redux迁移到Jotai需要将reducer拆解为独立atom,工作量较大但长期维护性更好。

两种方案也可以共存:Zustand管理全局共享状态(用户、主题),Jotai管理业务组件内部状态(表单、表格筛选)。在响应式布局和跨端小程序开发场景中,状态管理方案的选择还需要考虑SSR兼容性和Hydration支持。Zustand通过persist中间件支持SSR,Jotai提供useHydrateAtoms处理服务端状态注水。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/react-zhuang-tai-guan-li-fang-an-dui-bi-zustand-yu-jotai/

(0)
小编小编
上一篇 5小时前
下一篇 5小时前

相关推荐