同一项目中同时使用Apollo与Redux/Mobx作为状态管理器的可行性、实现与最佳实践
结论:同一项目中完全可以同时使用Apollo与Redux/Mobx作为状态管理器
两者不存在底层冲突,本质是定位互补的工具:Apollo Client核心是管理和GraphQL服务端同步的状态,自带请求处理、归一化缓存、乐观更新、订阅等开箱即用的能力;Redux/Mobx核心是管理纯客户端交互产生的本地状态,两者的适用场景本来就没有重叠,大量生产环境项目都在采用这套组合方案。
具体落地实现步骤
- 第一步:先划清状态归属边界,这是落地的核心前提
所有需要和后端GraphQL接口交互、需要持久化缓存、涉及服务端数据增删改查的状态,全部交给Apollo管理,比如用户基础信息、业务列表数据、实体详情数据、接口订阅的实时数据等。
所有不依赖服务端、纯前端交互产生的状态,全部交给Redux/Mobx管理,比如全局弹窗的显隐、主题配置、多步表单的临时输入值、UI组件的折叠/展开状态、前端自定义的全局标记等。 - 第二步:独立初始化两个状态实例,不要做深度耦合绑定
直接分开初始化Apollo Client实例和Redux/Mobx的store实例,在应用根组件分别用对应的Provider包裹即可,不需要把Apollo的缓存强行挂载到Redux Store中(老版本的apollo-cache-redux包已经停止维护,Apollo 3.0+版本默认自带的InMemoryCache性能和能力都远好于自定义Redux缓存方案,不推荐使用)。
以React项目为例,入口代码参考:import { ApolloProvider, ApolloClient, InMemoryCache } from '@apollo/client'; import { Provider as ReduxProvider } from 'react-redux'; import store from './redux-store'; // 自行配置的Redux store // 如果用Mobx,直接初始化对应的store实例即可,不需要额外包裹全局Provider(按需用context注入也可) const apolloClient = new ApolloClient({ uri: '/api/graphql', cache: new InMemoryCache(), }); ReactDOM.createRoot(document.getElementById('root')).render( <ApolloProvider client={apolloClient}> <ReduxProvider store={store}> <App /> </ReduxProvider> </ApolloProvider> ); - 第三步:处理跨状态的联动逻辑
两类状态保持单向联动即可,不要做全量双向同步:- 如果需要根据Apollo的请求结果触发客户端状态变更,直接在
useQuery/useMutation的onCompleted、onError回调,或者对应的副作用逻辑里,调用Redux的dispatch方法或者Mobx的action更新本地状态即可,比如登录接口请求成功后,触发Redux action打开新手引导弹窗。 - 如果需要用Redux/Mobx中存储的本地状态作为GraphQL请求的参数,直接在调用Apollo hooks的时候从Redux/Mobx中取出对应值,传给请求的
variables即可,比如从Redux中取出用户选择的列表筛选条件,作为列表查询的请求参数。
- 如果需要根据Apollo的请求结果触发客户端状态变更,直接在
开发最佳实践
- 坚决避免同一份数据双写:绝对不要把Apollo已经缓存的服务端数据,再复制一份存到Redux/Mobx中,双数据源一定会导致数据不一致的问题,排查成本极高,严格守住“服务端状态归Apollo,客户端状态归Redux/Mobx”的边界。
- 不要为了“统一状态入口”强行整合两者:很多人一开始会想把Apollo的所有状态都同步到Redux里实现所谓的“单一数据源”,实际上完全没有必要,Apollo自带的缓存已经做了归一化处理,强行同步只会增加冗余代码、降低性能、提升维护成本。
- 简单本地状态优先用Apollo原生能力承接:如果是非常简单的本地状态(比如全局提示文案、简单的UI开关),直接用Apollo的本地状态管理能力(通过
typePolicies配置本地字段)即可,不需要为了少量简单状态引入Redux/Mobx,只有当客户端状态逻辑复杂、存在大量跨组件交互、复杂派生计算的时候,再用Redux/Mobx承接。 - 避免循环更新逻辑:不要在Apollo的缓存订阅回调里触发Redux/Mobx状态更新,又在Redux/Mobx的状态订阅里触发Apollo的缓存写入或请求重拉,很容易引发无限循环的更新,所有跨状态的联动必须保持明确的单向触发源。
- 分开调试两类状态:Apollo相关的请求、缓存用Apollo DevTools排查,Redux/Mobx的状态变更用对应的官方DevTools排查即可,不需要额外开发统一的调试面板,投入产出比极低。
内容的提问来源于stack exchange,提问作者Nikola Markovic
相关产品推荐
相关产品推荐

