Redux Toolkit #

一、Redux Toolkit概述 #

1.1 什么是Redux Toolkit #

Redux Toolkit(RTK)是 Redux 官方推荐的编写 Redux 逻辑的方式,简化了 Redux 的使用。

1.2 为什么使用RTK #

问题 RTK解决方案
配置复杂 简单的configureStore
样板代码多 createSlice自动生成
异步处理麻烦 内置createAsyncThunk
不可变更新 内置Immer库

1.3 安装 #

bash
npm install @reduxjs/toolkit react-redux

二、configureStore #

2.1 基本配置 #

javascript
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from './features/todosSlice';
import usersReducer from './features/usersSlice';

export const store = configureStore({
  reducer: {
    todos: todosReducer,
    users: usersReducer
  }
});

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

2.2 配置选项 #

javascript
export const store = configureStore({
  reducer: rootReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .concat(logger)
      .concat(customMiddleware),
  devTools: process.env.NODE_ENV !== 'production',
  preloadedState: {
    todos: [{ id: 1, text: 'Initial todo' }]
  }
});

三、createSlice #

3.1 基本用法 #

javascript
import { createSlice } from '@reduxjs/toolkit';

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      // 可以直接修改state(Immer处理)
      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;
      }
    },
    removeTodo: (state, action) => {
      return state.filter(t => t.id !== action.payload);
    }
  }
});

// 导出actions
export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;

// 导出reducer
export default todosSlice.reducer;

3.2 准备Action Payload #

javascript
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: {
      reducer: (state, action) => {
        state.push(action.payload);
      },
      prepare: (text) => {
        return {
          payload: {
            id: Date.now(),
            text,
            completed: false,
            createdAt: new Date().toISOString()
          }
        };
      }
    }
  }
});

3.3 复杂状态 #

javascript
const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    filter: 'all',
    loading: false,
    error: null
  },
  reducers: {
    setFilter: (state, action) => {
      state.filter = action.payload;
    },
    clearCompleted: (state) => {
      state.items = state.items.filter(t => !t.completed);
    }
  }
});

四、createAsyncThunk #

4.1 基本用法 #

javascript
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 创建异步thunk
export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/todos');
      if (!response.ok) {
        throw new Error('Failed to fetch');
      }
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

export const addTodoAsync = createAsyncThunk(
  'todos/addTodoAsync',
  async (text, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/todos', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text })
      });
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    loading: false,
    error: null
  },
  reducers: {},
  extraReducers: (builder) => {
    builder
      // fetchTodos
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      })
      // addTodoAsync
      .addCase(addTodoAsync.fulfilled, (state, action) => {
        state.items.push(action.payload);
      });
  }
});

4.2 带参数的异步请求 #

javascript
export const fetchUserById = createAsyncThunk(
  'users/fetchById',
  async (userId, { getState, rejectWithValue }) => {
    const { auth } = getState();
    
    try {
      const response = await fetch(`/api/users/${userId}`, {
        headers: {
          Authorization: `Bearer ${auth.token}`
        }
      });
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

五、在组件中使用 #

5.1 配置Provider #

javascript
import { Provider } from 'react-redux';
import { store } from './store';

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

5.2 使用Hooks #

javascript
import { useSelector, useDispatch } from 'react-redux';
import {
  addTodo,
  toggleTodo,
  removeTodo,
  fetchTodos,
  selectFilteredTodos
} from './features/todosSlice';

function TodoApp() {
  const dispatch = useDispatch();
  const todos = useSelector(selectFilteredTodos);
  const { loading, error } = useSelector(state => state.todos);
  const [input, setInput] = useState('');

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

  const handleAdd = () => {
    if (input.trim()) {
      dispatch(addTodo(input));
      setInput('');
    }
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyPress={(e) => e.key === 'Enter' && handleAdd()}
      />
      <button onClick={handleAdd}>添加</button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span
              onClick={() => dispatch(toggleTodo(todo.id))}
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
            >
              {todo.text}
            </span>
            <button onClick={() => dispatch(removeTodo(todo.id))}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

5.3 自定义Hooks #

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

// 类型化的hooks
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// 使用
function Component() {
  const dispatch = useAppDispatch();
  const todos = useAppSelector(state => state.todos);
}

六、最佳实践 #

6.1 文件结构 #

text
src/
├── store/
│   └── index.js
├── features/
│   ├── todos/
│   │   ├── todosSlice.js
│   │   └── TodosList.jsx
│   └── users/
│       ├── usersSlice.js
│       └── UsersList.jsx
└── hooks/
    └── redux.js

6.2 选择器模式 #

javascript
// todosSlice.js
export const selectAllTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;

export const selectFilteredTodos = createSelector(
  [selectAllTodos, selectFilter],
  (todos, filter) => {
    switch (filter) {
      case 'active':
        return todos.filter(t => !t.completed);
      case 'completed':
        return todos.filter(t => t.completed);
      default:
        return todos;
    }
  }
);

export const selectTodoById = (state, id) =>
  state.todos.items.find(t => t.id === id);

6.3 持久化 #

javascript
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['todos']
};

const rootReducer = combineReducers({
  todos: todosReducer,
  users: usersReducer
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE']
      }
    })
});

export const persistor = persistStore(store);

七、完整示例 #

javascript
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';

export const store = configureStore({
  reducer: {
    todos: todosReducer
  }
});

// features/todos/todosSlice.js
import { createSlice, createAsyncThunk, createSelector } from '@reduxjs/toolkit';

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('/api/todos');
    return response.json();
  }
);

const todosSlice = createSlice({
  name: 'todos',
  initialState: {
    items: [],
    filter: 'all',
    loading: false,
    error: null
  },
  reducers: {
    addTodo: (state, action) => {
      state.items.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.items.find(t => t.id === action.payload);
      if (todo) todo.completed = !todo.completed;
    },
    removeTodo: (state, action) => {
      state.items = state.items.filter(t => t.id !== action.payload);
    },
    setFilter: (state, action) => {
      state.filter = action.payload;
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      });
  }
});

export const { addTodo, toggleTodo, removeTodo, setFilter } = todosSlice.actions;

export const selectTodos = (state) => state.todos.items;
export const selectFilter = (state) => state.todos.filter;

export const selectFilteredTodos = createSelector(
  [selectTodos, selectFilter],
  (todos, filter) => {
    if (filter === 'active') return todos.filter(t => !t.completed);
    if (filter === 'completed') return todos.filter(t => t.completed);
    return todos;
  }
);

export default todosSlice.reducer;

八、总结 #

要点 说明
configureStore 简化store配置
createSlice 自动生成actions和reducer
createAsyncThunk 处理异步操作
Immer 自动处理不可变更新

核心原则:

  • 使用 RTK 简化 Redux 开发
  • 直接修改 state(Immer 处理)
  • 使用 createAsyncThunk 处理异步
  • 使用选择器封装状态访问
最后更新:2026-03-26