09. Redux Toolkit 与 React-Redux

一、5W1H 概述

维度 内容
What 使用 Redux Toolkit 简化配置,React-Redux 连接 React
Why 减少样板代码,提供更好的开发体验
When 使用 Redux 的项目
Where store 配置和组件中
Who 选择 Redux 的开发者
How configureStorecreateSliceuseSelectoruseDispatch

二、What - 什么是 Redux Toolkit?

Redux Toolkit 是 Redux 官方推荐的工具集,简化了 Redux 的使用。

安装

npm install @reduxjs/toolkit react-redux

为什么用 Redux Toolkit?

特性 传统 Redux Redux Toolkit
配置 复杂 简单
样板代码
不可变更新 手动 自动(Immer)
异步支持 需要配置 内置

三、Why - 为什么需要 Redux Toolkit?

3.1 减少样板代码

传统 Redux 需要大量样板代码,Redux Toolkit 大幅简化。

3.2 内置最佳实践

自动配置 Redux DevTools、Thunk 中间件。

3.3 Immer 集成

可以直接编写"可变"代码,Immer 自动处理不可变性。


四、When - 何时使用?

场景 推荐程度 说明
新 Redux 项目 ✅ 强烈推荐 官方推荐
现有 Redux 项目迁移 ✅ 推荐 逐步迁移
大型复杂应用 ✅ 推荐 更好的开发体验
简单状态管理 ❌ 不推荐 使用 Zustand

五、Where - 在哪里使用?

src/
├── store/
│   ├── index.js          # Store 配置
│   ├── slices/
│   │   ├── counterSlice.js
│   │   ├── userSlice.js
│   │   └── todoSlice.js
│   └── hooks.js          # 自定义 hooks
└── components/
    └── Component.jsx     # 使用 Redux

六、Who - 谁需要使用?

使用 Redux 进行状态管理的开发者。


七、How - 如何使用?

7.1 创建 Slice

// store/slices/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 },
  reducers: {
    increment: (state) => {
      state.value += 1;  // 可以直接修改!Immer 处理
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
    reset: (state) => {
      state.value = 0;
    }
  }
});

// 导出 actions
export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions;

// 导出 reducer
export default counterSlice.reducer;

7.2 配置 Store

// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slices/counterSlice';
import userReducer from './slices/userSlice';
import todoReducer from './slices/todoSlice';

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

// 导出 RootState 和 AppDispatch 类型(TypeScript)
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

7.3 Provider 配置

// App.jsx
import { Provider } from 'react-redux';
import { store } from './store';
import Counter from './components/Counter';

function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

7.4 组件中使用

// components/Counter.jsx
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset } from '../store/slices/counterSlice';

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

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(reset())}>重置</button>
    </div>
  );
}

7.5 自定义 Hooks(TypeScript)

// store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './index';

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// 组件中使用
import { useAppDispatch, useAppSelector } from '../store/hooks';

function Counter() {
  const count = useAppSelector((state) => state.counter.value);
  const dispatch = useAppDispatch();
  // ...
}

7.6 异步操作(createAsyncThunk)

// store/slices/userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 异步 thunk
export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId, { rejectWithValue }) => {
    try {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) {
        throw new Error('Failed to fetch user');
      }
      const data = await response.json();
      return data;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

export const updateUser = createAsyncThunk(
  'user/updateUser',
  async ({ id, data }) => {
    const response = await fetch(`/api/users/${id}`, {
      method: 'PUT',
      body: JSON.stringify(data)
    });
    return response.json();
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: {
    data: null,
    loading: false,
    error: null
  },
  reducers: {
    clearUser: (state) => {
      state.data = null;
      state.error = null;
    }
  },
  extraReducers: (builder) => {
    builder
      // fetchUser
      .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;
      })
      // updateUser
      .addCase(updateUser.fulfilled, (state, action) => {
        state.data = action.payload;
      });
  }
});

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

7.7 完整 Todo 示例

// store/slices/todoSlice.js
import { createSlice } from '@reduxjs/toolkit';

const todoSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
    deleteTodo: (state, action) => {
      return state.filter(todo => todo.id !== action.payload);
    },
    editTodo: (state, action) => {
      const todo = state.find(t => t.id === action.payload.id);
      if (todo) {
        todo.text = action.payload.text;
      }
    },
    clearCompleted: (state) => {
      return state.filter(todo => !todo.completed);
    }
  }
});

export const { addTodo, toggleTodo, deleteTodo, editTodo, clearCompleted } = todoSlice.actions;
export default todoSlice.reducer;
// components/TodoApp.jsx
import { useSelector, useDispatch } from 'react-redux';
import { addTodo, toggleTodo, deleteTodo, clearCompleted } from '../store/slices/todoSlice';

function TodoApp() {
  const [text, setText] = useState('');
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim()) {
      dispatch(addTodo(text));
      setText('');
    }
  };

  const activeCount = todos.filter(t => !t.completed).length;

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={text} onChange={(e) => setText(e.target.value)} />
        <button type="submit">添加</button>
      </form>
      
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => dispatch(toggleTodo(todo.id))}
            />
            <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
              {todo.text}
            </span>
            <button onClick={() => dispatch(deleteTodo(todo.id))}>删除</button>
          </li>
        ))}
      </ul>
      
      <div>
        <p>剩余 {activeCount} 项</p>
        {activeCount !== todos.length && (
          <button onClick={() => dispatch(clearCompleted())}>清除已完成</button>
        )}
      </div>
    </div>
  );
}

7.8 选择器优化

// store/selectors.js
// 创建可复用的选择器
export const selectCount = (state) => state.counter.value;
export const selectUser = (state) => state.user.data;
export const selectTodos = (state) => state.todos;
export const selectActiveTodos = (state) => state.todos.filter(t => !t.completed);
export const selectCompletedTodos = (state) => state.todos.filter(t => t.completed);
export const selectTodoById = (id) => (state) => state.todos.find(t => t.id === id);

// 组件中使用
import { selectActiveTodos } from '../store/selectors';

function ActiveTodoList() {
  const activeTodos = useSelector(selectActiveTodos);
  // ...
}

八、传统 Redux vs Redux Toolkit 对比

对比项 传统 Redux Redux Toolkit
Store 创建 createStore configureStore
Reducer 手动编写 createSlice
不可变更新 手动展开 自动(Immer)
Action Types 常量定义 自动生成
Action Creators 手动编写 自动生成
异步支持 需要配置中间件 内置 Thunk
代码量 少 70%

九、练习题

  1. 使用 Redux Toolkit 创建计数器应用
  2. 使用 Redux Toolkit 创建 Todo 应用
  3. 使用 createAsyncThunk 实现用户数据获取

十、小结

要点 说明
createSlice 同时创建 reducer 和 actions
configureStore 自动配置中间件和 DevTools
createAsyncThunk 简化异步操作
useSelector 读取状态
useDispatch 触发 action

更多推荐