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
中。