在React reducer中更新缓存是否合理?缓存更新应委托给系统特定模块吗?
你担心的没错,在reducer中执行缓存更新属于典型的反模式,不建议这么做。
核心原因
- 违背reducer的设计原则:reducer要求是无副作用的纯函数,相同的state和action输入必须返回完全相同的新state,不能修改外部状态、执行IO操作等。你调用
usersCache.updateUser属于修改外部缓存的副作用操作,直接违反了纯函数要求。 - 会引发不可预期的bug:React严格模式下reducer会被重复调用2次,会导致缓存被更新两次,比如关注操作本来要让粉丝数+1,实际变成+2,数据直接不一致。另外如果后续你要接入时间旅行调试、action重放等能力,重放action时缓存会被反复修改,状态直接混乱。
- 增加维护和测试成本:纯函数reducer的测试只需要传入固定的state和action,断言返回结果即可。加入副作用后你需要额外mock缓存实例,还要断言缓存方法的调用情况,测试复杂度大幅上升。
推荐实现方案
方案1:自定义dispatch中间层统一处理缓存同步
你可以在useReducer外层封装自定义hook,拦截dispatch操作,在reducer执行完成、状态更新后再同步缓存,既不污染reducer的纯函数特性,也能保证两边数据一致,示例代码:
import { useReducer, useEffect } from 'react'; import { usersCache } from "../../services/firebase/api/users"; import usersReducer from './yourReducerPath'; const useUsersState = () => { const [state, dispatch] = useReducer(usersReducer, new Map()); // 监听状态变化,只同步发生变更的用户数据到缓存 useEffect(() => { state.forEach((userData, userId) => { const cachedUser = usersCache.get(userId); if (cachedUser?.totalFollowers !== userData.totalFollowers) { usersCache.updateUser(userId, { totalFollowers: userData.totalFollowers }); } }) }, [state]) return [state, dispatch]; }
方案2:统一收口数据更新逻辑到服务层
将所有用户数据的更新操作统一封装到独立的用户服务模块,所有业务侧要更新用户数据的地方,都调用服务层的方法,由服务层统一处理缓存更新和Context状态更新,从根源避免两边数据不一致的问题,示例:
// services/userService.js import { usersCache } from "./firebase/api/users"; import { usersDispatch } from "../contexts/UsersContext"; // 导出你Context的dispatch export const followUser = (userId, isFollowing) => { // 先更新缓存 const prevUserData = usersCache.get(userId); const newTotalFollowers = prevUserData.totalFollowers + (isFollowing ? 1 : -1); usersCache.updateUser(userId, { totalFollowers: newTotalFollowers }); // 再更新Context状态 usersDispatch({ type: 'follow-user', userId, isFollowing }) }
这种方案的优势是完全解耦业务代码和状态、缓存的实现,后续不管是换缓存方案还是改状态管理逻辑,都只需要修改服务层代码,不需要动业务侧。
关于缓存更新的位置规范
缓存更新操作不建议散落在组件、页面等各个位置,最好统一委托给特定的服务模块或者状态中间层处理,避免出现漏更新、重复更新、逻辑不一致的问题。
内容的提问来源于stack exchange,提问作者Raul
相关产品推荐
相关产品推荐

