从零理解 Redux:从 state、action、reducer 到 Redux Toolkit 与异步请求

在刚开始学习 React 时,我们通常使用 useState 管理组件内部的数据.

比如一个最简单的计数器:

Kotlin 复制代码
import { useState } from 'react'

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

  return (
    <div>
      <button onClick={() => setCount(count - 1)}>-</button>

      <span>{count}</span>

      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  )
}

它的数据流非常直接:

javascript 复制代码
点击按钮
   ↓
setCount()
   ↓
count 改变
   ↓
组件重新渲染

对于一个小组件,这已经够用了。

但随着项目变大,会出现一个问题:

复制代码
App
├── Header
├── User
├── Product
├── Cart
├── Channel
└── ...

很多组件可能都要使用同一份数据。

比如:

复制代码
用户信息
购物车数量
登录状态
频道列表
主题配置

这时候,如果这些数据全部靠 props 一层一层传递,就会越来越麻烦。

Redux 的作用,可以先用一句话概括:

Redux 把需要共享的数据集中放到一个公共仓库中管理。

这个公共仓库就是:

复制代码
store

一、先别背 Redux 名词,先理解它改变了什么

普通 React:

复制代码
组件
 ↓
直接修改状态
 ↓
状态变化

例如:

复制代码
setCount(count + 1)

Redux 的思想稍微绕了一层:

复制代码
组件
 ↓
告诉 Redux:"我要加一"
 ↓
Redux 根据规定好的逻辑修改数据
 ↓
得到新状态

也就是说:

组件不直接修改公共数据,而是先发出一个"我要干什么"的消息。

比如:

复制代码
我要加一
我要减一
我要删除商品
我要保存用户信息

然后 Redux 再根据规则处理。

这就是理解 Redux 最重要的起点。


二、Redux 最核心的四个概念

Redux 最重要的是:

复制代码
state
action
reducer
store

可以先这样记:

名称 含义
state 当前数据
action 描述"我要做什么"
reducer 根据 state 和 action 决定怎么修改
store 保存和管理 Redux 数据的仓库

下面用计数器把它们串起来。


三、state:当前的数据

假设我们有一个计数器:

复制代码
{
  count: 0
}

这就是 state。

它表示当前 Redux 里的状态:

复制代码
state = {
  count: 0
}

点击加号以后可能变成:

复制代码
state = {
  count: 1
}

所以:

state 就是"现在的数据是什么"。


四、action:描述"我要做什么"

假设用户点击 +

组件不会直接写:

复制代码
state.count++

而是告诉 Redux:

我要执行"增加"操作。

可以使用一个对象描述:

复制代码
{
  type: 'INCREMENT'
}

这个对象就是:

复制代码
action

所以:

复制代码
const action = {
  type: 'INCREMENT'
}

注意:

action 本身并不会修改数据。

它只是一个描述。

类似于你给 Redux 发了一张命令单:

复制代码
复制代码
命令:INCREMENT

到底怎么加,是 reducer 决定的。


五、reducer:真正决定数据怎么变

reducer 是一个函数。

最基本的形式:

复制代码
function reducer(state, action) {

}

它接收两个参数:

复制代码
state
当前数据

action
这次想做什么

例如:

复制代码
function reducer(state = { count: 0 }, action) {
  if (action.type === 'INCREMENT') {
    return {
      count: state.count + 1
    }
  }

  if (action.type === 'DECREMENT') {
    return {
      count: state.count - 1
    }
  }

  return state
}

假设当前:

复制代码
state = {
  count: 0
}

收到:

复制代码
action = {
  type: 'INCREMENT'
}

那么:

复制代码
action.type === 'INCREMENT'

成立。

于是返回:

复制代码
{
  count: 1
}

所以 Redux 最核心的计算关系其实就是:

复制代码
旧 state
+
action
↓
reducer
↓
新 state

或者写成:

复制代码
state + action → reducer → newState

六、store:真正保存 Redux 数据的仓库

有了 reducer 后,可以创建 store。

早期 Redux 中:

复制代码
const store = createStore(reducer)

可以理解为:

根据 reducer 创建一个 Redux 仓库,这个仓库叫 store

为什么创建 store 时要传:

复制代码
reducer

因为 store 必须知道:

以后收到 action 后,到底应该按照什么规则修改数据?

这个规则就是 reducer。

所以:

复制代码
reducer
   ↓
createStore(reducer)
   ↓
store

七、dispatch:把 action 发给 Redux

如果想让 count 加一,需要:

复制代码
store.dispatch({
  type: 'INCREMENT'
})

dispatch 可以先理解成:

发送 action。

也就是:

复制代码
dispatch
=
发命令

整个过程:

复制代码
store.dispatch({
  type: 'INCREMENT'
})
        ↓
Redux 收到 action
        ↓
把当前 state 和 action 交给 reducer
        ↓
reducer(state, action)
        ↓
生成新的 state
        ↓
store 保存

所以:

action 负责描述,dispatch 负责发送,reducer 负责真正计算新状态。


八、getState:获取 Redux 当前数据

如果想看看仓库里现在的数据:

复制代码
store.getState()

比如:

复制代码
console.log(store.getState())

可能得到:

复制代码
{
  count: 1
}

所以可以简单记:

复制代码
dispatch → 发数据修改指令

getState → 拿当前数据

九、原生 Redux 的完整流程

现在把前面的知识连起来:

复制代码
用户点击 +
   ↓

dispatch({
  type: 'INCREMENT'
})

   ↓

action
{
  type: 'INCREMENT'
}

   ↓

reducer(state, action)

   ↓

return {
  count: state.count + 1
}

   ↓

store 保存新的 state

Redux 最核心的数据流就是这么简单。


十、Redux Toolkit 是什么

原生 Redux 思想并不复杂,但是代码写起来比较繁琐。

所以现在实际 React 项目中通常使用:

复制代码
Redux Toolkit

简称:

复制代码
RTK

它不是另一套状态管理。

它本质上还是 Redux,只不过:

帮你把 Redux 写得更简单。

常见功能包括:

复制代码
configureStore
createSlice
异步 thunk
Immer

所以:

复制代码
Redux = 核心思想

Redux Toolkit = 更方便地写 Redux

十一、react-redux 又是什么

另外还有一个:

复制代码
react-redux

它和 Redux Toolkit 不是一回事。

可以这样理解:

复制代码
Redux Toolkit
↓
帮助你写 Redux


react-redux
↓
帮助 React 组件使用 Redux

比如 React 组件要读取 Redux:

复制代码
useSelector()

React 组件要发送 action:

复制代码
useDispatch()

这些都是:

复制代码
react-redux

提供的。

因此:

复制代码
Redux
        ↑
   react-redux
        ↓
React组件

十二、项目中的 store 目录为什么要拆分

实际项目通常会这样设计:

复制代码
store
├── modules
│   ├── counterStore.js
│   └── channelStore.js
│
└── index.js

含义:

复制代码
counterStore.js
↓
只负责计数器

channelStore.js
↓
只负责频道数据

index.js
↓
把所有模块组合成最终 store

这样就不会把所有 Redux 逻辑都堆在一个文件中。


十三、createSlice:Redux Toolkit 的核心

来看计数器模块:

javascript 复制代码
import { createSlice } from '@reduxjs/toolkit'

const counterStore = createSlice({
  name: 'counter',

  initialState: {
    count: 0
  },

  reducers: {
    increment(state) {
      state.count++
    },

    decrement(state) {
      state.count--
    }
  }
})

这里:

复制代码
createSlice()

可以理解成:

帮我创建一个 Redux 子模块。

它里面主要有三个东西。


1. name

复制代码
name: 'counter'

表示:

这个模块叫 counter。

Redux Toolkit 后面生成 action type 时会用到它。


2. initialState

复制代码
initialState: {
  count: 0
}

表示初始状态:

复制代码
{
  count: 0
}

3. reducers

复制代码
reducers: {
  increment(state) {
    state.count++
  },

  decrement(state) {
    state.count--
  }
}

这里定义:

不同操作应该怎么修改 state。

例如:

复制代码
increment(state) {
  state.count++
}

真正表达的是:

如果执行 increment 操作,就让 count + 1。


十四、为什么 Redux Toolkit 可以直接写 state.count++

前面原生 Redux 通常要求:

复制代码
return {
  count: state.count + 1
}

而 Redux Toolkit 却可以:

复制代码
state.count++

原因是 RTK 内部使用了:

复制代码
Immer

所以你表面上看起来像直接修改 state:

复制代码
state.count++

实际上 Immer 会帮你生成新的不可变 state。

因此在 createSlice 的 reducers 中,这种写法是正常的。


十五、最容易混乱的地方:两个 increment

这是我们学习过程中最容易乱的一个地方。

你写了:

复制代码
reducers: {
  increment(state) {
    state.count++
  }
}

然后 Redux Toolkit 又会自动生成:

复制代码
counterStore.actions.increment

于是看起来像有两个 increment

它们虽然名字一样,但不是一个东西。

可以这样理解。

reducers 里的 increment

复制代码
increment(state) {
  state.count++
}

它的职责是:

真正修改 state。

可以把它脑补成:

复制代码
执行加一逻辑

actions 里的 increment

复制代码
counterStore.actions.increment

它是 RTK 自动生成的:

action creator。

它的职责不是修改 state。

它只是:

创建一个 action 对象。

调用:

复制代码
increment()

会得到类似:

复制代码
{
  type: 'counter/increment'
}

所以它只是发出:

我要 increment。

真正执行:

复制代码
state.count++

的还是 reducer。

整个关系:

复制代码
actions.increment()
↓
生成"我要加一"的 action


dispatch(...)
↓
把 action 发出去


reducers.increment(state)
↓
真正执行加一

这是理解 Redux Toolkit 非常关键的一点。


十六、action creator 是什么

所谓:

复制代码
action creator

直译就是:

action 创建器。

它就是一个专门生成 action 对象的函数。

比如:

复制代码
increment()

可能返回:

复制代码
{
  type: 'counter/increment'
}

所以:

复制代码
dispatch(increment())

本质上相当于:

复制代码
dispatch({
  type: 'counter/increment'
})

Redux Toolkit 只是帮我们自动生成了 action。


十七、导出 action 和 reducer

通常会这样写:

复制代码
const {
  increment,
  decrement
} = counterStore.actions

这是对象解构。

因为:

复制代码
counterStore.actions

大概类似:

复制代码
{
  increment: 一个 action creator,
  decrement: 一个 action creator
}

然后:

复制代码
const counterReducer = counterStore.reducer

得到这个模块的 reducer。

最终:

复制代码
export { increment, decrement }

export default counterReducer

这里可以记:

复制代码
increment / decrement
↓
给组件 dispatch 用


counterReducer
↓
给 configureStore 用

十八、configureStore:组合 Redux 模块

store/index.js

javascript 复制代码
import { configureStore } from '@reduxjs/toolkit'

import counterReducer from './modules/counterStore'

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
})

export default store

这里:

复制代码
counter: counterReducer

表示:

把 counter 模块加入整个 Redux store。

最终 Redux 总 state 的结构大概是:

复制代码
{
  counter: {
    count: 0
  }
}

所以:

复制代码
state.counter

得到:

复制代码
{
  count: 0
}

继续:

复制代码
state.counter.count

得到:

复制代码
0

十九、useSelector:React 组件读取 Redux 数据

在 React 中:

复制代码
const { count } = useSelector(
  state => state.counter
)

这里:

复制代码
state

指:

Redux 的整个 state。

假设:

复制代码
state = {
  counter: {
    count: 0
  }
}

那么:

复制代码
state.counter

得到:

复制代码
{
  count: 0
}

然后:

复制代码
const { count } = ...

得到:

复制代码
count = 0

所以:

useSelector 用来从 Redux 中读取数据。

可以简单记:

复制代码
useSelector = 读

二十、useDispatch:React 组件发送 action

首先:

复制代码
const dispatch = useDispatch()

相当于:

从 Redux 拿到 dispatch 函数。

然后:

复制代码
<button
  onClick={() => dispatch(increment())}
>
  +
</button>

点击以后:

复制代码
increment()
↓
生成 action

{
  type: 'counter/increment'
}

↓
dispatch 发送
↓
Redux 找到 reducer
↓
执行:

state.count++

于是 Redux 数据更新。


二十一、React + Redux 完整闭环

把:

复制代码
useSelector

和:

复制代码
useDispatch

放在一起,就形成完整数据流:

复制代码
Redux store
   ↓
useSelector
   ↓
React拿到 count
   ↓
页面显示 count
   ↓
用户点击按钮
   ↓
dispatch(increment())
   ↓
Redux reducer
   ↓
state.count++
   ↓
Redux state更新
   ↓
useSelector得到新值
   ↓
React重新渲染

这才是 React + Redux 最完整的一条线。


二十二、action 怎么传参数

前面的:

复制代码
increment()

不需要参数,因为它永远:

复制代码
+1

但如果需求变成:

加 10、加 20、加任意数字

就需要传参数。

例如:

javascript 复制代码
reducers: {
  addToNum(state, action) {
    state.count += action.payload
  }
}

组件:

复制代码
dispatch(addToNum(10))

Redux Toolkit 会让:

复制代码
addToNum(10)

生成类似:

复制代码
{
  type: 'counter/addToNum',
  payload: 10
}

所以:

复制代码
action.payload

就是:

复制代码
10

最后:

复制代码
state.count += action.payload

相当于:

复制代码
state.count += 10

二十三、payload 到底是什么

可以直接记:

复制代码
type
=
我要做什么


payload
=
做这件事情需要的数据

例如:

复制代码
{
  type: 'counter/addToNum',
  payload: 20
}

翻译成人话:

我要执行 addToNum,这次带的数据是 20。

所以:

复制代码
dispatch(addToNum(20))

完整流程:

复制代码
addToNum(20)
↓
生成:

{
  type: 'counter/addToNum',
  payload: 20
}

↓
dispatch
↓
reducer
↓
action.payload
↓
20
↓
state.count += 20

二十四、Redux 异步请求为什么又多一层

同步修改:

复制代码
dispatch(increment())

马上就可以计算。

但是请求接口不一样。

比如:

复制代码
await axios.get(url)

需要等待服务器返回。

所以异步 Redux 一般采用:

复制代码
异步函数负责请求数据

同步 reducer 负责把数据存进 store

这是异步 Redux 最核心的思想。


二十五、频道列表异步请求示例

先创建频道模块:

javascript 复制代码
import { createSlice } from '@reduxjs/toolkit'
import axios from 'axios'

const channelStore = createSlice({
  name: 'channel',

  initialState: {
    channelList: []
  },

  reducers: {
    setChannels(state, action) {
      state.channelList = action.payload
    }
  }
})

这里:

复制代码
setChannels(state, action)

只负责一件事:

把频道数据存到 Redux。

例如:

javascript 复制代码
action.payload = [
  { id: 0, name: '推荐' },
  { id: 1, name: 'HTML' }
]

那么:

复制代码
state.channelList = action.payload

完成数据保存。


二十六、异步函数本身不直接修改 state

接下来:

javascript 复制代码
const { setChannels } = channelStore.actions

const fetchChannelList = () => {
  return async (dispatch) => {
    const res = await axios.get(
      'http://geek.itheima.net/v1_0/channels'
    )

    dispatch(
      setChannels(res.data.data.channels)
    )
  }
}

重点是:

复制代码
fetchChannelList

负责:

复制代码
请求接口

而:

复制代码
setChannels

负责:

复制代码
修改 Redux state

所以不要把它们混在一起。

可以把它们理解为:

复制代码
fetchChannelList
= 去拿货


setChannels
= 把货放进仓库

二十七、为什么异步函数 return 另一个函数

复制代码
const fetchChannelList = () => {
  return async (dispatch) => {

  }
}

调用:

复制代码
fetchChannelList()

得到的不是普通 action 对象,而是:

复制代码
async (dispatch) => {
  ...
}

也就是一个函数。

Redux Toolkit 默认支持 thunk,因此:

复制代码
dispatch(fetchChannelList())

可以 dispatch 一个函数。

这个函数会拿到:

复制代码
dispatch

于是可以:

复制代码
const res = await axios.get(url)

dispatch(setChannels(...))

也就是:

复制代码
先异步请求
↓
请求完成
↓
再 dispatch 同步 action
↓
reducer 保存数据

二十八、异步 Redux 的完整流程

组件:

复制代码
const dispatch = useDispatch()

useEffect(() => {
  dispatch(fetchChannelList())
}, [dispatch])

执行:

复制代码
组件加载
↓
useEffect
↓
dispatch(fetchChannelList())
↓
fetchChannelList 返回异步函数
↓
Redux thunk 执行这个函数
↓
axios.get()
↓
接口返回频道列表
↓
dispatch(setChannels(频道数据))
↓
生成同步 action
↓
setChannels reducer
↓
state.channelList = action.payload
↓
Redux 更新
↓
useSelector 获取新数据
↓
组件重新渲染

所以异步 Redux 可以浓缩为:

先请求,再 dispatch;异步函数负责拿数据,同步 reducer 负责存数据。


二十九、同步和异步 Redux 对比

同步:

复制代码
dispatch(increment())

流程:

复制代码
action
↓
reducer
↓
state

异步:

复制代码
dispatch(fetchChannelList())

流程:

复制代码
异步函数
↓
请求服务器
↓
拿到数据
↓
dispatch(setChannels(data))
↓
action
↓
reducer
↓
state

所以异步只是在同步 Redux 前面又多了一步:

复制代码
先把数据拿回来

最终修改 store,还是靠同步 reducer。


三十、完整 counterStore 示例

复制代码
import { createSlice } from '@reduxjs/toolkit'

const counterStore = createSlice({
  name: 'counter',

  initialState: {
    count: 0
  },

  reducers: {
    increment(state) {
      state.count++
    },

    decrement(state) {
      state.count--
    },

    addToNum(state, action) {
      state.count += action.payload
    }
  }
})

export const {
  increment,
  decrement,
  addToNum
} = counterStore.actions

export default counterStore.reducer

三十一、完整 store 配置

复制代码
import { configureStore } from '@reduxjs/toolkit'

import counterReducer from './modules/counterStore'
import channelReducer from './modules/channelStore'

const store = configureStore({
  reducer: {
    counter: counterReducer,
    channel: channelReducer
  }
})

export default store

最终 state:

复制代码
{
  counter: {
    count: 0
  },

  channel: {
    channelList: []
  }
}

三十二、组件完整使用示例

复制代码
import {
  useDispatch,
  useSelector
} from 'react-redux'

import {
  increment,
  decrement,
  addToNum
} from './store/modules/counterStore'

function App() {
  const { count } = useSelector(
    state => state.counter
  )

  const dispatch = useDispatch()

  return (
    <div>

      <button
        onClick={() => dispatch(decrement())}
      >
        -
      </button>

      <span>{count}</span>

      <button
        onClick={() => dispatch(increment())}
      >
        +
      </button>

      <button
        onClick={() => dispatch(addToNum(10))}
      >
        +10
      </button>

    </div>
  )
}

export default App

三十三、最后真正需要记住的 Redux 数据流

如果前面全忘了,只要记住下面这条:

复制代码
React组件
   ↓
dispatch(action)
   ↓
Redux收到action
   ↓
reducer根据 action 修改 state
   ↓
store保存新的state
   ↓
useSelector拿到新state
   ↓
React组件重新渲染

使用 Redux Toolkit 后:

复制代码
createSlice
↓
帮你生成:

action creator
+
reducer

其中:

复制代码
action creator
负责说:"我要做什么"

reducer
负责真正修改 state

例如:

复制代码
dispatch(increment())

不是 increment() 自己让 count 加一。

而是:

复制代码
increment()
↓
生成"我要加一"的 action
↓
dispatch
↓
Redux 找到 reducer
↓
increment(state)
↓
state.count++

再比如:

复制代码
dispatch(addToNum(10))

就是:

复制代码
addToNum(10)
↓
生成:

{
  type: 'counter/addToNum',
  payload: 10
}

↓
dispatch
↓
reducer
↓
action.payload = 10
↓
state.count += 10

如果是异步:

复制代码
dispatch(fetchChannelList())
↓
请求接口
↓
拿到数据
↓
dispatch(setChannels(data))
↓
reducer
↓
数据存入 store

这就是 Redux 从原理到 Redux Toolkit、React-Redux,再到异步请求的完整知识链

相关推荐
烈风逍遥1 小时前
第四篇:AI 模块架构设计:多 Provider 切换、RAG 知识库与 Agent 编排
前端·后端·架构
用户80806181436931 小时前
JavaScript 面向对象入门到精通
前端·javascript
吠品1 小时前
STM32最小系统板引脚梳理与配置实操
前端·javascript·vue.js
曹一二2 小时前
前端性能优化场景题:图片懒加载 + 大数据渲染
前端
GISer_Jing2 小时前
Come on,工作总结
前端·ai·前端框架
Hilaku2 小时前
Sass 和 Less 在 2026 年彻底多余了吗?
前端·javascript·程序员
百慕大三角2 小时前
AI 写代码最大的风险不是不会写,而是太能写:我给 Coding Agent 加的 4 层工程约束
前端·ai编程·trae
用户921080262862 小时前
从 EventSource 到可复用 SSE Client:我如何实现多实例、双超时与自动重连
前端
咩咩啃树皮2 小时前
ES6 Set 核心特点 + 最简数组去重
前端·javascript·html