Skip to Content
🎉 Nextra 4.0 is released. dimaMachina is looking for a new job or consulting.
指引Redux 与 Redux Toolkit

在本项目中,我们使用了 Redux Toolkit(以下简称 RTK)来进行应用状态管理。相比于传统的 Redux 配置方式,RTK 提供了更简洁的 API 和更合理的默认配置,例如内置 immerredux-thunkdevTools 等,让我们可以专注于业务逻辑而非繁琐的样板代码。

一、Redux Toolkit 的优点

  1. 简化配置

    • 不再需要手动安装和配置 redux-thunkredux-devtools-extension 等常用中间件,RTK 内部已经默认集成。
    • 不必手动编写冗长的 combineReducerscreateStore 等函数,大大减少样板代码。
  2. 内置可变数据的支持

    • RTK 默认使用 immer 来自动管理不可变数据,直接在 reducer 中对 state 进行“可变”操作,实际上底层会帮你确保数据不可变。
  3. 统一的逻辑组织

    • RTK 官方推荐使用 Slices 来按逻辑模块或业务领域拆分状态。每个 slice 包含 reduceractionselector 等,结构清晰。
    • 大大减少了样板式的 actionTypes.jsactions.jsreducers.js 等文件,利于维护和拓展。
  4. 更好的开发者体验

    • 内置的 createAsyncThunkcreateSlice 等方法让异步逻辑处理、action 和 reducer 的创建更加流畅。
    • 支持与 TypeScript 深度结合,可自动推导函数、状态及 dispatch 的类型。

二、项目中的 Redux 结构

1. 根 store 与切片

在项目中,我们通过以下方式来配置 store,并将多个 slice(如 authSlice, themeSlice 等)合并到一起:

// src/store/index.ts import { combineSlices, configureStore } from '@reduxjs/toolkit'; import type { Action, ThunkAction } from '@reduxjs/toolkit'; import { authSlice } from '../features/auth/authStore'; import { routeSlice } from '../features/router/routeStore'; import { tabSlice } from '../features/tab/tabStore'; import { themeSlice } from '../features/theme'; import { appSlice } from '../layouts/appStore'; // 使用 combineSlices 自动组合所有 slice const rootReducer = combineSlices(appSlice, authSlice, themeSlice, routeSlice, tabSlice); export type RootState = ReturnType<typeof rootReducer>; // 推导出根 state 类型 export const store = configureStore({ reducer: rootReducer }); export type AppStore = typeof store; export type AppDispatch = AppStore['dispatch']; // thunk action 类型 export type AppThunk<ReturnType = void> = ThunkAction<ReturnType, RootState, unknown, Action>;
  • combineSlices: 自定义辅助函数,用于简化 combineReducers 的操作;它能够自动识别每个 slice 的 reducerPath 并进行合并。
  • RootState: 通过 ReturnType<typeof rootReducer> 自动推导整个应用的根状态类型,给 useSelector 等带来类型推导。
  • AppDispatch: 通过 store.dispatch 类型推导得到,给 useDispatch 等带来类型提示。

2. 定义与导出自定义 Hooks

项目中通过以下 hook 访问 Redux store,更好地与 TypeScript 结合:

// src/hooks/useStore.ts (或其他命名) import { useDispatch, useSelector } from 'react-redux'; import type { AppDispatch, RootState } from '@/store'; export const useAppDispatch = useDispatch.withTypes<AppDispatch>(); export const useAppSelector = useSelector.withTypes<RootState>();
  • useAppDispatch:封装了原生的 useDispatch,并带上 AppDispatch 类型。
  • useAppSelector:封装了原生的 useSelector,并带上 RootState 类型。

使用示例:

const dispatch = useAppDispatch(); const someValue = useAppSelector(state => state.someSlice.someValue);

3. Slice 示例

// src/features/router/routeStore.ts import { createAppSlice } from '../../store/createAppSlice'; import type { PayloadAction } from '@reduxjs/toolkit'; interface InitialStateType { cacheRoutes: string[]; removeCacheKey: string | null; routeHomePath: string; } const initialState: InitialStateType = { cacheRoutes: [], removeCacheKey: null, routeHomePath: import.meta.env.VITE_ROUTE_HOME }; export const routeSlice = createAppSlice({ initialState, name: 'route', reducers: create => ({ addCacheRoutes: create.reducer((state, { payload }: PayloadAction<string>) => { state.cacheRoutes.push(payload); }), resetRouteStore: create.reducer(() => initialState), setCacheRoutes: create.reducer((state, { payload }: PayloadAction<string[]>) => { state.cacheRoutes = payload; }), setHomePath: create.reducer((state, { payload }: PayloadAction<string>) => { state.routeHomePath = payload; }), setRemoveCacheKey: create.reducer((state, { payload }: PayloadAction<string | null>) => { state.removeCacheKey = payload; }) }), // 这里也可以直接定义选择器,统一导出 selectors: { selectCacheRoutes: route => route.cacheRoutes, selectRemoveCacheKey: route => route.removeCacheKey, selectRouteHomePath: route => route.routeHomePath } }); // actions export const { addCacheRoutes, resetRouteStore, setCacheRoutes, setHomePath, setRemoveCacheKey } = routeSlice.actions; // selectors export const { selectCacheRoutes, selectRemoveCacheKey, selectRouteHomePath } = routeSlice.selectors;
  • createAppSlice:一个对 createSlice 的封装,简化了 reducers、action、selector 的定义,减少重复代码。
  • 采用 selectors 字段统一定义选择器(Selectors),再在最后一起导出,使用 useAppSelector 时可以直接调用。

三、如何访问和修改 Redux 状态

在任意组件中,通过自定义 Hook来读取或更新状态:

import React, { useMemo } from 'react'; import { useAppDispatch, useAppSelector } from '@/hooks/useStore'; import { selectActiveFirstLevelMenuKey, setActiveFirstLevelMenuKey } from '@/features/menuStore'; // 伪示例 function ExampleMenu() { const dispatch = useAppDispatch(); const activeFirstLevelMenuKey = useAppSelector(selectActiveFirstLevelMenuKey); // 读取或计算派生数据 const menus = useMemo(() => { // ...根据路由或其他数据生成菜单 return []; }, []); // 调用 actions 来更新 redux state const handleChangeMenuKey = (key?: string) => { dispatch(setActiveFirstLevelMenuKey(key || '')); }; return ( <div> <p>当前激活菜单: {activeFirstLevelMenuKey}</p> {/* 渲染菜单 */} {/* 点击菜单项时调用 handleChangeMenuKey 来更新状态 */} </div> ); } export default ExampleMenu;
  • 读取useAppSelector(selectActiveFirstLevelMenuKey)
  • 更新dispatch(setActiveFirstLevelMenuKey(newKey))

四、总结

  1. Redux Toolkit 提供了更简洁、功能齐全的方式来管理全局状态,相比传统 Redux 省去了大量配置和模板代码。
  2. Slices
    • 通过 createSlice / createAppSlice 分别管理各领域状态(如 auth, theme, route, tab 等)。
    • 将 reducers、actions、selectors 集中到单一文件,降低维护成本。
  3. 自定义 Hook
    • useAppDispatchuseAppSelector 搭配 TypeScript,可以获得优秀的类型推断及编译期检查。
  4. 状态读写示例
    • useAppSelector 获取全局状态中的所需部分;
    • dispatch(actionCreator()) 触发 reducer 更新状态。

通过这种模式,项目可保持状态管理清晰可控,同时借助 TypeScript 的能力有效避免常见的动态类型错误,大大提升可维护性和开发效率。希望你在本项目中能顺利使用 Redux Toolkit 的各项特性,写出高质量的状态管理逻辑。

Last updated on