资讯专栏INFORMATION COLUMN

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

617035918 / 772人阅读

摘要:分钟学是一个系列,简单暴力,包学包会。搭建端,集成使用来获取数据修改本地的数据提供定制方案请求拦截封装修改的存储细节写入的失败原因分析和解决方案修改本地数据之前我们已经知道,我们可以在请求结束之后,通过自动执行的回调,修改。

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

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

请求拦截

封装修改 client 的 api

apollo store 存储细节
写入 store 的失败原因分析和解决方案

修改本地 store 数据

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

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

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

read & write

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

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

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

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

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








文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/91921.html

相关文章

  • 21 分钟 apollo-client 系列:apollo store 存储细节

    摘要:分钟学是一个系列,简单暴力,包学包会。内部通过自己的私有没有暴露给开发者来更新这个。相当于这个就是自己维护的,它将所有通过得到的数据保存在这里。的生成规则根据官方文档的说法,在创建时,可选设置。如果不存在,则可能出现。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来...

    lavor 评论0 收藏0
  • 21 分钟 apollo-client 系列:写入失败的原因和解决方案

    摘要:分钟学是一个系列,简单暴力,包学包会。一旦你丢失了,可能会导致写入失败,或者尽管写入了,但本该携带的那一层的数据没有写入。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本地的 apollo store 数据提供定制方案 请求拦截 封装修改 clie...

    Baoyuan 评论0 收藏0
  • 21 分钟 apollo-client 系列:获取数据

    摘要:分钟学是一个系列,简单暴力,包学包会。一旦组件挂载后,会自动进行数据请求,前提是客户端提供的和后端的相符。如果回调返回直接不作请求。在组件内进行分页请求之前提到了,这个装饰器为添加了对象,其中有个函数为。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本...

    robin 评论0 收藏0
  • 21 分钟 apollo-client 系列:扩展 ApolloClient 的 api

    摘要:分钟学是一个系列,简单暴力,包学包会。那怎么办呢本章就教你非常简单地实现扩展的。我们可以借鉴的的写法,为的实例添加一些自己的方法。更重要的是,也会有的效果,上一个的输出会成为下一个的输入,便于组合。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本地的 ...

    levy9527 评论0 收藏0
  • 21 分钟 apollo-client 系列:简单搭建

    摘要:分钟学是一个系列,简单暴力,包学包会。其中提到了等需要后端配合的东西,徒增了配置的复杂性。如果不行,再跟随我的简单步骤试试。环境要求请确保你已经搭建了自己的环境下文在行号前添加表示删除的原代码,表示新增的代码。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据...

    ranwu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<