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 數據