21 分钟学 apollo-client 系列:apollo store 存储细节

21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。

搭建 Apollo client 端,集成 redux
使用 apollo-client 来获取数据
修改本地的 apollo store 数据
提供定制方案

写入 store 的失败原因分析和解决方案

Apollo 集成 Redux 的原理

Apollo 仅仅是在 Redux 下开辟了一个 reducer,比如就叫 apollo。apollo 内部通过自己的私有 action (没有暴露给开发者)来更新这个 reducer 。
相当于这个 reducer 就是 Apollo 自己维护的 store ,它将所有通过 GraphQL query 得到的数据保存在这里

我们只能通过以下几种办法来修改 apollo store

  • query 成功后,通过 updateQuery 回调修改 store
  • 几个有限的命令式接口
  • Mutation

第二种方式,虽然接口是命令式的,但并不是直接修改 state 的值,背后本质是在调用它内部私有的 action ,最终还是以 dispatch 的形式修改 store。只是这个过程对开发者是屏蔽的。
当然你必须提供对应的 GraphQL Schema (一段用 gql 语法描述的 query 或 fragment),最终的数据结构如果不符合 Schema ,会 失败。
更具体的解释和运用,看 修改本地的 apollo store 数据 一节。

Apollo 的数据存储

可能你会问,既然 Apollo 的 store 是存在 redux 的 store 中的,自己写 reducer 去改不就好了吗?
这很容易想到,但不容易实现。

我们看看 apollo store 中数据存储的结构:

很像 normalizr 对不对?

简单说,apollo store 中存储的是扁平化的缓存。

当你想要直接修改 reducer 数据时,你需要

  • 手动计算出对应想去修改的 reducer 的 key
  • 当需要处理一个多层嵌套的实体时,还需要根据其嵌套的其它 __typename 找出其它嵌套的 reducer。这个过程也是递归的。

所以,手动写 reducer 去更新 apollo store 会相当麻烦。

扁平化数据

展开来说的话,Apollo 和 normalizr 之类的数据扁平化方案一样,只是一切都被自动化了,省去了你用 normalizr 手写的体力活,算是为数不多的惊喜了。

如果你没有接触过 normalizr ,那硬要用 reducer 的术语来描述的话,我们可以把 apollo 这个 reducer 视为一个 store。
在这个 store 中, 每一个存入 store 的实体都以 __typename:id 的方式单独存放到一个 reducer 中,__typename 取自于你请求时使用的 GraphQL Schema,如 UserTimeline:260
如果你从后端接收到一组 UserTimeline ,那么其中每一项都会在 store 里注册一个 reducer ,可能会出现 UserTimeline:1 ~ UserTimeline:100 的盛景。当你在别的请求中再请求到 UserTimeline:260 的时候,就直接 merge 到原有的 reducer 中。

你可能说这样很好啊,直接根据这个 key 访问对应的 state 就可以了。但问题是,凡是嵌套结构,都会被抽出来单独作为一个 reducer。
比方说,上图中 UserTimeline 包含一个 userInfo, 它的 __typenameUserInfo,那么 UserTimeline:260 下的 userInfo 中存储只是对应的 reducer 索引,形如

{ id: 'UserInfo:1004',generated: false,...}

真实的 UserTimeline:260.userInfo 存储在一个名为 UserInfo:1004 的 reducer 中。而 UserInfo:1004 可能也并不完整,因为它内部也可能存在嵌套,也需要经历这样的一次搜寻过程。要一直递归下去,我们才能得到最终的完整数据。

id 的生成规则

Updating the Store | Apollo React Docs

根据官方文档的说法,apollo 在创建 apollo client 时,可选设置 dataIdFromObject。

const client = new ApolloClient({
    networkInterface,dataIdFromObject: x => `${x.__typename}:${x.id}`,});

如果不设置 dataIdFromObject ,其认就是 ${x.__typename}:${x.id}
如果 x 不存在 id,则可能出现 ${__typename}:${id}.${property}.${subProperty}

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...