21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。
搭建 Apollo client 端,集成 redux
使用 apollo-client 来获取数据
修改本地的 apollo 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 去更新 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, 它的 __typename
是 UserInfo
,那么 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}
。