资讯专栏INFORMATION COLUMN

21 分钟学 apollo-client 系列:获取数据

robin / 3368人阅读

摘要:分钟学是一个系列,简单暴力,包学包会。一旦组件挂载后,会自动进行数据请求,前提是客户端提供的和后端的相符。如果回调返回直接不作请求。在组件内进行分页请求之前提到了,这个装饰器为添加了对象,其中有个函数为。

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

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

请求拦截

封装修改 client 的 api

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

使用 Apollo 获取数据

推荐先看:GraphQL 入门: 连接到数据
本文只做补充。

下面编写一个最简单的 Container,观察是否能 query 到数据。

container.jsx

import React, { PureComponent } from "react";
import { graphql } from "react-apollo";
import query from "./query.gql";

@graphql(query)
export default class ApolloContainer extends PureComponent {
    render() {
        console.log(this.props);
        return 
Hello Apollo
; } }

@graphql(query) 是 apollo 提供的高阶组件,以装饰器的形式包裹你的组件。这里是最简单的情况,只传一个 query。

query 语法

基本的 query 语法可以参看官方文档 Queries and Mutations | GraphQL,这里提一下 Apollo 特有的一些语法。

query.gql

#import "../gql/pageInfo.gql"
#import "@/gql/topic/userTopicEntity.gql"

query topic($topicId: Int!, $pageNum: Int = 1) {
    community {
        topicEntity {
            listByTopicId(topicId: $topicId, pageSize: 10, pageNum: $pageNum) {
                pageInfo {
                    ...pageInfo
                }
                edges {
                    ...userTopicEntity
                }
            }
        }
    }
}

前两行 import 了其它的 fragment。想必你已经知道,GraphQL 主要通过 fragment 来组合分形 Query。一个好的实践是尽量对业务实体编写 fragment 以便复用。
代码脱敏的关系我就不放详细的 fragment 了。

上一节我们在 webpack 中配置了 graphql-tag/loader,这个 loader 允许你将 query 、fragment 这些 schema 字符串,以 .gql 文件的形式保存,在 import 时转化成 js 代码。

其余部分,基本上和 GraphQL 原生写法是一样的,注意几个点:

一次请求只能包含一个 query,而且不能包含未使用的 fragment。

#import 语法是 loader 提供的,语法和 js 的 import 差不多,除了不能解构 。
如果你 webpack 配置了 alias 就能使用第二行那种写法。注意,它会把该文件内所有的内容都 import 进来,所以不能在一个 gql 文件里写多个 queryfragment

对了,为了最小化实践,你可以先写不带参数的 query。也先不要写 union type。

props.data 的数据结构

这样就好了吗,是的。一旦组件挂载后,会自动进行数据请求,前提是客户端提供的 query schema 和后端的相符。

如果请求成功后,会发生什么事情呢?我们可以查看 this.props 打出的 log 来验证:

// this.props
{
    // ....
    data: {
        // ...
        community: { ... }, // 这是获取到的数据,结构和你提供的 query schema 一致
        loading: false, // 请求过程中为 true
        networkStatus: 7, // 从 0-8,具体值的含义看这个文件 https://github.com/apollographql/apollo-client/blob/master/src/queries/networkStatus.ts
        variables: { ... }, // 请求时所用的参数
        fetchMore, // 一个函数,用于在组件内「继续请求」,一般用于分页请求
        refetch, // 函数,用于组件内「强制重新请求」
        updateQuery, // 请求成功后立即调用,用于更新本地 store
    }
}
高级请求

我们仅改写装饰器部分

@graphql(query, {
    skip: props => !isValid(props),
    options: props => ({
        variables: {
            topicId: getIdFromUrl(),
        },
    }),
})

其中

skipshouldComponentUpdate 的效果是一样的,决定是否 re-fetch。如果回调返回 false 直接不作请求。

options 返回一个函数,用以设置请求的细节,比如 variables 用于设置 query 参数

更详细的文档可以查阅

API: graphql container with queries | Apollo React Docs

GraphQL 入门: Apollo Client - 连接到数据

分页请求

如文档 Pagination | Apollo React Docs 所说,Apollo 支持两种分页

offset-based

按条数偏移量来请求分页,请求时提供两个参数

limit:相当于 pageSize,一页最多取多少个

offset: 条数偏移量,第 n 页的 offset = limit * n

可见你需要自己维护一个 pageNum: n 来实现按页码分页

cursor-based

这是 Relay 风格的请求,cursor 用于记录下个请求开始时,返回的第一个元素的位置,一般可以用该元素的 id 来标识。

RESTful 风格

我们后端并没有采取上面任何一种,而是提供了一个 pageInfo 对象,由前端传入所需参数,保持和 RESTful api 相似的风格。

query.gql

#import "../gql/pageInfo.gql"
#import "@/gql/topic/userTopic.gql"

query topic($topicId: Int!, $pageNum: Int = 1) {
    community {
        topicEntity {
            listByTopicId(topicId: $topicId, pageSize: 10, pageNum: $pageNum) {
                pageInfo {
                    ...pageInfo
                }
                edges {
                    ...userTopicEntity
                }
            }
        }
    }
}

pageInfo.gql

fragment pageInfo on PageInfo {
    pageNum     # 页码
    pageSize    # 每页条数
    pages       # 总页数
    total       # 总条数
}

声明下,由于我们只使用 GraphQL 的 Query 功能,所以没研究过这种格式是否会影响 Mutation。现在或以后有 Mutation 需求的,尽量采用官方推荐的前两种吧。

在组件内进行分页请求

之前提到了, graphql 这个装饰器为 this.props 添加了 data 对象,其中有个函数为 fetchMore

fetchMore 看名字就知道是用来作分页请求的。

下面我们看一个比较真实的例子,许多业务相关的代码都用表示其作用的函数替代了,注意看注释:

import React, { PureComponent } from "react";
import { graphql } from "react-apollo";
import { select } from "./utils";
// 注意,这里用的 query 是 「RESTful 风格」那一节中贴出的 schema
import query from "./query.gql";

@graphql(query, {
    skip: props => !isValid(props),
    options: props => ({
        variables: {
            topicId: getIdFromUrl(),
        },
    }),
})
@select({
    // 你可以写一个函数,从 this.props.data 里过滤出当前列表的 pageInfo,直接添加到 this.props.pageInfo
    pageInfo: getPathInfoFromProps(props),
})
export default class TopicListContainer extends PureComponent {
    hasMore = () => {
        const { pageNum = 0, pages = 0 } = this.props.pageInfo || {};
        return pageNum < pages;
    }

    loadNextPage = () => {
        const { pageInfo = {}, data } = this.props;
        const { pageNum = 1 } = pageInfo;
        const fetchMore = data && data.fetchMore;

        if (!this.hasMore()) return;
        if (!fetchMore) return;

        return fetchMore({
            variables: {
                // 是的,这里不需要把你在 `@graphql` 装饰器中定义的其它 variables 再写一遍
                // apollo 会自动 merge
                pageNum: pageNum + 1,
            },
            // 这个回调函数,会在 fetch 成功后自动执行,用于修改本地 apollo store
            updateQuery: (prev, { fetchMoreResult }) => {
                if (!fetchMoreResult) return prev;

                // 尝试 log 下 `fetchMoreResult`,其返回的数据结构,和 query 中的 schmea 是一致的

                // parseNextData 返回新数据。
                // 新数据的数据结构必须和 query schema 一样
                // NOTE: 此处会有大坑,如果你发现最终数据并未改变,请阅读后文
                return parseNextData(prev, fetchMoreResult);
            }
        });
    }

    render() {
        return (
            
        );
    }
}

updateQuery 中,使用 parseNextData 经过一些处理,返回新数据给 apollo,apollo 将把它写入到 apollo store 中。
注意,这里至少会有两处大坑

如果写入失败,是会静默失败的,也就是说 没有任何报错提示

如果写入数据的结构,和 query schema 不符,就会写入失败。

但写入失败的情况还不止于此!如果你发现最终数据并未改变,可能是中招了,解毒方案 请阅读 写入 store 的失败原因分析和解决方案

这段代码只演示了如何 被动 地去修改本地的 apollo store 数据,要问如何 主动 去修改 apollo store,请看这篇文章: 修改本地的 apollo store 数据








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

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

相关文章

  • 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
  • 21 分钟 apollo-client 系列:写入失败的原因和解决方案

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

    Baoyuan 评论0 收藏0
  • 21 分钟 apollo-client 系列:apollo store 存储细节

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

    lavor 评论0 收藏0
  • 21 分钟 apollo-client 系列:请求拦截和 FragmentMatcher

    摘要:分钟学是一个系列,简单暴力,包学包会。接管了请求和状态管理。一般在生产环境中,我们通常还希望做权限验证请求拦截等事务处理。 21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。 搭建 Apollo client 端,集成 redux使用 apollo-client 来获取数据修改本地的 apollo store 数据提供定制方案 请求拦截 封装修改 clie...

    Eastboat 评论0 收藏0

发表评论

0条评论

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