Redux Toolkit 基础使用
Redux Toolkit(简称 RTK),它是Redux官方推出的官方推荐写法,解决了原生Redux样板代码过多、配置繁琐、需要手动集成中间件等痛点,新项目必须用RTK,而非原生Redux
一、核心认知:Redux Toolkit 是什么 & 解决什么问题
Redux 是 React 的全局状态管理库,用来管理组件之间共享的、跨层级的公共数据(比如用户信息、主题配置、购物车数据等)。 原生 Redux 开发有3大痛点:
- 需要手动写
action、actionCreator、reducer,样板代码巨多; - 必须手动安装
redux-thunk才能处理异步请求; - 状态修改必须写纯函数、深拷贝,容易出错;
- 还要手动组合 reducer、配置 store,步骤繁琐。
Redux Toolkit 就是 Redux 的官方封装工具集,把原生Redux的所有繁琐配置都内置封装,提供了极简的API,核心做到:少写代码、少配依赖、少踩坑,是现在React生态中Redux的唯一推荐方案。
二、核心依赖安装(必装2个)
Redux Toolkit 核心依赖只有2个,无需额外装redux、redux-thunk、immer(这些都内置了),一行命令安装即可:
# npm 安装
npm install @reduxjs/toolkit react-redux
# yarn 安装
yarn add @reduxjs/toolkit react-redux
@reduxjs/toolkit:RTK核心库,封装了创建store、reducer、异步action的所有API;react-redux:连接 Redux 和 React 组件的桥梁,提供Provider、useSelector、useDispatch等核心hooks。
三、核心概念
原生Redux的概念很多(action、actionType、actionCreator、reducer、middleware、combineReducers等),RTK对其做了极大精简和封装,核心概念只剩3个,全部记住:
1. configureStore() - 创建全局唯一的 Store
configureStore 是 RTK 提供的创建store的核心方法,替代了原生Redux的createStore+combineReducers+applyMiddleware。
- 作用:创建全局唯一的状态仓库,所有组件的公共状态都存在这里;
- 特点:内置redux-thunk(直接处理异步请求)、内置状态校验、自动合并多个reducer,无需任何额外配置。
2. createSlice() - RTK的「核心核心」,重中之重
createSlice 是 RTK 最核心的API,90%的业务代码都靠它实现,直译是「创建切片」。
- 作用:一个方法封装了 原生Redux的 action + reducer 所有功能,无需手动定义actionType、actionCreator;
- 核心原理:基于
immer库实现,允许在reducer中「直接修改状态」(无需深拷贝、无需返回新状态),底层会自动转换成不可变数据,既简洁又不会出错; - 切片的含义:把全局状态按业务模块拆分(比如用户模块、购物车模块、商品模块),每个模块就是一个slice,最终把所有slice合并到store中,便于维护。
3. createAsyncThunk() - 处理异步请求
React 项目中,99%的场景都需要请求接口获取数据,这个API就是RTK专门用来处理异步action的(比如请求接口获取用户信息、商品列表)。
- 作用:封装异步逻辑(axios请求),自动分发「请求中/请求成功/请求失败」三种状态,让异步数据请求与状态管理完美结合;
- 特点:无需手动写dispatch,无需手动管理loading/error状态,与createSlice无缝联动。
四、完整基础使用流程
整体目录规范(React项目通用,必须遵守,方便维护)
所有Redux相关代码统一放在 src/store 目录下,按模块拆分,基础结构如下:
src
└── store/
├── modules/ # 所有业务模块的slice(核心)
│ ├── userSlice.js # 用户模块:登录、个人信息等
│ └── cartSlice.js # 购物车模块:添加、删除商品等
└── index.js # 创建全局store,合并所有slice
步骤1:创建全局 Store 仓库(src/store/index.js)
核心是调用 configureStore 方法,合并所有业务模块的reducer,整个项目只有1个store。
// 1. 导入RTK的核心方法
import { configureStore } from '@reduxjs/toolkit'
// 2. 导入各个业务模块的slice
import userReducer from './modules/userSlice'
import cartReducer from './modules/cartSlice'
// 3. 创建全局store并导出
const store = configureStore({
// 合并所有slice的reducer,key自定义,value是slice的reducer
reducer: {
user: userReducer,
cart: cartReducer
}
})
// 导出store,供根组件使用
export default store
步骤2:将 Store 注入整个React应用(入口文件 src/main.jsx / src/index.js)
通过 react-redux 提供的 Provider 根组件,将创建好的store注入到整个React应用中,这样所有子组件都能访问到Redux的全局状态。
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
// 1. 导入Provider组件
import { Provider } from 'react-redux'
// 2. 导入全局store
import store from './store'
ReactDOM.createRoot(document.getElementById('root')).render(
// 3. 用Provider包裹根组件,传入store属性
<Provider store={store}>
<App />
</Provider>
)
步骤3:创建业务模块的 Slice(核心!以用户模块为例 src/store/modules/userSlice.js)
这是最核心的步骤,分为「同步修改状态」和「异步请求数据」两个最常用场景,全部实现。
场景1:纯同步操作(比如修改用户名称、切换登录状态)
// 1. 导入RTK核心方法
import { createSlice } from '@reduxjs/toolkit'
// 2. 定义初始状态:该模块的所有状态都在这里初始化
const initialState = {
name: '张三',
age: 20,
isLogin: false, // 是否登录
token: '' // 登录令牌
}
// 3. 创建slice,核心配置3个参数
const userSlice = createSlice({
// ① name:模块名称,必须唯一,用于区分不同slice
name: 'user',
// ② initialState:初始状态
initialState,
// ③ reducers:同步修改状态的方法集合(核心)
// 这里的每一个函数,都是一个「同步action」,函数名就是action名称
reducers: {
// 修改用户名:直接修改状态,无需深拷贝!
changeName(state, action) {
state.name = action.payload // payload是传参,固定写法
},
// 用户登录:存入token,修改登录状态
login(state, action) {
state.token = action.payload.token
state.isLogin = true
},
// 用户退出:重置状态
logout(state) {
state.token = ''
state.isLogin = false
state.name = '游客'
}
}
})
// 4. 必须导出:同步action方法(供组件调用,触发状态修改)
export const { changeName, login, logout } = userSlice.actions
// 5. 必须导出:当前slice的reducer(供store合并)
export default userSlice.reducer
场景2:异步操作(比如请求接口登录、获取用户信息,必用 createAsyncThunk)
在上面的同步代码基础上,新增异步逻辑,无缝联动,完整代码如下(真实项目直接改接口地址即可):
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios' // 项目中请先安装:npm install axios
const initialState = {
name: '张三',
age: 20,
isLogin: false,
token: '',
// 新增:异步请求的状态,用于页面loading/错误提示
loading: false,
error: ''
}
// ✅ 核心:创建异步action,处理接口请求
// createAsyncThunk(标识名, 异步回调函数)
// 回调函数的参数:组件调用时传入的参数;返回值:接口请求的结果
export const fetchLogin = createAsyncThunk(
'user/fetchLogin', // 标识名:格式「模块名/方法名」,必须唯一
async (loginForm, { rejectWithValue }) => {
try {
// 真实接口请求:用户名+密码登录
const res = await axios.post('/api/login', loginForm)
return res.data // 返回成功数据,会传给下方的extraReducers
} catch (err) {
// 请求失败:返回错误信息
return rejectWithValue(err.response.data)
}
}
)
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
changeName(state, action) { state.name = action.payload },
logout(state) { state.token = ''; state.isLogin = false }
},
// ✅ 核心:处理异步action的「三种状态」,必须写在extraReducers里
// pending: 请求中 fulfilled: 请求成功 rejected: 请求失败
extraReducers: (builder) => {
builder
// 登录请求中
.addCase(fetchLogin.pending, (state) => {
state.loading = true
state.error = ''
})
// 登录请求成功
.addCase(fetchLogin.fulfilled, (state, action) => {
state.loading = false
state.token = action.payload.token // 接口返回的token
state.isLogin = true
state.name = action.payload.username // 接口返回的用户名
})
// 登录请求失败
.addCase(fetchLogin.rejected, (state, action) => {
state.loading = false
state.error = action.payload.msg // 接口返回的错误信息
})
}
})
export const { changeName, logout } = userSlice.actions
export default userSlice.reducer
步骤4:组件中使用Redux(核心2个hooks,无其他)
在任意React组件中,想要读取Redux状态、修改Redux状态,只需要用 react-redux 提供的 2个专属hooks,无任何其他API,无需高阶组件、无需connect,这是React18的推荐写法:
1. useSelector - 读取/订阅 Redux 全局状态
- 作用:从Redux的store中获取需要的状态数据,组件会自动订阅状态变化,状态更新时组件会重新渲染;
- 语法:
const 状态名 = useSelector( state => state.模块名.具体状态 ) - 说明:
state就是store中合并的所有状态,模块名是store/index.js中定义的key(比如user、cart)。
2. useDispatch - 触发action,修改 Redux 状态
- 作用:获取Redux的dispatch方法,通过dispatch调用action方法,从而触发reducer执行,最终修改状态;
- 语法:
const dispatch = useDispatch()→dispatch( action方法(传参) ) - 说明:无论是同步action(slice的reducers里的方法)还是异步action(createAsyncThunk创建的方法),调用方式完全一致!
组件完整使用示例(同步+异步)
import { useSelector, useDispatch } from 'react-redux'
// 导入需要的同步/异步action
import { changeName, logout, fetchLogin } from './store/modules/userSlice'
function UserInfo() {
// 1. 读取Redux状态:解构赋值,按需获取
const { name, age, isLogin, loading, error } = useSelector(state => state.user)
// 2. 获取dispatch方法
const dispatch = useDispatch()
// 同步修改状态:调用同步action
const handleChangeName = () => {
dispatch(changeName('李四')) // payload就是传入的参数
}
// 异步请求:调用异步action(登录请求)
const handleLogin = async () => {
const loginForm = { username: 'admin', password: '123456' }
dispatch(fetchLogin(loginForm)) // 传参就是接口需要的参数
}
// 同步退出登录
const handleLogout = () => {
dispatch(logout())
}
return (
<div>
<h2>用户名:{name}</h2>
<h2>年龄:{age}</h2>
<h2>是否登录:{isLogin ? '已登录' : '未登录'}</h2>
{error && <p style={{color: 'red'}}>{error}</p>}
<button onClick={handleChangeName}>修改用户名</button>
<button onClick={handleLogin} disabled={loading}>{loading ? '登录中...' : '登录'}</button>
<button onClick={handleLogout} disabled={!isLogin}>退出登录</button>
</div>
)
}
export default UserInfo
五、核心补充知识点
1. 为什么 RTK 允许直接修改 state?
原生Redux中,必须遵守不可变数据原则:不能直接修改state,必须返回一个新的state(否则无法触发组件更新),比如 return { ...state, name: '李四' }。
而RTK中,createSlice 内置了 immer 库,它会在底层做一个「代理」:你写的 state.name = '李四' 看起来是直接修改,但immer会自动将这个「可变操作」转换成「不可变操作」,生成一个新的状态对象,既保证了Redux的核心原则,又简化了代码。
结论:在RTK的reducers/extraReducers中,放心直接修改state!
2. action.payload 是什么?(固定写法)
payload 是 RTK 中传递参数的固定属性名,没有其他名字。
- 当你调用
dispatch(changeName('李四'))时,'李四' 就是payload; - 在reducer中,通过
action.payload获取这个参数; - 如果需要传多个参数,直接传一个对象即可:
dispatch(changeName({name: '李四', age: 25})),然后在reducer中解构const {name, age} = action.payload。
3. 多模块状态如何拆分?
完全按照业务模块拆分即可,比如:
- 用户相关:userSlice.js(登录、个人信息、退出)
- 购物车相关:cartSlice.js(添加商品、删除商品、修改数量)
- 商品相关:goodsSlice.js(商品列表、商品详情) 每个slice独立维护自己的状态和方法,最后在store中合并,高内聚低耦合,大型项目也能轻松维护。
六、总结
- Redux Toolkit (RTK) 是 Redux 官方推荐的唯一写法,解决原生Redux样板代码多的问题;
- 核心依赖:
@reduxjs/toolkit+react-redux,无需其他依赖; - 核心API:
configureStore(创建store)、createSlice(核心,同步状态)、createAsyncThunk(异步请求); - 组件核心hooks:
useSelector(读状态)、useDispatch(改状态); - 核心特性:内置redux-thunk、内置immer,允许直接修改state,异步同步调用方式一致;
- 目录规范:所有Redux代码放
src/store,按模块拆分slice,便于维护。