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
相关推荐
前端若水10 小时前
会话管理:创建、切换、删除对话历史
前端·人工智能·python·react.js
放下华子我只抽RuiKe511 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架
从文处安16 小时前
「前端何去何从」React Router:让单页应用有多页的体验
前端·react.js
whuhewei18 小时前
React diff算法为什么是DFS,不是BFS
算法·react.js·深度优先
从文处安18 小时前
「前端何去何从」混乱到有序的状态管理: Reducer 与 Context
前端·react.js
爱滑雪的码农20 小时前
React Native 完整开发全流程(从零到上线)
javascript·react native·react.js
Maimai1080821 小时前
React 多步骤表单工程化落地:从 Zod Schema、React Hook Form 到 Zustand 持久化
前端·javascript·react.js·前端框架·状态模式
Maimai1080821 小时前
React Query + Zustand 正确结合方式:不要把接口数据复制进 Store
前端·javascript·react.js·前端框架·web3·状态模式
Maimai1080821 小时前
Zustand 项目落地:从全局状态、Store 拆分到真实业务封装
前端·react.js·前端框架·状态模式
放下华子我只抽RuiKe521 小时前
React 从入门到生产(五):状态管理选型
前端·javascript·人工智能·深度学习·react.js·前端框架·ecmascript