Skip to content

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-redux
jsx
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。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例一:创建切片 + 创建仓库(同步) ​

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.reducer
js
// src/store/index.js
import { configureStore } from '@reduxjs/toolkit'
import count from './slices/countSlice'

const store = configureStore({
  reducer: {
    count
  }
})

export default store
js
// 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.reducer
js
// 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。

常见错误 ​

  1. 组件里 useSelector 报错“could not find react-redux context value” → 根组件未用 Provider 包裹或 store 未正确绑定 → 在入口用 <Provider store={store}> 包裹 <App/>。
  2. 修改数据时直接调用 reducer 或传普通对象 → 混淆了同步 actionCreator 与 dispatch 用法 → 统一写成 dispatch(addNum(payload))。
  3. 异步成功后页面数据不变 → fulfilled 分支未使用 action.payload,或 extraReducers 未配置对应 addCase → 在 asyncAddNum.fulfilled 中读取 action.payload 并更新 state。
  4. createAsyncThunk 的 pending/fulfilled/rejected 配错或漏配 → 只写了同步 reducers,误以为异步也会自动生成分支 → 在 extraReducers(builder) 中分别 addCase(asyncThunk.pending/fulfilled/rejected, ...)。
  5. 组件拿不到切片数据 → useSelector 回调返回了错误层级,如 state.count 后又取错字段 → 按切片结构返回,如 useSelector(state => state.count.num)。
  6. 导出/解构 actionCreator 失败 → 忘记 export const { addNum } = countSlice.actions 或默认导出写错 → 检查 slice.actions 解构名与 reducers 方法名一致,并正确 export default countSlice.reducer。

练习 ​

  1. 在组件中读取 count.num,并点击按钮执行 dispatch(addNum(5)),观察页面变化。
  2. 创建 asyncDecNum:2 秒后把传入的 payload 作为成功结果,并在 fulfilled 中减少 state.num。
  3. 给 asyncAddNum 增加 pending 显示 loading、fulfilled 关闭 loading 并累加、rejected 记录错误信息。
  4. 找出一个“useSelector 返回整个 state 导致组件频繁重渲染”的写法,并改成只订阅所需切片字段。
  5. 写出 countSlice 需要向外暴露的 reducer、同步 actionCreator、异步 actionCreator 三类内容。

原视频对照 ​

#视频时长
1redux作用介绍04:14
2redux核心概念及相互关系11:54
3数据切片的创建21:03
4store.dispatch修改状态数据05:17
5监听仓库数据的变化-store.subscribe01:41
6redux调试工具的安装及基本使用04:40

小结 ​

  1. store 由各切片 reducer 组合而成,切片负责分类管理状态。
  2. 同步 actionCreator 由 slice.actions 自动生成,直接解构导出即可。
  3. 异步 actionCreator 必须用 createAsyncThunk 手动创建。
  4. 异步状态处理放在 extraReducers,重点区分 pending/fulfilled/rejected。
  5. react-redux 的核心用法是 Provider、useSelector、useDispatch 三件套。
  6. 修改数据统一走 dispatch(actionCreator(payload)),同步异步都一样。
  7. 异步成功结果通过 fulfilled 的 action.payload 进入 reducer。
  8. 调试时重点看每次 action 和 state 的前后差异。