在本项目中,我们使用了 Redux Toolkit (以下简称 RTK)来进行应用状态管理。相比于传统的 Redux 配置方式,RTK 提供了更简洁的 API 和更合理的默认配置,例如内置 immer
、redux-thunk
、devTools
等,让我们可以专注于业务逻辑而非繁琐的样板代码。
一、Redux Toolkit 的优点
-
简化配置
- 不再需要手动安装和配置
redux-thunk
、redux-devtools-extension
等常用中间件,RTK 内部已经默认集成。 - 不必手动编写冗长的
combineReducers
、createStore
等函数,大大减少样板代码。
- 不再需要手动安装和配置
-
内置可变数据的支持
- RTK 默认使用 immer 来自动管理不可变数据,直接在 reducer 中对
state
进行“可变”操作,实际上底层会帮你确保数据不可变。
- RTK 默认使用 immer 来自动管理不可变数据,直接在 reducer 中对
-
统一的逻辑组织
- RTK 官方推荐使用 Slices 来按逻辑模块或业务领域拆分状态。每个 slice 包含
reducer
、action
、selector
等,结构清晰。 - 大大减少了样板式的
actionTypes.js
、actions.js
、reducers.js
等文件,利于维护和拓展。
- RTK 官方推荐使用 Slices 来按逻辑模块或业务领域拆分状态。每个 slice 包含
-
更好的开发者体验
- 内置的
createAsyncThunk
、createSlice
等方法让异步逻辑处理、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))
。
四、总结
- Redux Toolkit 提供了更简洁、功能齐全的方式来管理全局状态,相比传统 Redux 省去了大量配置和模板代码。
- Slices:
- 通过
createSlice
/createAppSlice
分别管理各领域状态(如auth
,theme
,route
,tab
等)。 - 将 reducers、actions、selectors 集中到单一文件,降低维护成本。
- 通过
- 自定义 Hook:
useAppDispatch
和useAppSelector
搭配 TypeScript,可以获得优秀的类型推断及编译期检查。
- 状态读写示例:
useAppSelector
获取全局状态中的所需部分;dispatch(actionCreator())
触发 reducer 更新状态。
通过这种模式,项目可保持状态管理清晰可控,同时借助 TypeScript 的能力有效避免常见的动态类型错误,大大提升可维护性和开发效率。希望你在本项目中能顺利使用 Redux Toolkit 的各项特性,写出高质量的状态管理逻辑。
Last updated on