欢迎光临
我们一直在努力

一文梳理 React 状态管理:从 useState 到 Redux Toolkit / Zustand

目录

状态管理与组件通信的关系

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 的基本流程

  • 创建 Slice(状态 + reducer 的集合)
  • 在 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

    赞(0)
    未经允许不得转载:171主机测评 » 一文梳理 React 状态管理:从 useState 到 Redux Toolkit / Zustand
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址