依赖安装准备
RTK作为简化redux使用的工具包,在React中使用时二者都必不可少
cmd
npm install react-redux @reduxjs/toolkit -s
使用
在开始进入具体的业务环境之前,我们先要对RTK整体使用步骤有一个大概的认识。由于RTK作为简化工具,所以使用RTK的过程中避免了原本使用redux时产生的大量样板代码,这里对其状态管理原理不做深入,旨在快速学习上手
- 创建Slice(createSlice):定义状态和对应的修改逻辑
- 配置Store(configureStore):组合所有的状态模块
- 连接React(Provider):Provider包裹顶层组件,注入store
- 组件交互(useSelector、useDispatch):通过hooks获取状态和派发action
- 处理异步(createAsyncThunk):通过thunk管理副作用
创建Slice
Slice是RTK的核心概念,RTK中的createSlice会自动生成action creators 和 reducer,简化了redux中需要手动编写action类型和action创建函数的过程
这里选择用一个简单的全局登录状态管理作为本文的示例:
先明确一下需求:
需要存储的只有一个登录状态(true/false)和一个token,项目运行时会有路由守卫机制检查存储中的token状态,如果没有token或者token已过期,则重定向到登录页面进行登录,用户登录后将token存在localStorae中且登录状态为true,用户登出后销毁token并将登录状态更新为false
authSlice.js
js
import {createSlice} from '@reduxjs/toolkit'
const authSlice = createSlice({
name:'auth'//对切片进行命名
initialState:() => { //赋初始状态值
const token = localStorage.getItem('token')
if(!token) {
return {
isLogged:false,
token:null
}
} else {
return {
isLogged:true,
token,
}
}
},//这里由于特殊的需求需要进行判断返回不同的状态初值,也可以在initialState这里不返回函数而直接赋值
reducers: {
login(state, action) {
state.isLogged = true
state.token = action.payload.token
//同时将token存到本地
localStorage.setItem('token', state.token)
},
logout(state, action) {
state.isLogged = false
state.token = null
//删除本地token
localStorage.removeItem('token', state.token)
},
},
})
export const {login, logout} = authSlice.actions //自动生成 action creators
export default authSlice.reducer // 导出 reducer,每个reducer对应一个同步 action
配置Store
store可以想象成一个仓库的入口,在这里对各个切片进行总的管理和集成,在与React连接时也是将store进行注入
store.js
js
import { configureStore } from "@reduxjs/toolkit";
import authReducer from "./authSlice.js";
export const store = configureStore({
//这里作为store中的一个命名空间用于区分不同的 reducer
reducer:{
auth:authReducer, //将 authReducer 命名为 auth,在其他组件中调用时统一使用 auth 作为状态路径
},
})
注意:configureStore 会默认集成Redux DevTools 扩展和默认包含redux-thunk 中间件(处理异步)
连接 React
在React挂载根组件处,使用 Provider 包裹并注入 store,使 store 全局可用
index.js
js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import {store} from '../src/store/index'
import { Provider } from 'react-redux';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
组件交互
当我们需要再具体组件中对状态进行更新时,还需要用到其他两个钩子: useSelector() 和 useDispatch()
如果对照官方解释,可能一时半会难以理解,但实际使用时,这两个钩子的作用简单来说就是 一取一改
- useSelector():用来取出store中具体切片所存储的状态值
- useDispatch():用来对具体的状态值进行更新,也就是修改
分别以路由守卫机制和登录界面逻辑做示例:
needAuth.js
js
import React from 'react'
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
const NeedAuth =(props) => {
//利用 useSelector 取出用户当前的登录状态
const res = useSelector( state => state.auth.isLogged);
//判断此时是否应该重定向到登录页面
return res.isLogged ? props.children : <Navigate to={'/'} replace/>
}
export default NeedAuth;
//在路由配置中引入 NeedAuth 包裹主页面即可实现路由守卫
login.js
js
import { useDispatch } from "react-redux";
import { login } from "./authSlice.js";
const Login = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
//登录表单提交,这里使用了antd的表单组件函数
const onFinish = async (values) => {
const res = await loginFetch(values) //此处默认使用封装后的登录API
if(res.data.isSuccess) {
dispatch(
login({
token:res.data.token
})
)
}
//跳转主页面
navigate('/', {replace:true})
}
}
处理异步
在Redux中,处理异步操作通常需要使用中间件,比如redux-thunk。RTK默认集成了这些中间件,而createAsyncThunk的作用就是生成处理异步生命周期的action,简化流程。
createAsyncThunk
的工作原理:
它接收一个action类型的前缀和一个返回Promise的payload创建函数。然后,它会自动生成pending、fulfilled和rejected三种action类型,我们无需手动编写这些action creators。这样减少了样板代码,提高了代码的可维护性。
使用场景:
进行API调用、异步数据获取或提交表单数据,需要处理loading、成功和错误状态的场景下。如果是简单的同步操作,则不适合使用
场景 | 适用性 | 示例 |
---|---|---|
API 请求 | ✅ 最佳实践 | 获取用户数据 |
表单提交 | ✅ 推荐 | 登录/注册 |
简单状态更新 | ⚠️ 过度设计 | 切换按钮状态 |
复杂数据转换 | ✅ 适用 | CSV 文件解析上传 |
需要取消的操作 | ⚠️ 需额外配置 | 搜索建议请求 |
下面依旧使用刚才的登录状态管理作为示例,使用createAsyncThunk进行优化: authSlice.js
js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
// 步骤1:创建异步登录操作
export const loginUser = createAsyncThunk(
'auth/login', // action类型前缀(自动生成三种状态:pending/fulfilled/rejected)
async (values, { rejectWithValue }) => { // values 包含用户名和密码
try {
const response = await loginFetch(values);
return response.token; // 返回的值会成为 fulfilled action 的 payload
} catch (error) {
return rejectWithValue(error.message); // 错误信息会传递到 rejected action
}
}
);
//创建切片
const authSlice = createSlice({
name: 'auth',
initialState: () => {
const token = localStorage.getItem('token');
return {
isLogged: !!token, // 简化的布尔值转换
token: token || null,
loading: false, // 新增加载状态
error: null // 新增错误信息
};
},
reducers: {
// 步骤2:保持登出操作为同步
logout(state) {
state.isLogged = false;
state.token = null;
localStorage.removeItem('token');
state.error = null; // 清除可能的错误信息
}
},
// 步骤3:处理异步操作的生命周期
extraReducers: (builder) => {
builder
.addCase(loginUser.pending, (state) => {
state.loading = true;
state.error = null; // 开始新请求时清除旧错误
})
.addCase(loginUser.fulfilled, (state, action) => {
state.loading = false;
state.isLogged = true;
state.token = action.payload;
localStorage.setItem('token', action.payload); // 存储token
})
.addCase(loginUser.rejected, (state, action) => {
state.loading = false;
state.error = action.payload; // 显示错误信息
});
}
});
// 导出同步action和reducer
export const { logout } = authSlice.actions;
export default authSlice.reducer;
// 步骤4:创建选择器(可选但推荐)
export const selectIsLoggedIn = state => state.auth.isLogged;
export const selectAuthToken = state => state.auth.token;
export const selectAuthLoading = state => state.auth.loading;
export const selectAuthError = state => state.auth.error;
相比于之前的authSlice版本,这里将登录操作拆分为三个明确阶段:
pending
: 开始登录(显示加载状态)fulfilled
: 登录成功(更新状态)rejected
: 登录失败(显示错误)
使用流程对比:
场景 | 原版 | 优化版 |
---|---|---|
发起登录 | dispatch(login({ token })) |
dispatch(loginUser(values)) |
处理加载 | 手动管理 | 自动更新 loading 状态 |
错误处理 | 需要自行捕获 | 自动捕获并存储错误 |
本地存储 | 在reducer中处理 | 在fulfilled阶段处理 |
组件中使用: login.js
js
import { useDispatch, useSelector } from 'react-redux';
import { loginUser, selectAuthLoading, selectAuthError } from './authSlice';
function LoginForm() {
const dispatch = useDispatch();
const loading = useSelector(selectAuthLoading);
const error = useSelector(selectAuthError);
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
dispatch(loginUser({
username: formData.get('username'),
password: formData.get('password')
}));
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<input name="username" disabled={loading} />
<input name="password" type="password" disabled={loading} />
<button type="submit" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
);
}
供学习参考,有错误敬请指出