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

相关文章
相关标签/搜索