21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。javascript
搭建 Apollo client 端,集成 redux
使用 apollo-client 来获取数据
修改本地的 apollo store 数据
提供定制方案html
以前咱们已经知道,咱们能够在请求结束以后,经过自动执行 fetchMore
的 updateQuery
回调,修改 apollo store。react
那么,如何在不触发请求的状况下,主动修改 apollo store 呢?redux
也许你会说经过 redux 的方式,dispatch 一个 action,由 reducer 来处理,但由于 apollo store 的数据存储方案,这会至关麻烦。
详细原理请看这一小节 apollo store 存储细节segmentfault
Apollo 对此,提供了两组命令式 api read/writeQuery
和 read/writeFragment
api
详见其文档:DataProxy
或者这篇中文文档:GraphQL 入门: Apollo Client 存储APIapp
读完这两篇文档,你大概就能掌握修改 apollo store 的技巧。函数
不过其中仍是有很多值得注意的点,下面经过代码和注释来体会:fetch
import React, { PureComponent } from 'react'; import TodoQuery from './TodoQuery'; import TodoFragment form './TodoFragment'; import { withApollo, graphql } from 'react-apollo'; @graphql(TodoQuery) @withApollo // 经过 props 让组件能够访问 client 实例 class TodoContainer extends PureComponent { handleUpdateQuery = () => { const client = this.props.client; const variables = { id: 5 }; const data = client.readQuery({ variables, query: TodoQuery, }); const nextData = parseNextData(data); client.writeQuery({ variables, query: TodoQuery, data: nextData, }); } handleUpdateFragment = () => { const client = this.props.client; const data = client.readFragment({ id: 'Todo:5', fragment: TodoFragment, fragmentName: 'todo', // fragment 的名字,必填,不然可能会 read 失败 }); const nextData = parseNextData(data); client.writeFragment({ id: 'Todo:5', fragment: TodoFragment, fragmentName: 'todo', data: nextData, }); } }
不过,仍是须要注意,它们和 fetchMore 里的 updateQuery 同样,都存在静默失败和写入限制。
若是你发现数据没有被更新,尝试看我给出的解读和解毒方案: 写入 store 的失败缘由分析和解决方案
你可能还注意到了 read/writeFragment 时,其 id 并非简单的 5
,而是 ${__typename}:5
。
这和 apollo store 存储数据的方式有关,我在 apollo store 存储细节 详述了 apollo store 存储数据的原理。
在此处,你只须要知道,这里 id 的值应当与你在建立 apollo client 时设置的 dataIdFromObject
有关,若是没有设置,默认为 ${__typename}:${data.id}
。
最好的方式是调用 client.dataIdFromObject
函数计算出 id
const { id, __typename } = data; const id = client.dataIdFromObject({ id, __typename, });
不过你不以为上面这种写法至关麻烦吗?
虽然先 read 再 write 比较原子化,可是考虑到大部分场景下咱们只须要 update 就能够了,参数这么传来传去至关麻烦,更不用说会写太多重复的代码。
因此我们能够写一个 updateQuery
、updateFragment
函数。
enhancers.js
import client from './client'; function updateFragment(config) { const { id: rawId, typename, fragment, fragmentName, variables, resolver } = config; // 默认使用 fragmentName 做为 __typename const __typename = typename || toUpperHeader(fragmentName); const id = client.dataIdFromObject({ id: rawId, __typename, }); const data = client.readFragment({ id, fragment, fragmentName, variables }); const nextData = resolver(data); client.writeFragment({ id, fragment, fragmentName, variables, data: nextData, }); return nextData; } function updateQuery(config) { const { variables, query, resolver } = config; const data = client.readQuery({ variables, query }); const nextData = resolver(data); client.writeQuery({ variables, query, data: nextData, }); return nextData; }; function toUpperHeader(s = '') { const [first = '', ...rest] = s; return [first.toUpperCase(), ...rest].join(''); }
如此,咱们能够这样简化以前的代码
import React, { PureComponent } from 'react'; import TodoQuery from './TodoQuery'; import TodoFragment form './TodoFragment'; import { withApollo, graphql } from 'react-apollo'; import { updateQuery, updateFragment } from '@/apollo/enhancers'; @graphql(TodoQuery) @withApollo // 经过 props 让组件能够访问 client 实例 class TodoContainer extends PureComponent { handleUpdateQuery = () => { return updateQuery({ variables: { id: 5 }, query: TodoQuery, resolver: data => parseNextData(data), }); } handleUpdateFragment = () => { return updateFragment({ id: 5, typename: 'Todo', fragment: TodoFragment, fragmentName: 'todo', resolver: data => parseNextData(data); }); } }
其中,resolver
是一个数据处理函数,它接收 read 操做后的 data ,返回的 nextData 将用于 write 操做。
这种简单的 update 场景实际上是更常见的,且你仍然能够在 resolver 中进行 debug,能够说代码至关精简了。
在 封装修改 client 的 api 里咱们提到可使用 enhancer 的方式为 client 添加接口,若是你懒得每次都 import 这两个函数,那么不妨将他们注册为 client 的实例方法:
enhancers.js
const enhancers = [ updateFragmentFactory, updateQueryFactory, ]; export default function applyEnhancers(client) { // 更函数式的写法是把 enhancers 也做为参数传进来,可是我须要的 enhancer 比较少,作此精简 return enhancers.reduce( (result, enhancer) => enhancer(result), client ); } // --- enhancers --- function updateFragmentFactory(client) { return function updateFragment(config) { // ... } return client; } function updateQueryFactory(client) { return function updateQuery(config) { // ... }; return client; }
这样你就能够直接从 client 实例中访问这两个函数了。
提示:在组件中只需
withApollo
便可添加client
到props
中。