21 分钟学 apollo-client 系列:修改本地 store 数据

前端之家收集整理的这篇文章主要介绍了21 分钟学 apollo-client 系列:修改本地 store 数据前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。@H_404_2@

搭建 Apollo client 端,集成 redux
使用 apollo-client 来获取数据
修改本地的 apollo store 数据
提供定制方案@H_404_2@

@L_502_5@
写入 store 的失败原因分析和解决方案@H_404_2@

修改本地 store 数据

之前我们已经知道,我们可以在请求结束之后,通过自动执行 fetchMoreupdateQuery 回调,修改 apollo store。@H_404_2@

那么,如何在不触发请求的情况下,主动修改 apollo store 呢?@H_404_2@

也许你会说通过 redux 的方式,dispatch 一个 action,由 reducer 来处理,但因为 apollo store 的数据存储方案,这会相当麻烦。
详细原理请看这一小节 @L_502_5@@H_404_2@

read & write

Apollo 对此,提供了两组命令式 api read/writeQueryread/writeFragment@H_404_2@

详见其文档:DataProxy
或者这篇中文文档:GraphQL 入门: Apollo Client 存储API@H_404_2@

读完这两篇文档,你大概就能掌握修改 apollo store 的技巧。@H_404_2@

不过其中还是有不少值得注意的点,下面通过代码和注释来体会:@H_404_2@

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,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',});
    }
}

不过,还是需要注意,它们和 fetchMore 里的 updateQuery 一样,都存在静默失败和写入限制。
如果你发现数据没有被更新,尝试看我给出的解读和解毒方案: 写入 store 的失败原因分析和解决方案@H_404_2@

你可能还注意到了 read/writeFragment 时,其 id 并不是简单的 5,而是 ${__typename}:5
这和 apollo store 存储数据的方式有关,我在 @L_502_5@ 详述了 apollo store 存储数据的原理。@H_404_2@

在此处,你只需要知道,这里 id 的值应当与你在创建 apollo client 时设置的 dataIdFromObject 有关,如果没有设置,默认为 ${__typename}:${data.id}
最好的方式是调用 client.dataIdFromObject 函数计算出 id@H_404_2@

const { id,__typename } = data;
const id = client.dataIdFromObject({
    id,__typename,});

简化接口

不过你不觉得上面这种写法相当麻烦吗?@H_404_2@

虽然先 read 再 write 比较原子化,但是考虑到大部分场景下我们只需要 update 就可以了,参数这么传来传去相当麻烦,更不用说会写太多重复的代码
所以咱们可以写一个 updateQueryupdateFragment 函数。@H_404_2@

enhancers.js@H_404_2@

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,});
    const data = client.readFragment({ id,variables });
    const nextData = resolver(data);

    client.writeFragment({
        id,});

    return nextData;
}

function updateQuery(config) {
    const { variables,query,resolver } = config;
    const data = client.readQuery({ variables,query });
    const nextData = resolver(data);
    client.writeQuery({
        variables,});
    return nextData;
};

function toUpperHeader(s = '') {
    const [first = '',...rest] = s;
    return [first.toUpperCase(),...rest].join('');
}

如此,我们可以这样简化之前的代码@H_404_2@

import React,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
            },resolver: data => parseNextData(data),});
    }

    handleUpdateFragment = () => {
        return updateFragment({
            id: 5,typename: 'Todo',resolver: data => parseNextData(data);
        });
    }
}

其中,resolver 是一个数据处理函数,它接收 read 操作后的 data ,返回的 nextData 将用于 write 操作。
这种简单的 update 场景其实是更常见的,且你仍然可以在 resolver 中进行 debug,可以说代码相当精简了。@H_404_2@

注册为 client api

封装修改 client 的 api 里我们提到可以使用 enhancer 的方式为 client 添加接口,如果你懒得每次都 import 这两个函数,那么不妨将他们注册为 client 的实例方法:@H_404_2@

enhancers.js@H_404_2@

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 实例中访问这两个函数了。@H_404_2@

提示:在组件中只需 withApollo 即可添加 clientprops 中。@H_404_2@

猜你在找的React相关文章