背景

在 React 开发里,状态管理(State Management,指“对组件数据、共享数据、更新逻辑进行统一组织和控制”)几乎是绕不开的话题。

很多初学者一开始会把“状态管理”理解成“必须使用一个库”,但这其实不准确。真正的问题不是“要不要用库”,而是:

当项目越来越复杂时,我们该如何管理状态,才能让代码保持清晰、可维护、可扩展。

所以这篇文章不只是讲几个库怎么用,更重要的是梳理一条合理的演进路线:

  1. 为什么会有状态管理这个问题

  2. Redux Toolkit(Redux 官方工具集)为什么曾经成为主流方案

  3. 为什么很多场景下,根本不需要上库,先用 useStateuseRef 就够了

  4. 当共享状态开始变多时,为什么 Jotai(原子状态管理库)很合适

  5. 当项目进一步复杂,需要把状态系统化组织时,为什么会引出 Zustand(轻量全局状态管理库)


一、为什么前端会遇到状态管理问题

先从最根本的问题说起。

React是组件化开发。组件内部有自己的数据,组件之间也会共享数据。如果项目小,这些数据通常很好处理;但随着业务增长,状态会越来越复杂,典型问题就会出现:

  • 一个状态要传很多层组件(一对多)

  • 多个组件依赖同一份数据(多对一)

  • 更新逻辑分散在不同地方

  • 异步请求和本地状态混在一起

  • 数据变化越来越难追踪

这样就出现了两个核心矛盾点:

1. 状态放哪里

是放在某个组件里,还是提到父组件,还是做成全局共享?

2. 状态怎么改

是任意组件都能改,还是必须走统一流程?

所以,所谓“状态管理”,本质上解决的是两件事:

  • 状态如何存放

  • 状态如何更新

这也是后面 Redux Toolkit、Jotai、Zustand 这些方案的核心区别。


二、Redux Toolkit:最早成熟、最有体系的状态管理方案之一

如果说 React 状态管理历史上最经典、最有代表性的方案,Redux 一定绕不过去。

但今天真正应该学习的,不是早年那种样板代码很多的 Redux 老写法,而是 Redux Toolkit(Redux 官方工具集)

Redux Toolkit 的本质,是在保留 Redux 思想的前提下,把 Redux 原本繁琐的写法进行官方简化。
所以理解 Redux Toolkit,先要理解 Redux 的核心思想。

2.1 Redux 的核心思想是什么

Redux 的核心思想可以概括成一句话:

把共享状态集中放进一个 store(状态仓库),所有状态更新都通过统一流程完成。

这套流程里有三个最关键的概念:

1. state(状态)

就是当前数据本身。

比如:

  • 当前用户名称

  • 当前购物车商品

  • 当前计数器值

2. action(动作对象)

action(动作对象,指“描述发生了什么的普通对象”)不是直接修改状态的代码,而是一个“说明书”。

例如:

{ type: 'counter/increment' }

它的意思不是“立刻把 count +1”,而是:

发生了一个“计数增加”的动作

也就是说,action 负责描述状态要发生什么变化


3. dispatch(分发函数)

dispatch(分发函数,指“把 action 发送到状态系统中的入口方法”)的作用,是把 action 交给 Redux。

比如:

dispatch({ type: 'counter/increment' })

它的意思是:

把“counter/increment 这个动作发生了”这件事,发送给 Redux 处理

所以它不是“修改状态”的代码,而是“发起状态变更流程”的入口。


4. reducer(归约函数)

reducer(归约函数,指“接收旧状态和 action,计算并返回新状态的纯函数”)才是真正负责“怎么改状态”的地方。

也就是说:

  • action 只负责描述

  • dispatch 只负责发送

  • reducer 负责根据描述,计算出新的 state

这三者的关系可以记成一句话:

action 是变更说明书,dispatch 是投递动作,reducer 是真正计算新状态的地方。


2.2 Redux 的运行机制

Redux 的运行机制其实非常清晰:

组件触发 dispatch(action)
-> store 收到 action
-> reducer 根据 action 和旧 state 计算新 state
-> store 保存新 state
-> React 组件读取到新 state,重新渲染

import { configureStore, createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => {
      state.count += 1
    },
    setCount: (state, action) => {
      state.count = action.payload
    },
  },
})

export const { increment, setCount } = counterSlice.actions

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
})

export default store

这段代码里有几个关键点:

createSlice(切片创建函数)

createSlice(切片创建函数,指“把一组相关状态和更新逻辑组织在一起的 Redux Toolkit API”)用来把一个模块的状态和 reducer 放在一起管理。

这里的 counterSlice 就是一个计数器模块。


initialState

initialState: { count: 0 }

表示这个模块的初始状态。


reducers

reducers: {
 increment: (state) => {
   state.count += 1
 },
 setCount: (state, action) => {
   state.count = action.payload
 },
}

这里定义的是这个模块允许怎样更新。

  • increment:把 count 加一

  • setCount:把 count 设置成外部传进来的值

这里的 action.payload(动作载荷,指“action 中携带的额外数据”)就是传进来的新值。


configureStore

const store = configureStore({
 reducer: {
  counter: counterSlice.reducer,
 },
})

configureStore(仓库配置函数,指“创建 Redux store 并自动完成常见配置的 API”)用于创建全局 store。


2.3 Redux Toolkit 的价值是什么

Redux Toolkit 的价值,不是“发明了新的状态管理思想”,而是:

让 Redux 这套强约束、强组织性的思想,更容易写、更适合现代 React 项目。

它特别适合以下场景:

  • 大型项目

  • 团队多人协作

  • 状态变更流程要清晰可追踪

  • 异步逻辑比较多

  • 希望有明确的模块边界

它最大的优点是:

1. 统一

所有人都按同一套流程改状态。

2. 可追踪

你能知道状态是被哪个 action 改掉的。

3. 可维护

复杂项目里,统一规范比“随手能写”更重要。


2.4 Redux Toolkit 的问题是什么

Redux Toolkit 很强,但它并不适合一切场景。

它的问题不是“不好”,而是:

对于很多中小项目来说,它可能有点重。

为什么?

因为它本质上仍然是一套“有组织、有约束”的状态系统。
当你的状态其实并不复杂时,直接上 Redux Toolkit 可能会带来这些问题:

  • 概念较多

  • 全局化太早

  • 小需求也要走一整套流程

  • 对简单项目来说心智负担偏大

所以,Redux Toolkit 很重要,但它不是“默认第一选择”。
它更像是:复杂项目里很稳的工程化方案。


三、什么时候其实不需要状态管理库:先考虑 useStateuseRef

这是很多人最容易忽略的一步。

一提“状态管理”,很多人会立刻想到 Redux、Jotai、Zustand。
但真正成熟的做法是:

先判断这个状态是否真的需要共享,是否真的需要被管理。

很多状态,其实只属于当前组件本身,这时候最合适的方案,永远是 React 自带能力。


3.1 useState(状态 Hook)

useState(状态 Hook,指“React 中用于在函数组件里保存和更新局部状态的 Hook”)适合管理组件内部状态。

例如:

  • 输入框内容

  • 弹窗显示与隐藏

  • 当前 tab

  • 局部 loading

  • 表单校验状态

代码示例:

import { useState } from 'react'

function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>当前计数:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}

这里的逻辑非常直接:

  • count 是当前状态

  • setCount 是更新函数

如果状态只在当前组件里用,这就是最合适的方案。


3.2 useRef(引用 Hook)

useRef(引用 Hook,指“用于保存不会触发组件重新渲染的可变引用值,或获取 DOM 引用的 Hook”)通常用于两类场景:

1. 获取 DOM 引用

import { useRef } from 'react'

function InputBox() {
  const inputRef = useRef<HTMLInputElement | null>(null)

  const focusInput = () => {
    inputRef.current?.focus()
  }

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>聚焦输入框</button>
    </div>
  )
}

2. 保存不需要触发重新渲染的值

例如:

  • 定时器 id

  • 上一次的值

  • 某些缓存数据

也就是说,useRef 更偏“引用”和“容器”,而不是用来驱动 UI 重新渲染。


3.3 为什么这一层最重要

因为很多状态,根本不值得全局化。

如果一个状态:

  • 只在当前组件使用

  • 不需要跨组件共享

  • 没有复杂更新逻辑

那么最好的方案就是:

不要引入额外状态管理库。

这是状态管理里非常关键的一条原则:

状态尽量靠近使用它的地方。

换句话说:

不要为了“以后可能会复杂”而过早引入全局状态管理。


四、从局部状态走向共享状态:为什么会需要 Jotai

当项目继续增长,你会发现只靠 useState 已经开始吃力了。

比如:

  • 多个组件需要共享一份状态

  • 某个状态不只在父子组件中使用

  • props(属性传递)一层层往下传很麻烦

  • 一些状态之间存在明显的依赖关系

这时我们就开始进入“共享状态”阶段。

而 Jotai(原子状态管理库)就是一个非常适合这个阶段的方案。


4.1 Jotai 的核心思想是什么

Jotai 的核心是 atom(原子状态,指“最小可独立读写的状态单元”)。

你可以把它理解成:

把原本组件内部的状态,拆成一个一个可共享的小状态单元。

这也是 Jotai 最大的特点:
它不是先让你建立一个很大的全局 store,而是让你从一个个 atom 开始。

所以它特别像 useState 的自然升级版。


4.2 为什么 Jotai 适合这个阶段

Jotai 适合的场景是:

  • 状态开始需要共享

  • 但项目还没复杂到必须建立完整 store 体系

  • 你希望写法尽量贴近 React

  • 希望状态粒度细一些

  • 希望派生状态(可以理解为:它不是一个独立保存的数据,而是由已有数据推导出来的结果)表达清晰

换句话说:

Jotai 适合“共享了,但还没复杂到系统化管理”的状态。


4.3 Jotai 的基本用法

基础 atom

import { atom, useAtom } from 'jotai'

const countAtom = atom(0)

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

  return (
    <div>
      <p>计数:{count}</p>
      <button onClick={() => setCount((c) => c + 1)}>+1</button>
    </div>
  )
}

这里要这样理解:

atom(0)

表示定义了一个原子状态,它的初始值是 0

useAtom(countAtom)

useAtom(原子状态 Hook,指“用于读取和更新 atom 的 Hook”)和 useState 很像,返回:

  • 当前值(value)

  • 更新函数(setter)

所以这里 Jotai 的使用体验非常接近 React 原生状态。


4.4 Jotai 的关键优势:派生状态

Jotai 真正很强的一点,是派生状态。

例如:

import { atom, useAtom } from 'jotai'

const priceAtom = atom(100)
const quantityAtom = atom(2)

const totalAtom = atom((get) => {
  return get(priceAtom) * get(quantityAtom)
})

这里的 totalAtom 不是手动存进去的值,而是根据别的 atom 算出来的。

其中:

get(读取函数,指“Jotai 在派生 atom 中提供的,用于读取其他 atom 当前值的函数”)是 Jotai 自动传进来的工具函数。

所以:

  • get(priceAtom) 表示读取 priceAtom 当前值

  • get(quantityAtom) 表示读取 quantityAtom 当前值

整段代码的意思是:

totalAtom 的值,等于 priceAtomquantityAtom 当前值的乘积

这样设计的好处是:

  • 状态依赖关系非常清晰

  • 不需要手动同步

  • 某个 atom 变化时,派生 atom 自动重新计算

这就是 Jotai 很适合处理中小型共享状态的原因之一。


4.5 Jotai 的定位

所以,从项目演进角度看,Jotai 解决的是这样一个问题:

当局部状态已经不够,但又不想太早引入大而全的全局状态架构时,如何优雅地共享状态?

答案就是:

用 atom,把共享状态拆成细粒度的可组合单元。


五、当项目继续变复杂:为什么会引出 Zustand

继续往后走,项目再大一些,状态就不只是“共享”这么简单了。

这时候你会开始遇到新的问题:

  • 状态越来越多

  • 业务模块越来越明显

  • 状态和操作逻辑需要放在一起

  • 需要更清晰的组织方式

  • 不希望 atom 到处散落

这个阶段,项目对状态管理的要求已经从“能共享”变成了:

能组织、能分模块、能维护。

这就是 Zustand(轻量全局状态管理库)出现的价值。


5.1 Zustand 的核心思想是什么

Zustand 的核心是 store(状态仓库,指“集中保存一组相关状态和更新方法的容器”)。

如果说:

  • Jotai 是从“状态单元”出发

  • 那么 Zustand 是从“状态模块”出发

它更强调把一组相关状态和操作放在一起。

所以它更像一种轻量的 store 模式。


5.2 Zustand 适合什么阶段

Zustand 适合这些场景:

  • 全局状态已经明显按业务模块存在

  • 需要把状态和更新方法集中组织

  • 希望代码结构比 Jotai 更整齐

  • 不想上 Redux Toolkit 那么重的体系

  • 想要轻量,但又要有明显的组织感

简单说:

Zustand 适合“共享状态已经开始成系统”的项目。


5.3 Zustand 的基本用法

import { create } from 'zustand'

type CounterStore = {
  count: number
  increment: () => void
  setCount: (count: number) => void
}

const useCounterStore = create<CounterStore>((set) => ({
  count: 0,

  increment: () =>
    set((state) => ({
      count: state.count + 1,
    })),

  setCount: (count) => set({ count }),
}))

这个例子里,有两个非常关键的写法。


第一种:依赖旧状态时,用函数形式

increment: () =>
  set((state) => ({
    count: state.count + 1,
  }))

这里为什么要写成函数?

因为新状态依赖旧状态。
你需要先拿到原来的 state.count,再算出新的值。

也就是:

基于旧状态计算新状态

同样的也可以写成(对 state 进行解构):

increment: () =>
  set(({ count }) => ({
    count: count + 1,
  }))

第二种:不依赖旧状态时,直接传对象

setCount: (count) => set({ count })

这里不需要看旧状态,因为你已经知道新值就是传进来的 count

这段代码等价于:

setCount: (count) => set({ count: count })

只是因为对象属性名和变量名相同,所以可以简写成:

{ count }

5.4 Zustand 为什么比 Jotai 更“有组织”

Jotai 的思路是把状态拆成 atom。
Zustand 的思路是把相关状态和操作收进一个 store。

所以在项目更复杂时,Zustand 往往会显得更顺手,因为它天然更适合这种结构:

  • 用户模块一个 store

  • 购物车模块一个 store

  • 权限模块一个 store

  • 主题模块一个 store

这就比“很多 atom 分散定义”更容易建立模块边界。

也正因为这样,Zustand 常常被认为比 Jotai 更适合做“有组织的全局状态管理”。


六、把这条路线串起来:状态管理应该怎么选

现在把整篇文章的逻辑合起来,你就会发现,React 状态管理并不是“几个库谁更强”的问题,而是一个随着项目复杂度逐步演进的问题。


第一层:先用 React 原生能力

如果状态只是局部使用:

  • useState

  • useRef

优先解决。

这时候不要急着全局化。


第二层:开始共享,但还不复杂,用 Jotai

如果多个组件要共享状态,但项目还没到“强组织、强架构”的程度,Jotai 非常合适。

因为它:

  • 足够轻

  • 足够直观

  • 和 React 心智接近

  • 派生状态很好写


第三层:状态越来越多,需要组织,用 Zustand

如果状态不再只是零散共享,而是开始按业务模块成型,那么 Zustand 会更适合。

因为它强调:

  • store 化

  • 模块化

  • 状态和方法集中管理


第四层:复杂业务、多人协作、强规范,用 Redux Toolkit

当项目进入更复杂阶段,比如:

  • 业务流程很长

  • 状态更新要严格可追踪

  • 团队多人协作

  • 需要统一规范和调试能力

Redux Toolkit 依然是非常稳定的选择。

它不是最轻的,但通常是最“工程化”的。


结语:真正重要的不是库,而是状态管理思维

最后做一个总结。

前端状态管理的发展,不是为了让我们记住越来越多的 API,而是为了回答一个始终不变的问题:

当数据越来越多、组件越来越多、共享越来越频繁时,我们该怎样把状态放对地方、改对方式。

所以一条比较成熟的路线应该是:

  • 先学会用 useStateuseRef 管好局部状态

  • 再理解 Redux Toolkit 的统一状态流思想

  • 然后在实际项目中,根据复杂度选择 Jotai 或 Zustand

  • 真正做到“工具匹配问题”,而不是“为了用库而用库”

这才是理解 React 状态管理的关键。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐