21 分钟学 apollo-client 是一个系列,简单暴力,包学包会。javascript
搭建 Apollo client 端,集成 redux
使用 apollo-client 来获取数据
修改本地的 apollo store 数据
提供定制方案html
推荐先看:GraphQL 入门: 链接到数据
本文只作补充。java
下面编写一个最简单的 Container,观察是否能 query 到数据。react
container.jsx
webpack
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 <div>Hello Apollo</div>; } }
@graphql(query)
是 apollo 提供的高阶组件,以装饰器的形式包裹你的组件。这里是最简单的状况,只传一个 query。git
基本的 query 语法能够参看官方文档 Queries and Mutations | GraphQL,这里提一下 Apollo 特有的一些语法。github
query.gql
web
#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 了。redux
上一节咱们在 webpack 中配置了 graphql-tag/loader,这个 loader 容许你将 query 、fragment 这些 schema 字符串,以 .gql
文件的形式保存,在 import 时转化成 js 代码。
其他部分,基本上和 GraphQL 原生写法是同样的,注意几个点:
#import
语法是 loader 提供的,语法和 js 的 import 差很少,除了不能解构 。gql
文件里写多个 query
或 fragment
。对了,为了最小化实践,你能够先写不带参数的 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(), }, }), })
其中
skip
和 shouldComponentUpdate
的效果是同样的,决定是否 re-fetch。若是回调返回 false 直接不做请求。options
返回一个函数,用以设置请求的细节,好比 variables
用于设置 query 参数更详细的文档能够查阅
如文档 Pagination | Apollo React Docs 所说,Apollo 支持两种分页
按条数偏移量来请求分页,请求时提供两个参数
可见你须要本身维护一个 pageNum: n 来实现按页码分页
这是 Relay 风格的请求,cursor 用于记录下个请求开始时,返回的第一个元素的位置,通常能够用该元素的 id 来标识。
咱们后端并无采起上面任何一种,而是提供了一个 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 ( <TopicList hasMore={this.hasMore()} // TopicList 里有一个按钮,点击后调用 loadNextPage 进行下一页请求 loadNextPage={this.loadNextPage} loading={this.props.data && this.props.data.loading} isError={this.props.data && this.props.data.error} /> ); } }
updateQuery
中,使用 parseNextData
通过一些处理,返回新数据给 apollo,apollo 将把它写入到 apollo store 中。
注意,这里至少会有两处大坑
但写入失败的状况还不止于此!若是你发现最终数据并未改变,多是中招了,解毒方案 请阅读 写入 store 的失败缘由分析和解决方案
这段代码只演示了如何 被动 地去修改本地的 apollo store 数据,要问如何 主动 去修改 apollo store,请看这篇文章: 修改本地的 apollo store 数据