主题
react-redux基本使用与redux-异步操作(Day9 1/3)
本页对应 React + TypeScript + 医通项目,共 6 段视频,约 48 分钟。
学习目标
- 能够用自己的话解释 Redux 集中式状态管理的作用,并说明 store、slice、reducer、action、action creator、dispatch 之间的对应关系。
- 能够使用
@reduxjs/toolkit的createSlice定义切片名称、初始状态和 reducer,并说明切片actions中同名 action creator 的自动生成机制。 - 能够使用
store.dispatch(actionCreator(payload))触发 reducer 修改指定切片状态,并通过打印或 DevTools 验证修改结果。 - 能够使用
store.subscribe监听仓库状态变化,在回调中读取最新状态,并调用其返回函数取消监听。 - 能够安装并启用 Redux DevTools,查看仓库 state 树、action 对象及状态 diff,定位一次 dispatch 引起的数据变化。
- 能够概述 react-redux 的基本连接方式、Redux 异步操作的处理位置,以及
@reduxjs/toolkit对 Redux 使用流程的简化作用。
知识点
先理解:为什么要用 react-redux
Redux 的核心作用是对数据状态做集中式管理。转写稿里反复强调:如果数据散落在各个组件里,兄弟组件、父子组件之间频繁传递就会很麻烦;有了仓库后,组件需要数据就去仓库取,需要修改就向仓库派发任务。redux toolkit 本身可以独立于 React 使用,而 react-redux 的作用,是让 React 组件更方便地连接这个仓库。
所以本节的目标不是重新讲一遍 Redux 原理,而是把“React 组件如何读仓库、如何改仓库”跑通。主线只有四步:安装 react-redux、用 Provider 绑定 store、用 useSelector 读数据、用 useDispatch 派发修改。
第一步:安装并绑定 Provider
为什么需要 Provider?因为组件里的 hooks 要知道“去哪个仓库取数据”。Provider 就是 React 和 Redux 之间的入口,把创建好的 store 放到 React 组件树的最外层。
怎么做:先安装,再用 Provider 包住 App。
bash
npm i react-reduxjsx
import {Provider} from 'react-redux'
<Provider store={store}>
<App/>
</Provider>这里的 store 必须是前面创建好的仓库实例,不是随便一个对象。易错点是:Provider 要包在使用 useSelector、useDispatch 的组件外层;如果组件不在 Provider 内部,hooks 就取不到仓库上下文。调试时可以先确认 store 已经正确导入,再检查包裹层级。
第二步:读取数据 useSelector
为什么不用普通变量取仓库数据?因为组件需要和仓库建立连接,仓库状态变化后组件才能拿到对应数据。useSelector 就是读取入口。
怎么想:它的回调函数参数是“整个仓库的数据 state”,回调函数的返回值,就是 useSelector 的返回值。所以可以返回整个 state 调试,也可以只取某个切片。
jsx
/**
* 作用:获取redux中的状态数据
* 语法:useSelector(回调函数)
* 回调函数的参数,就是整个仓库的数据 state
* 回调函数的返回值,就是useSelector函数调用的返回值
*
*/
// let res = useSelector(state=>{
// console.log('state: ', state);
// return state;
// })
// let count = useSelector(state=>state.count) // 获取count切片的数据
// let user = useSelector(state=>state.user);// 获取user切片的数据
let {num} = useSelector(state=>state.count);转写稿里提到,可以先 console.log('state: ', state) 看整个仓库结构,再决定取哪个切片。易错点:回调参数是整个仓库 state,不是某个切片;如果要取 count 切片里的 num,应写成 state.count.num 或像笔记里那样先取 state.count 再解构。不要误写成 state.num。
第三步:修改数据 useDispatch
为什么不能直接改?Redux 里数据修改要交给 reducer 执行,组件只负责“派发需求”。useDispatch 返回一个 dispatch 函数,用它把 action 交给仓库。
jsx
// useDispatch 创建一个dispatch函数
const dispatch = useDispatch();
dispatch(actionCreator(payload))怎么理解 actionCreator(payload)?转写稿里用“产品经理创建需求”作比喻:actionCreator 调用后返回一个 action 对象,里面通常有 type 和 payload;dispatch 把这个 action 分发出去,仓库根据 type 找到对应 reducer 方法执行,payload 就是本次修改要用的数据。
易错点有三个:第一,不要直接写 state.num++ 改仓库;第二,dispatch 里传的是 actionCreator(payload) 的执行结果,不是只传 actionCreator 函数名;第三,payload 要和 reducer 里读取的 action.payload 对应。调试时可以先用 store.getState() 看修改前后的值。
调试与验证:getState、subscribe 和 DevTools
转写稿里还补充了两个验证手段。一个是 store.getState(),可以直接看仓库最新状态;另一个是 store.subscribe(callback),仓库数据变化时触发回调,并且它返回一个取消监听的函数。
jsx
const unsubscribe = store.subscribe(() => {
console.log(store.getState())
})
// 需要取消监听时
// unsubscribe()易错点:回调里应通过 store.getState() 取最新数据;取消监听要调用 subscribe 返回的函数。浏览器里还可以用 Redux DevTools 查看 state、每次派发的 action 以及数据差异(diff),帮助确认 dispatch 后到底改了什么。
小结:一条完整链路
react-redux 的基本使用可以串成:先创建好 store,用 <Provider store={store}> 包住应用;组件里用 useSelector(state => ...) 读数据;用 useDispatch() 得到 dispatch;再 dispatch(actionCreator(payload)) 派发修改;最后由 reducer 执行修改。验证时用 store.getState()、subscribe 或 Redux DevTools 观察变化。记住易错点:Provider 包裹层级要对,useSelector 回调参数是整个仓库 state,dispatch 要传 action 对象而不是直接改数据。
一、先理解:异步操作为什么要单独放进 actionCreator
为什么需要: Redux 的核心是集中管理状态,同步修改通常由 reducer 根据 action 完成。但异步请求有等待、成功、失败三个阶段,不能把 axios、setTimeout 直接写进普通 reducer。转写稿里用“项目经理、产品经理、程序员”作比喻:actionCreator 像产品经理,负责创建需求;reducer 像程序员,负责按需求改数据;dispatch 像分配任务。到了异步场景,发请求、等结果这类“耗时工作”应交给异步 actionCreator,而不是让 reducer 干副作用。
怎么想: 先明确分工:异步 actionCreator 负责执行异步代码并返回 Promise;reducer 只负责在不同阶段根据 action 修改 state。Redux Toolkit 提供 createAsyncThunk,它会自动生成 pending、fulfilled、rejected 三种 action,供 extraReducers 接收。
怎么做: 用 createAsyncThunk(typePrefix, payloadCreator) 创建异步 actionCreator。第一个参数是 action 类型前缀,第二个参数是真正执行异步逻辑的函数。
易错点: 不要把请求写进 reducers 的同步方法里;不要忘记 return 异步结果,否则 fulfilled 里拿不到 payload。笔记中的“卸载异步的 actionCreator”应理解为“异步操作的代码写在异步 actionCreator 中”。
二、创建异步 actionCreator:createAsyncThunk
为什么需要: 异步请求的结果要变成 action 的 payload,才能被 reducer 使用。createAsyncThunk 把“发请求、等结果、成功/失败”封装成标准 action 流程。
怎么想: asyncAddNum 中用 axios.get 请求接口,成功后将 data.total_count 返回;这个返回值会成为成功 Promise 的结果值,也就是后续 fulfilled 分支里的 action.payload。asyncDecNum 则用 setTimeout 模拟两秒后 resolve(payload)。
怎么做: 保留笔记代码如下:
js
/**
* 想实现异步的操作,需要有异步的产品经理[actionCreator] 和 异步的程序员
*
* 异步的actionCreator:需要手动创建 createAsyncThunk 函数创建,
* 异步的reducer: 需要配置在 extraReducers中, 【pending、fulfilled、rejected 】
*/
// 创建异步的产品经理
export const asyncAddNum = createAsyncThunk('count/addNum', async (payload) => {
let { data } = await axios.get('https://api.github.com/search/users?q=aa')
// console.log('data: ',data);
return data.total_count;// 会得到一个成功的promise,成功的结果值是 total_count
// return Promise.reject('error123123')
})
export const asyncDecNum = createAsyncThunk('count/decNum', (payload)=>{
return new Promise((resolve, reject)=>{
setTimeout(()=>{
resolve(payload)
},2000)
})
})易错点: createAsyncThunk 的第一个参数不是最终完整 action type,而是前缀;三种状态 action 会基于它生成。asyncAddNum 中返回的是 data.total_count,不是整个 response。若要模拟失败,可用 throw 或返回 Promise.reject(...),不要只在注释里写。
三、创建异步 reducer:extraReducers 的三种分支
为什么需要: 异步 action 不会走普通 reducers 中同名方法,而要在 extraReducers 中监听 pending、fulfilled、rejected。这样能分别处理“请求中”“成功”“失败”。
怎么想: pending 适合做加载提示;fulfilled 才是成功结果落地的地方;rejected 处理失败。笔记强调:fulfilled 分支的 action.payload 就是异步 actionCreator 成功 Promise 的结果值。
怎么做: 用 builder.addCase 逐个添加分支,保留代码如下:
js
// 异步的程序员
extraReducers: (builder) => {
builder
.addCase(asyncAddNum.pending, (state, action) => {
console.log('pending action: ', action);
})
// 如果是一个成功的promise,成功的结果值就是action的payload属性值
.addCase(asyncAddNum.fulfilled, (state, action)=>{
console.log('fulfilled action', action);
state.num += action.payload
})
.addCase(asyncAddNum.rejected, (state, action)=>{
console.log('rejected action: ', action);
})
.addCase(asyncDecNum.fulfilled, (state, {payload})=>{
state.num -= payload
})
}易错点: 不要在 pending 里提前加最终数据;不要在 fulfilled 里写 action.response,应取 action.payload。asyncDecNum 只写了 fulfilled,表示成功后才减;如果失败也要处理,应补 rejected。写分支时不要漏掉 asyncAddNum.fulfilled 这种完整写法。
四、串起来看:异步流程与调试思路
为什么需要: 异步 action 的价值在于把“请求”和“状态修改”串成可追踪流程。转写稿中同步流程是 dispatch(actionCreator(...)) 后由 reducer 改 state;异步流程则是 dispatch 异步 actionCreator 后,先出现 pending,请求成功出现 fulfilled,失败出现 rejected。
怎么想: 调试时重点看 action 的 type 和 payload。如果 fulfilled 中 action.payload 是 undefined,先检查异步函数是否 return 了结果;如果数据没变,检查是否写进了 extraReducers 而不是普通 reducers。转写稿提到可用 store.subscribe 监听仓库变化,也可用 Redux DevTools 看 state 和 action;异步场景下同样应观察三种生命周期 action 的顺序和 payload。
怎么做: 先保证同步 reducer 能正常改数据,再把异步请求移入 createAsyncThunk,最后在 extraReducers 中只处理 fulfilled 的最终赋值。每次只验证一个分支:先看 pending 是否触发,再看 fulfilled 的 payload 是否正确,最后补 rejected。
易错点: 不要直接在组件里绕过 dispatch 改仓库;不要以为 pending 的 action.payload 是请求结果;不要混淆 actionCreator 与 reducer 方法名。把异步代码集中在 actionCreator、把状态修改集中在 extraReducers,流程才清晰。
先厘清 Redux Toolkit 的定位
为什么需要:React 组件之间传数据时,如果状态分散在各个组件里,兄弟组件、深层组件都要靠 props 一层层传,修改也不集中。Redux Toolkit 的思路是建一个“数据仓库”,组件都从仓库取数据,也都通过仓库改数据。
怎么想:把 store 想成项目经理,slice 是它管理的子项目;reducer 是具体改数据的程序员,action 是需求,actionCreator 是造需求的人,dispatch 是派任务。
怎么做:先创建 slice,再由 slice 组成 store;组件里通过 dispatch 派发 action,reducer 根据 action 修改 state。
易错点:Redux Toolkit 不是只能给 React 用,它本身是独立的数据管理工具;react-redux 才是把仓库接进 React 组件的桥。
store:用 configureStore 组装仓库
为什么需要:slice 只是分类后的“子项目”,最终要有一个总仓库给应用使用。
怎么想:configureStore 接收 reducer 配置,把各个 slice 的 reducer 注册进仓库。本节笔记中的仓库写法是:
js
const store = configureStore({
reducer:{
count
}
})怎么做:先有 count 这个 slice 的 reducer,再把它放进 reducer 字段。之后组件或调试代码通过 store.dispatch 改状态,通过 store.getState() 看状态。
易错点:reducer 里放的是切片 reducer,不是整个 slice 对象;命名要和后续 useSelector 取状态时的字段对应。
slice:一个切片要配齐 name、state、reducer、extraReducers
为什么需要:仓库数据多了要分类,否则所有状态堆在一起很乱。slice 就是把某一类数据和修改它的逻辑放在一起。
怎么想:切片里要有切片名 name、初始状态 state、同步修改逻辑 reducer、异步相关逻辑 extraReducers。转写稿里把 reducer 比作“程序员”:它接收当前 state 和 action,按需求改数据。
怎么做:同步 reducer 中写修改方法,例如给 num 加 action.payload;Redux Toolkit 会自动在 slice.actions 上生成同名同步 actionCreator。异步 actionCreator 需要手动用 createAsyncThunk 定义,异步过程放到 extraReducers 里处理。
易错点:同步 actionCreator 不需要自己写,直接从 slice.actions 解构;不要把 reducer 方法本身当 actionCreator 调用。切片对外通常这样暴露:默认暴露 reducer,分别暴露同步 actionCreator,分别暴露异步 actionCreator。
actionCreator 与 dispatch:需求怎么变成修改
为什么需要:reducer 不能直接被组件调用,必须通过 action 描述“要做什么”,再由 dispatch 派发。
怎么想:调用同步 actionCreator 会返回一个 action 对象,里面有 type 和 payload;type 用来找到对应 reducer,payload 是本次修改携带的数据。
怎么做:同步修改写成 dispatch(同步actionCreator(payload));异步修改写成 dispatch(异步actionCreator(payload))。转写稿中的调试过程是:初始 num 为 99,执行 dispatch(addNum(3)) 后变成 102;再次派发加 5 后变成 107。
易错点:dispatch 里要传 actionCreator 调用后的结果,不是传 actionCreator 函数本身;异步 actionCreator 被 dispatch 后返回的是 thunk,具体结果在 extraReducers 中按异步状态处理。
subscribe 与调试工具:确认状态真的变了
为什么需要:只看代码不容易确认 reducer 是否执行、payload 是否正确。
怎么想:store.subscribe 可以监听仓库变化,变化后执行回调;它返回取消监听的函数。Redux DevTools 则能看初始 state、每次 action、state 差异。
怎么做:在回调里打印 store.getState();在 DevTools 中查看 INIT、每次 addNum 的 action 对象和 state 从 99→102→107 的变化。
易错点:subscribe 的回调不会自动给你最新 state,要自己调用 getState();取消监听函数要保存并在合适时机调用。
react-redux:把仓库接进 React
为什么需要:Redux Toolkit 管数据,React 组件还需要读取和派发。
怎么想:Provider 负责把 store 提供给根组件;useSelector 从仓库取状态;useDispatch 拿到 dispatch 函数。
怎么做:根组件外层包 Provider 并绑定 store;组件内用 useSelector 取所需字段,用 useDispatch 创建 dispatch,再按同步或异步方式派发。
易错点:Provider 要包住使用状态的组件树;useSelector 尽量只取需要的字段;修改状态时不要直接改仓库对象,仍要通过 dispatch 走 reducer。
课堂画面






代码示例
示例一:创建切片 + 创建仓库(同步)
js
// src/store/slices/countSlice.js
import { createSlice } from '@reduxjs/toolkit'
const countSlice = createSlice({
name: 'count', // 切片名
initialState: { // 切片的数据
num: 99,
msg: 'hello'
},
reducers: { // 同步的程序员
addNum(state, action) {
state.num += action.payload
},
decNum(state, action) {
state.num -= action.payload
},
changeMsg(state, action) {
state.msg += action.payload
}
}
})
// 分别暴露同步的 actionCreator(redux 自动创建在切片的 actions 属性上)
export const { addNum, decNum, changeMsg } = countSlice.actions
// 默认暴露 reducer
export default countSlice.reducerjs
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import count from './slices/countSlice'
const store = configureStore({
reducer: {
count
}
})
export default storejs
// src/index.js 先验证仓库能跑通,再接入 React
import store from './store'
import { addNum } from './store/slices/countSlice'
console.log('初始 state:', store.getState())
// 先看看 actionCreator 执行后返回的 action 对象长什么样
console.log('action 对象:', addNum(3)) // { type: 'count/addNum', payload: 3 }
// 监听仓库数据的变化
const unsubscribe = store.subscribe(() => {
console.log('仓库数据变了:', store.getState())
})
store.dispatch(addNum(3)) // num: 99 -> 102
store.dispatch(addNum(5)) // num: 102 -> 107
unsubscribe() // 取消监听
store.dispatch(addNum(7)) // 数据仍然会变,但回调不再执行关键行与易错点
name决定 action 的 type 前缀,reducers里的方法名决定后缀,所以addNum的 type 固定是count/addNum;type就是用来定位“哪个切片里的哪个方法”干活的。actionCreator不用自己写,往reducers里加一个方法,redux 就自动在slice.actions上生成同名函数,它的返回值就是{ type, payload }。- 修改数据只能靠
dispatch(actionCreator(payload)),不能直接改store.getState()里的数据。 reducer里写的是state.num += action.payload,RTK 内部用 Immer,允许“直接改”,不要写成return { ...state, num: ... }。store.subscribe返回的是取消监听函数,不是取消的返回值,别写成unsubscribe变量但忘了调用。- 顺序别搞反:先
subscribe再dispatch,否则第一次变化监听不到。
示例二:异步 actionCreator + extraReducers
js
// src/store/slices/countSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
// 异步的 actionCreator:需要手动创建
export const asyncAddNum = createAsyncThunk('count/addNum', async (payload) => {
let { data } = await axios.get('https://api.github.com/search/users?q=aa')
return data.total_count // 会得到一个成功的 promise,成功的结果值就是 total_count
// return Promise.reject('error123123') // 走 rejected 分支
})
export const asyncDecNum = createAsyncThunk('count/decNum', (payload) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(payload)
}, 2000)
})
})
const countSlice = createSlice({
name: 'count',
initialState: {
num: 99,
msg: 'hello'
},
reducers: {
addNum(state, action) {
state.num += action.payload
},
changeMsg(state, action) {
state.msg += action.payload
}
},
// 异步的程序员
extraReducers: (builder) => {
builder
.addCase(asyncAddNum.pending, (state, action) => {
console.log('pending action: ', action)
})
// 如果是成功的 promise,成功的结果值就是 action 的 payload 属性值
.addCase(asyncAddNum.fulfilled, (state, action) => {
console.log('fulfilled action: ', action)
state.num += action.payload
})
.addCase(asyncAddNum.rejected, (state, action) => {
console.log('rejected action: ', action)
})
.addCase(asyncDecNum.fulfilled, (state, { payload }) => {
state.num -= payload
})
}
})
export const { addNum, changeMsg } = countSlice.actions
export default countSlice.reducerjs
// src/index.js
import store from './store'
import { asyncAddNum, asyncDecNum } from './store/slices/countSlice'
store.subscribe(() => {
console.log('仓库数据变了:', store.getState())
})
store.dispatch(asyncAddNum()) // pending -> fulfilled,num 加上 total_count
store.dispatch(asyncDecNum(5)) // pending -> 2 秒后 fulfilled,num -= 5关键行与易错点
- 异步代码写在
createAsyncThunk的回调里,不要写在reducers中;reducers只放同步的。 extraReducers里fulfilled分支的action.payload,就是异步回调return的那个值;rejected分支靠Promise.reject或抛错触发。- 易错点:上面按笔记原样写的
createAsyncThunk('count/addNum', ...)与同步reducers.addNum生成的 type 完全一样(都是count/addNum),两者会撞车,实际项目里把异步的字符串改成'count/asyncAddNum'之类区分开。 pending / fulfilled / rejected三个 case 必须用builder.addCase链式加,不能写成普通对象形式的 key。asyncDecNum的resolve(payload)中的payload就是dispatch(asyncDecNum(5))传进去的 5。
示例三:react-redux 在组件里读取与修改
jsx
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(
<Provider store={store}>
<App />
</Provider>
)jsx
// src/App.jsx
import { useSelector, useDispatch } from 'react-redux'
import {
addNum,
changeMsg,
asyncAddNum,
asyncDecNum
} from './store/slices/countSlice'
export default function App() {
// 回调函数的参数就是整个仓库的数据 state,返回值就是 useSelector 的返回值
// let count = useSelector(state => state.count) // 获取 count 切片的数据
// let { num } = useSelector(state => state.count)
const { num, msg } = useSelector((state) => state.count)
// useDispatch 创建一个 dispatch 函数
const dispatch = useDispatch()
return (
<div>
<p>num:{num}</p>
<p>msg:{msg}</p>
{/* 同步:dispatch(同步 actionCreator(payload)) */}
<button onClick={() => dispatch(addNum(3))}>+3</button>
<button onClick={() => dispatch(changeMsg('!'))}>拼接 !</button>
{/* 异步:dispatch(异步 actionCreator(payload)) */}
<button onClick={() => dispatch(asyncAddNum())}>异步 +</button>
<button onClick={() => dispatch(asyncDecNum(5))}>异步 -5</button>
</div>
)
}关键行与易错点
Provider必须包裹根组件并绑定store,否则子组件里useSelector / useDispatch直接报错。useSelector的回调参数是整个仓库的 state(即{ count: {...} }),所以取数据要写state.count,再解构num / msg;写成state.num会拿到undefined。useSelector的返回值就是回调的返回值,别写成const { num } = useSelector(...)却返回了整个 state。dispatch只接收 action 对象,正确写法是dispatch(addNum(3));写成dispatch(addNum)或dispatch(3)都无效。- 同步和异步调用形式完全一样,区别只在于
actionCreator是不是createAsyncThunk创建的。 - 组件里不要直接
import store去store.dispatch,统一用useDispatch拿到的 dispatch。
常见错误
- 组件里
useSelector报错“could not find react-redux context value” → 根组件未用Provider包裹或store未正确绑定 → 在入口用<Provider store={store}>包裹<App/>。 - 修改数据时直接调用
reducer或传普通对象 → 混淆了同步actionCreator与dispatch用法 → 统一写成dispatch(addNum(payload))。 - 异步成功后页面数据不变 →
fulfilled分支未使用action.payload,或extraReducers未配置对应addCase→ 在asyncAddNum.fulfilled中读取action.payload并更新state。 createAsyncThunk的pending/fulfilled/rejected配错或漏配 → 只写了同步reducers,误以为异步也会自动生成分支 → 在extraReducers(builder)中分别addCase(asyncThunk.pending/fulfilled/rejected, ...)。- 组件拿不到切片数据 →
useSelector回调返回了错误层级,如state.count后又取错字段 → 按切片结构返回,如useSelector(state => state.count.num)。 - 导出/解构 actionCreator 失败 → 忘记
export const { addNum } = countSlice.actions或默认导出写错 → 检查slice.actions解构名与reducers方法名一致,并正确export default countSlice.reducer。
练习
- 在组件中读取
count.num,并点击按钮执行dispatch(addNum(5)),观察页面变化。 - 创建
asyncDecNum:2 秒后把传入的payload作为成功结果,并在fulfilled中减少state.num。 - 给
asyncAddNum增加pending显示 loading、fulfilled关闭 loading 并累加、rejected记录错误信息。 - 找出一个“
useSelector返回整个state导致组件频繁重渲染”的写法,并改成只订阅所需切片字段。 - 写出
countSlice需要向外暴露的 reducer、同步 actionCreator、异步 actionCreator 三类内容。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | redux作用介绍 | 04:14 |
| 2 | redux核心概念及相互关系 | 11:54 |
| 3 | 数据切片的创建 | 21:03 |
| 4 | store.dispatch修改状态数据 | 05:17 |
| 5 | 监听仓库数据的变化-store.subscribe | 01:41 |
| 6 | redux调试工具的安装及基本使用 | 04:40 |
小结
store由各切片reducer组合而成,切片负责分类管理状态。- 同步
actionCreator由slice.actions自动生成,直接解构导出即可。 - 异步
actionCreator必须用createAsyncThunk手动创建。 - 异步状态处理放在
extraReducers,重点区分pending/fulfilled/rejected。 react-redux的核心用法是Provider、useSelector、useDispatch三件套。- 修改数据统一走
dispatch(actionCreator(payload)),同步异步都一样。 - 异步成功结果通过
fulfilled的action.payload进入 reducer。 - 调试时重点看每次
action和state的前后差异。