目录
状态管理与组件通信的关系
React状态管理学习路线
1. useState: 最小状态标准
2. useReducer
2.1 useReducer基本实现结构
2.2 案例分析
3. useReducer+ useContext
3.1 useReducer+ useContext基本流程
3.2 useReducer + Context 的局限性
4. Redux toolkit
4.1 使用 Redux Toolkit 的基本流程
5. Zustand
5.1 最基础的 Zustand 示例
参考
状态管理与组件通信的关系
在 React 中,组件通信和状态管理是两个密切相关但职责不同的概念。在上一章我们已经描述了整个组件通信:
- 组件通信关注的是:state 如何在组件之间传递
- 状态管理关注的是:state 如何被存储、更新以及由谁来更新
可以用下面这张分层模型来理解它们之间的关系:
┌───────────────┐
│ 状态管理 │ ← Redux / Zustand / useReducer
│(state 怎么变)│
└───────▲───────┘
│
┌───────┴───────┐
│ 组件通信 │ ← props / 回调 / Context
│(state 怎么传)│
└───────────────┘
组件通信为状态提供“传递通道”,
而状态管理则负责定义状态的变化规则和作用范围。
React状态管理学习路线
在 React 中,状态管理并不是一蹴而就的,而是随着应用规模逐步演进的,以下路线从局部状态逐步过渡到全局状态管理,也是 React 官方与社区普遍推荐的学习顺序。
useState
↓
useReducer
↓
useReducer + Context
↓
Redux Toolkit
↓
轻量方案(Zustand / Jotai)
1. useState: 最小状态标准
首先我们需要明确一点:useState 并不是状态管理库,它只是 React 中“状态的最小单元”。
它主要解决以下三个最基础的问题:
- 状态存储在哪里
- 状态如何被更新
- 状态更新后如何触发组件重新渲染
const [count, setCount] =useState(0)
需要注意的是,count 是只读的状态值,我们不能直接修改它,只能通过 setCount 来触发状态更新。当状态发生变化时,React 会重新渲染当前组件,从而反映最新的 UI。
随着界面内部状态逐渐变得复杂,多个状态之间往往会建立起一定的业务联系。例如一个常见的 API 请求场景:
const [loading, setLoading] =useState(false)
const [data, setData] =useState(null)
const [error, setError] =useState(null)
在这个例子中:
- 请求开始时需要设置 loading
- 请求成功时需要更新 data 并关闭 loading
- 请求失败时需要更新 error 并关闭 loading
可以看到,这几个状态并不是彼此独立的,而是在同一个业务流程中协同变化的。
如果仍然单纯使用多个 useState 分散管理,不仅更新逻辑会变得零散,也更容易出现状态不一致的问题。
因此,当状态之间存在明确的关联和变化规则时,我们就需要引入更加结构化的状态管理方式 —— useReducer。
2. useReducer
useReducer 用来描述「状态是如何变化的」, 它把状态更新逻辑从组件中抽离出来,集中放在一个 reducer 函数 中统一管理。
2.1 useReducer基本实现结构
那么如何使用useReducer呢,具体看以下案例:
- state:当前状态
- dispatch:触发状态变化的唯一入口,我们不再直接修改状态,而是通过 dispatch 发送一个 action,来“告诉 reducer 发生了什么事情(触发reduce 中 action的逻辑) 其实就有些跟state中的setState一样,我们只能通过setState来修改值。
- action:描述“发生了什么”
- reducer:根据 state + action 计算出新的状态
const initialState = { count: 0 }
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count – 1 }
default:
return state
}
}
const [state, dispatch] = useReducer(reducer, initialState)
2.2 案例分析
继续使用刚刚的 API 请求案例,按照以下4个step,我们能很好理解这里的关系
import { useEffect, useReducer } from 'react'
// step 1 我们可以先定义一个统一的初始状态:
const initialState = {
loading: false,
data: null,
error: null
}
// step 2 接着定义 reducer,用来描述不同业务阶段下状态应该如何变化:
function reducer(state, action) {
switch (action.type) {
case 'request':
return { loading: true, data: null, error: null }
case 'success':
return { loading: false, data: action.payload, error: null }
case 'failure':
return { loading: false, data: null, error: action.payload }
default:
return state
}
}
// step 3 在组件中使用 useReducer
function DataFetcher() {
const [state, dispatch] = useReducer(reducer, initialState)
const { loading, data, error } = state //这里就是把初始值赋值过来了
// step 4 使用dispatch 触发请求并更新状态
useEffect(() => {
dispatch({ type: 'request' })
fetchData()
.then(res => {
dispatch({ type: 'success', payload: res })
})
.catch(err => {
dispatch({ type: 'failure', payload: err })
})
}, [])
}
在以上部分,我们使用 useReducer 管理的是单个组件内部的状态。但在真实业务中,很多状态往往需要被**多个组件同时使用,**因此我们需要使用useReducer+ useContext结合的方式。
3. useReducer+ useContext
我们在上一章已经学习了useContext如何使用, 它的作用是为组件树中的任意组件,提供一种无需层层传递 props 的数据访问方式。
因此,当我们将:
- useReducer —— 负责状态变化规则
- Context —— 负责状态分发与访问
组合在一起时,就形成了一套完整的跨组件状态管理方案。
3.1 useReducer+ useContext基本流程
我们总共有4个步骤
1. 创建Context
通常会创建两个 Context,分别存储:
- state(当前状态)
- dispatch(触发状态变化的方法)
这样可以让职责更加清晰,也有助于减少不必要的组件重新渲染
import { createContext } from 'react'
export const StateContext = createContext(null)
export const DispatchContext = createContext(null)
2. 创建 Provider 组件,统一管理 reducer 在 Provider 组件中,使用 useReducer 维护全局状态,并将 state 和 dispatch 通过 Context 向下传递。
import { useReducer } from 'react'
import { StateContext, DispatchContext } from './context'
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState) //这里reducer 跟 initialState跟上面的useReduce写法一样,这里不再写一遍
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
)
}
export default AppProvider
3. 在应用入口包裹 Provider
将 Provider 放在组件树的较高层级,使其子组件都能访问到同一份状态。
<AppProvider>
<App />
</AppProvider>
这样,App 及其所有子组件,都可以共享这套 state 和 dispatch。
4. 在任意子组件中使用 state 和 dispatch
子组件中只需要通过 useContext 获取所需的数据即可,无需关心状态是在哪里创建的。
import { useContext } from 'react'
import { StateContext, DispatchContext } from './context'
function ChildComponent() {
const state = useContext(StateContext)
const dispatch = useContext(DispatchContext)
return (
<div>
<p>Loading: {String(state.loading)}</p>
<button onClick={() => dispatch({ type: 'request' })}>
Fetch Data
</button>
</div>
)
}
总结一下:
- useReducer 负责定义 状态如何变化
- useContext 负责解决 状态如何在组件间共享
- 二者结合,构成了 React 中最小可用的全局状态管理方案
这一模式也正是后续 Redux Toolkit 等状态管理库的设计基础。
3.2 useReducer + Context 的局限性
在中大型项目中,常见的痛点包括:
- Context 更新会导致依赖该 Context 的组件重新渲染
- reducer 和 Provider 数量增多,结构逐渐复杂
- 异步逻辑(请求、缓存、错误处理)需要自行封装
这些问题并不是使用方式错误,而是原生方案本身缺乏工程化支持。因此我们可以使用Redux toolkit 或者Zustand来解决
4. Redux toolkit
Redux Toolkit 依然遵循 Redux 的核心设计:
- 单一数据源
- state 是只读的
- 通过 action 描述发生了什么
- 使用 reducer 计算新状态(是不是跟上述useReducer 逻辑差不多^_^)
4.1 使用 Redux Toolkit 的基本流程
在 Redux Toolkit 中,slice 是最核心的概念。
它不是单纯的 reducer,而是把一块业务相关的所有状态逻辑都集中在一起。
一个 slice 同时包含:
- 初始状态(initialState)
- 状态变化规则(reducers)
- 对应的行为描述(action)
import { createSlice } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0
},
reducers: {
increment(state) {
state.value += 1
},
decrement(state) {
state.value -= 1
}
}
})
1. 导出 action 和 reducer
export const { increment, decrement } = counterSlice.actions
export default counterSlice.reducer
这里可能有疑问,我们上面没有写action,这里怎么能直接导出的 实际上 Redux Toolkit 已经帮你自动生成了 action。
返回值结构大概是这样的,也就是会自动生成这个actions
createSlice
{
name: 'counter',
reducer: function reducer(state, action) { … },
actions: {
increment: () => ({ type: 'counter/increment' }),
decrement: () => ({ type: 'counter/decrement' })
}
}
2. 创建 Store
你可以把 store 想象成:整个数据的总仓库
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
3. 在应用入口注入 Store
import { Provider } from 'react-redux'
import { store } from './store'
<Provider store={store}>
<App />
</Provider>
4. 在组件中使用 state 和 dispatch
import { useDispatch, useSelector } from 'react-redux'
import { increment, decrement } from './counterSlice'
function Counter() {
const count = useSelector(state => state.counter.value)
const dispatch = useDispatch()
return (
<div>
<button onClick={() => dispatch(decrement())}>-</button>
<span>{count}</span>
<button onClick={() => dispatch(increment())}>+</button>
</div>
)
}
这里发生了几件事:
- useSelector:从全局 store 中选择你需要的状态
- useDispatch:获取触发状态变化的唯一入口
- dispatch(increment()):描述一次明确的业务行为
总结
- Redux Toolkit 的核心思想与 useReducer 高度一致
- 区别在于:它更规范、更工程化
5. Zustand
在学习了 useReducer + Context 和 Redux Toolkit 之后,你可能会产生一个疑问:
有没有一种状态管理方案,
不用写 reducer、不用 Provider、也没有大量样板代码?
Zustand 正是为这个需求而诞生的。
Zustand 是一个基于 Hooks 的轻量级状态管理库,它的核心理念可以概括为一句话:把状态当成一个普通的 store
5.1 最基础的 Zustand 示例
在写Zustand时,我们需要注意,所有逻辑集中在一个 store 中,也就是原先的state,还有action全部写在一个对象里面
import { create } from 'zustand'
const useCounterStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count – 1 }))
}))
这里你可以看到:
– `count` 是状态
– `increment / decrement` 是修改状态的方法
接下来我们就能直接使用了
function Counter() {
const { count, increment, decrement } = useCounterStore()
return (
<div>
<button onClick={decrement}>-</button>
<span>{count}</span>
<button onClick={increment}>+</button>
</div>
)
}
如果我们需要在别的组件使用这个count,也是同样这样导入就行了。
因此,整个zustand确实非常简单,那么什么场景适合用zustand呢?
- 中小型项目
- 独立开发或小团队
- 对状态结构要求不复杂
- 不想引入 Redux 的完整体系
以上内容如有错误欢迎评论修改🙏🙏🙏
参考
react:useReducer – React 中文文档
redux toolkit: https://redux-toolkit.js.org/
zustand: https://zustand.docs.pmnd.rs/getting-started/introduction

