21 分鐘學 apollo-client 系列:修改本地 store 數據

21 分鐘學 apollo-client 是一個系列,簡單暴力,包學包會。javascript

搭建 Apollo client 端,集成 redux
使用 apollo-client 來獲取數據
修改本地的 apollo store 數據
提供定製方案html

apollo store 存儲細節
寫入 store 的失敗緣由分析和解決方案java

修改本地 store 數據

以前咱們已經知道,咱們能夠在請求結束以後,經過自動執行 fetchMoreupdateQuery 回調,修改 apollo store。react

那麼,如何在不觸發請求的狀況下,主動修改 apollo store 呢?redux

也許你會說經過 redux 的方式,dispatch 一個 action,由 reducer 來處理,但由於 apollo store 的數據存儲方案,這會至關麻煩。
詳細原理請看這一小節 apollo store 存儲細節segmentfault

read & write

Apollo 對此,提供了兩組命令式 api read/writeQueryread/writeFragmentapi

詳見其文檔: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 就能夠了,參數這麼傳來傳去至關麻煩,更不用說會寫太多重複的代碼。
因此我們能夠寫一個 updateQueryupdateFragment 函數。

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

封裝修改 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 便可添加 clientprops 中。

相關文章
相關標籤/搜索