RTK使用基本流程

依赖安装准备

RTK作为简化redux使用的工具包,在React中使用时二者都必不可少

cmd 复制代码
npm install react-redux @reduxjs/toolkit -s

使用

在开始进入具体的业务环境之前,我们先要对RTK整体使用步骤有一个大概的认识。由于RTK作为简化工具,所以使用RTK的过程中避免了原本使用redux时产生的大量样板代码,这里对其状态管理原理不做深入,旨在快速学习上手

  1. 创建Slice(createSlice):定义状态和对应的修改逻辑
  2. 配置Store(configureStore):组合所有的状态模块
  3. 连接React(Provider):Provider包裹顶层组件,注入store
  4. 组件交互(useSelector、useDispatch):通过hooks获取状态和派发action
  5. 处理异步(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>
  );
}

供学习参考,有错误敬请指出

相关推荐
卸任11 小时前
Vibe coding工具使用心得
前端·ai编程
brien912 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance12 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M12 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写12 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李12 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易12 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马13 小时前
前沿视界
前端
kyriewen13 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易13 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员