mcpField
R
redux-toolkit

redux-toolkit

github.com/claude-dev-suite开发4642 次安装4 天前更新
代码仓库

关于 redux-toolkit

面向 Redux 应用开发者的 Redux Toolkit 技能指南,介绍状态管理配置。内容涵盖 slice、store、异步 thunk、类型化 Hook、RTK Query,以及测试和错误处理建议。

主要功能

  1. 01创建 slice 并配置 store
  2. 02使用 thunk 处理异步操作
  3. 03使用类型化 React Redux Hook
  4. 04通过 RTK Query 获取数据
  5. 05实践状态管理建议
  6. 06排查常见 Redux 问题

适用场景

  1. 01设置类型安全的应用状态
  2. 02实现异步用户请求
  3. 03管理 API 数据与缓存失效
  4. 04诊断 Redux 状态和选择器问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/claude-dev-suite/claude-dev-suite --skill redux-toolkit

安装后在 Claude Code 中输入/redux-toolkit确认已加载。

附带文件quick-ref/rtk-query.mdquick-ref/slices.md

详细介绍

Redux Toolkit 核心知识

深入了解:使用 mcp__documentation__fetch_docs 并指定 technology:redux-toolkit,获取完整文档。

Slice

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

interface CounterState {
  value: number;
}

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0 } as CounterState,
  reducers: {
    increment: (state) => { state.value += 1; },
    decrement: (state) => { state.value -= 1; },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;

Store 设置

import { configureStore } from '@reduxjs/toolkit';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    users: usersReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

异步 Thunk

import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async (_, { rejectWithValue }) => {
    try {
      const response = await api.getUsers();
      return response.data;
    } catch (err) {
      return rejectWithValue(err.message);
    }
  }
);

// In slice
extraReducers: (builder) => {
  builder
    .addCase(fetchUsers.pending, (state) => { state.loading = true; })
    .addCase(fetchUsers.fulfilled, (state, action) => {
      state.loading = false;
      state.users = action.payload;
    })
    .addCase(fetchUsers.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload;
    });
}

Hooks

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

export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();

// Usage
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
dispatch(increment());

何时不应使用此技能

场景改用
简单的全局状态(用户偏好、主题)使用 zustand,减少样板代码
服务端状态管理仅使用 tanstack-query 或 RTK Query
Vue 3 应用pinia
小型项目或原型React Context + hooks 或 Zustand
组件本地状态React useState/useReducer

反模式

反模式不好的原因正确做法
使用原生 Redux,而不是 RTK样板代码过多,容易出错始终使用 Redux Toolkit
不借助 Immer 修改状态破坏不可变性,导致错误使用内置 Immer 的 createSlice
将所有内容都存入 Redux造成不必要的复杂性尽可能将组件状态保留在本地
不使用类型化 hooks丧失类型安全性导出类型化的 useAppDispatch/useAppSelector
在组件中使用 thunk 获取数据请求逻辑重复使用 RTK Query 获取数据
不使用 createAsyncThunk需要手动处理加载和错误使用 createAsyncThunk 执行异步操作
持久化整个状态localStorage 体积大,水合速度慢仅持久化 auth/关键 slice
thunk 中没有错误处理错误被静默忽略在 thunk 中使用 rejectWithValue
slice 之间存在循环依赖难以维护,容易出错使用中间件或单独的选择器
不规范化嵌套数据更新缓慢,reducer 复杂使用 @reduxjs/toolkit/normalizr 规范化

快速故障排查

问题原因解决方案
“Cannot read property of undefined”状态水合竞态条件添加加载检查或使用 skipHydration
Action 未触发重新渲染未使用类型化选择器使用类型正确的 useAppSelector
Redux 中出现 “Invariant violation”在 Immer 外部修改了状态仅在 createSlice reducer 中修改状态
状态较大时性能缓慢选择器未记忆化使用 Reselect 中的 createSelector
DevTools 未显示 action生产环境中已禁用 DevTools设置 devTools: process.env.NODE_ENV !== 'production'
RTK Query 缓存未失效缺少标签或标签错误添加 providesTags 和 invalidatesTags
使用 persist 时出现序列化错误状态中存在不可序列化的数据将其添加到 serializableCheck.ignoredActions
Thunk 错误未捕获没有错误边界用错误边界封装,或在组件中处理

生产就绪

Store 配置
// store/index.ts - Production-ready store
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const rootReducer = combineReducers({
  auth: authReducer,
  users: usersReducer,
  [api.reducerPath]: api.reducer,
});

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth'], // Only persist auth
  blacklist: [api.reducerPath], // Don't persist API cache
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat(api.middleware),
  devTools: process.env.NODE_ENV !== 'production',
});

setupListeners(store.dispatch);

export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
RTK Query 最佳实践
// services/api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const baseQuery = fetchBaseQuery({
  baseUrl: '/api',
  prepareHeaders: (headers, { getState }) => {
    const token = (getState() as RootState).auth.token;
    if (token) {
      headers.set('Authorization', `Bearer ${token}`);
    }
    return headers;
  },
});

const baseQueryWithReauth = async (args, api, extraOptions) => {
  let result = await baseQuery(args, api, extraOptions);

  if (result.error?.status === 401) {
    const refreshResult = await baseQuery('/auth/refresh', api, extraOptions);
    if (refreshResult.data) {
      api.dispatch(setToken(refreshResult.data.token));
      result = await baseQuery(args, api, extraOptions);
    } else {
      api.dispatch(logout());
    }
  }

  return result;
};

export const api = createApi({
  reducerPath: 'api',
  baseQuery: baseQueryWithReauth,
  tagTypes: ['User', 'Post'],
  endpoints: (builder) => ({
    getUsers: builder.query<User[], void>({
      query: () => '/users',
      providesTags: (result) =>
        result
          ? [...result.map(({ id }) => ({ type: 'User' as const, id })), 'User']
          : ['User'],
    }),
    createUser: builder.mutation<User, CreateUserDto>({
      query: (body) => ({
        url: '/users',
        method: 'POST',
        body,
      }),
      invalidatesTags: ['User'],
    }),
  }),
});

export const { useGetUsersQuery, useCreateUserMutation } = api;
Redux 测试
// test-utils.tsx
import { configureStore } from '@reduxjs/toolkit';
import { render, RenderOptions } from '@testing-library/react';
import { Provider } from 'react-redux';

interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
  preloadedState?: Partial<RootState>;
  store?: ReturnType<typeof configureStore>;
}

export function renderWithProviders(
  ui: React.ReactElement,
  {
    preloadedState = {},
    store = configureStore({
      reducer: rootReducer,
      preloadedState,
    }),
    ...renderOptions
  }: ExtendedRenderOptions = {}
) {
  function Wrapper({ children }: { children: React.ReactNode }) {
    return <Provider store={store}>{children}</Provider>;
  }
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

// Usage
test('displays user list', async () => {
  renderWithProviders(<UserList />, {
    preloadedState: {
      users: { items: [{ id: '1', name: 'John' }], loading: false },
    },
  });

  expect(screen.getByText('John')).toBeInTheDocument();
});
错误处理
// Centralized error handling middleware
const errorMiddleware: Middleware = () => (next) => (action) => {
  if (isRejectedWithValue(action)) {
    const error = action.payload;

    if (error.status === 401) {
      // Handle unauthorized
      store.dispatch(logout());
    }

    // Log to monitoring service
    logError({
      action: action.type,
      error: error.data?.message || 'Unknown error',
    });
  }

  return next(action);
};
监控指标
指标目标
状态序列化时间< 50ms
Action 分发时间< 16ms
缓存命中率> 80%
测试覆盖率> 85%
检查清单
  • 类型化 hooks(useAppDispatch、useAppSelector)
  • 使用 RTK Query 获取数据
  • 自动缓存失效
  • 令牌刷新处理
  • 使用 Redux Persist 持久化 auth 状态
  • 在生产环境中禁用 DevTools
  • 错误处理中间件
  • 使用预加载状态的测试工具
  • 对派生数据使用记忆化选择器
  • Redux DevTools 中不包含敏感数据

参考文档

评论

支持 Markdown