Redux Toolkit中使用Map/Set是否属于不良实践?
Redux状态中使用Map/Set是否为不良实践?
问题背景
我正在使用Redux Toolkit构建Redux Store,希望在状态中存储用户映射,代码实现如下:
export type UserType = { _id: string firstName: string; lastName: string; email: string; }; interface UserStoreType { users: Map<string, UserType> } const initialState: UserStoreType = { users: new Map(); }; export const userSlice = createSlice({ name: "counter", initialState, reducers: { append: (state, action) => { const user = action.payload; state.users.set(user._id, user); } }, }); export const userAction = userSlice.actions; export const userReducer = userSlice.reducer;
但我看到有观点称在Redux状态中使用Map或Set是反模式,应避免。请问这种用法是否属于不良实践?若是,推荐方案是什么?是否应改用普通JavaScript对象替代Map?
我知道Redux Toolkit允许在reducer中安全修改状态,但不确定Map/Set的使用是否存在问题或不被推荐。我选择Map是因为认为其按键查找性能更优,且API便于遍历键/值,原本预期这是存储键值对的合理方式,但网上讨论似乎不推荐,希望明确是否应避免使用Map/Set,或该场景下是否可行。
回答
1. 是否属于不良实践?
严格来说不是绝对的反模式,但确实不推荐在Redux状态中使用Map/Set,核心原因在于Redux的设计基础:
- Redux要求状态是可序列化的纯JS对象/数组,Map/Set无法被
JSON.stringify这类默认工具正确处理,会导致状态持久化(比如localStorage缓存)、Redux DevTools状态回溯、服务端渲染的序列化环节出错。 - 虽然Redux Toolkit的Immer支持直接修改Map/Set,但这只是技术上可行,并不符合社区主流的最佳实践。
2. 推荐方案:改用普通JS对象存储
对于这种键值对存储场景,直接用以用户_id为键的普通JS对象是最优解,改造后的代码如下:
export type UserType = { _id: string firstName: string; lastName: string; email: string; }; // 改为对象类型,键为用户_id,值为UserType interface UserStoreType { users: Record<string, UserType> } const initialState: UserStoreType = { users: {}; // 初始化为空对象 }; export const userSlice = createSlice({ name: "users", // 原代码的"counter"是笔误,建议改为对应业务名称 initialState, reducers: { append: (state, action) => { const user = action.payload; // Immer会自动处理不可变更新,直接赋值即可 state.users[user._id] = user; }, // 额外示例:删除用户 remove: (state, action) => { const userId = action.payload; delete state.users[userId]; } }, }); export const userAction = userSlice.actions; export const userReducer = userSlice.reducer;
3. 性能与遍历的替代方案
你担心的Map的优势,用普通对象完全可以覆盖:
- 按键查找:对象属性访问
state.users[userId]的性能和Map的get方法几乎无差异,日常业务场景下完全够用。 - 遍历操作:可以用
Object.keys()、Object.values()、Object.entries()实现,比如:// 获取所有用户数组 const allUsers = Object.values(userStore.users); // 遍历键值对 Object.entries(userStore.users).forEach(([userId, user]) => { // 处理逻辑 });
4. 特殊场景的例外
如果你的业务确实依赖Map的独有特性(比如非字符串类型的键、严格的键顺序、超大规模数据的频繁增删),可以考虑使用,但需要自己处理序列化问题:比如把Map转换为Array.from(map.entries())存储,读取时再转成Map。但这种方式会增加代码复杂度,非必要不建议。
内容的提问来源于stack exchange,提问作者Hashi
相关产品推荐
相关产品推荐

