Redux Toolkit 实战

引言

Redux 曾是 React 状态管理的标准解决方案,但传统 Redux 的样板代码过多、配置复杂、异步处理繁琐。Redux Toolkit(RTK)官方推出,旨在解决这些痛点,成为现代 Redux 开发的标准方式。

一、为什么选择 Redux Toolkit?

传统 Redux 的三大痛点

  1. 配置复杂:需要手动配置 store、中间件、devtools
  2. 样板代码多:action types、action creators、reducers 分离
  3. 异步处理 麻烦:需要额外安装 redux-thunk 或 redux-saga

RTK 的优势

特性 传统 Redux Redux Toolkit
Store 配置 多行手动配置 configureStore 一行搞定
Action 定义 手动定义 type + creator createSlice 自动生成
不可变更新 需要 spread 运算符 Immer 内置,可直接修改
DevTools 手动配置 自动集成
TypeScript 繁琐的类型定义 内置类型支持

二、四步实战 Redux Toolkit

步骤 1:安装与 Store 配置

bash 复制代码
npm install @reduxjs/toolkit react-redux
javascript 复制代码
// src/app/store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';
import userReducer from '../features/user/userSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    user: userReducer,
  },
});

export default store;

configureStore 自动帮你:

  • 配置 Redux DevTools
  • 设置 redux-thunk 中间件
  • 启用 Immer 支持不可变更新

步骤 2:创建 Slice

javascript 复制代码
// src/features/counter/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    value: 0,
    history: [],
  },
  reducers: {
    increment: (state) => {
      // Immer 允许直接修改 state
      state.value += 1;
      state.history.push(`+1 → ${state.value}`);
    },
    decrement: (state) => {
      state.value -= 1;
      state.history.push(`-1 → ${state.value}`);
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
      state.history.push(`+${action.payload} → ${state.value}`);
    },
    reset: (state) => {
      state.value = 0;
      state.history = [];
    },
  },
});

export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;
export default counterSlice.reducer;

createSlice 自动生成:

  • Action types(counter/increment
  • Action creators(increment()
  • Reducer 函数

步骤 3:处理异步请求

javascript 复制代码
// src/features/user/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

// 创建异步 thunk
export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, { rejectWithValue }) => {
    try {
      const response = await axios.get(`/api/users/${userId}`);
      return response.data;
    } catch (error) {
      return rejectWithValue(error.response.data);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null,
  },
  reducers: {
    clearUser: (state) => {
      state.data = null;
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  },
});

export const { clearUser } = userSlice.actions;
export default userSlice.reducer;

createAsyncThunk 自动生成三种状态:

  • pending:请求中
  • fulfilled:请求成功
  • rejected:请求失败

步骤 4:React 组件中使用

javascript 复制代码
// src/components/Counter.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, incrementByAmount, reset } from '../features/counter/counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const history = useSelector((state) => state.counter.history);
  const dispatch = useDispatch();

  return (
    <div className="counter">
      <h2>计数器:{count}</h2>
      <div className="buttons">
        <button onClick={() => dispatch(decrement())}>-</button>
        <button onClick={() => dispatch(increment())}>+</button>
        <button onClick={() => dispatch(incrementByAmount(5))}>+5</button>
        <button onClick={() => dispatch(reset())}>重置</button>
      </div>
      
      {history.length > 0 && (
        <div className="history">
          <h3>操作历史:</h3>
          <ul>
            {history.map((item, index) => (
              <li key={index}>{item}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

export default Counter;
javascript 复制代码
// src/components/UserProfile.jsx
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser, clearUser } from '../features/user/userSlice';

function UserProfile({ userId }) {
  const dispatch = useDispatch();
  const { data, loading, error } = useSelector((state) => state.user);

  useEffect(() => {
    dispatch(fetchUser(userId));
  }, [userId, dispatch]);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error}</div>;
  if (!data) return null;

  return (
    <div className="user-profile">
      <h2>{data.name}</h2>
      <p>邮箱:{data.email}</p>
      <button onClick={() => dispatch(clearUser())}>清除</button>
    </div>
  );
}

export default UserProfile;

三、项目结构组织

bash 复制代码
src/
├── app/
│   └── store.js          # Store 配置
├── features/
│   ├── counter/
│   │   ├── counterSlice.js
│   │   └── Counter.jsx
│   └── user/
│       ├── userSlice.js
│       └── UserProfile.jsx
├── components/            # 通用组件
└── index.js              # 入口文件
javascript 复制代码
// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  <Provider store={store}>
    <App />
  </Provider>
);

四、最佳实践

1. 使用 Typed Hooks(TypeScript 项目)

typescript 复制代码
// src/app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// 在整个应用中使用这些 hooks,而非默认的 useDispatch/useSelector
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

2. Slice 文件组织

  • 每个功能模块一个 slice
  • 相关的组件和 slice 放在同一目录
  • 使用 export 统一导出 actions 和 reducer

3. 避免的常见错误

错误做法 正确做法
直接修改 state 外部 只在 reducer 内修改
在 slice 中发异步请求 使用 createAsyncThunk
过多全局状态 局部状态用 useState
忽略 extraReducers 正确处理异步状态

总结

Redux Toolkit 让 Redux 开发变得简单高效:

  1. configureStore - 一键配置 store
  2. createSlice - 自动生成 action + reducer
  3. createAsyncThunk - 优雅处理异步
  4. Immer 内置 - 直接修改 state 无副作用

选型建议:

  • 小型应用 → Context 或 Zustand
  • 中大型应用 → Redux Toolkit
  • 需要时间旅行调试 → Redux Toolkit
相关推荐
snow_yan2 小时前
基于 json-render 的流式表单渲染方案
前端·react.js·llm
Csvn2 小时前
组件设计模式(下):HOC、Render Props 与 Compound Components
react.js
用户2814512549922 小时前
迷你React手写系列-React基本概念
react.js
im_AMBER3 小时前
万字长文:编辑器集成Vercel AI SDK
前端·人工智能·react.js·前端框架·编辑器
大雷神3 小时前
HarmonyOS APP<玩转React>开源教程二十:收藏功能实现
前端·react.js·开源·harmonyos
问道飞鱼4 小时前
【前端知识】React生态你了解多少?
前端·react.js·前端框架·生态
We་ct4 小时前
React Diff & Key 核心解析
开发语言·前端·javascript·react.js·前端框架·reactjs·diff
Highcharts.js17 小时前
React 图表如何实现下钻(Drilldown)效果
开发语言·前端·javascript·react.js·前端框架·数据可视化·highcharts
发现一只大呆瓜21 小时前
React-深度拆解 React路由:从实战进阶到底层原理
前端·react.js·面试