Slide 1

Slide 1 text

Data feching and caching on Apollo Client 2017/09/15 ToKyoto.js LT @joe_re

Slide 2

Slide 2 text

Who am I? twitter: @joe_re github: @joe­re working in freee.K.K GraphQL Tokyo Organizer

Slide 3

Slide 3 text

What is Apollo Client? GraphQL のClient ライブラリ React 、Angular 、Vue 、NativeApp(ReactNative, iOS, Android) などなど幅広くサポート 対抗馬はFacebook 製のRelay

Slide 4

Slide 4 text

vs Relay

Slide 5

Slide 5 text

の話はしないけどざっくりApollo が優位なところをざっくり サポートはReact, ReactNative のみ GraphQL API に制約をかける 規約が多いので理解するまで大変 ( ただし慣れればパフォーマンスやDE は向上する)

Slide 6

Slide 6 text

Fundations of GraphQL

Slide 7

Slide 7 text

What is GraphQL Facebook が公開しているAPI の仕様 データの取得、更新を行うクエリ言語を提供する 特定の言語やフレームワークを指すものではない

Slide 8

Slide 8 text

Example Query query { repository(owner: "apollographql", name: "apollo-client") { name, description, stargazers { totalCount } } } Result { "data": { "repository": { "name": "apollo-client", "description": ":rocket: A fully-featured, production ready caching GraphQL client for every server or UI framework", "stargazers": { "totalCount": 3948 } } } }

Slide 9

Slide 9 text

Good Points クライアント側で必要なデータを細かく取捨選択できるの で無駄がない 複数のリソースを1 つのリクエストで一度に得られる クライアントが理解できるスキーマを元にクエリするの で、期待した結果を得られる( 型定義も容易) (http://graphql.org/)

Slide 10

Slide 10 text

Three types of GraphQL operation Query: データの取得 Mutation: Query で取得したデータの更新 Subscription: データの変更の購読(Response Stream)

Slide 11

Slide 11 text

Example of Query query SearchRepository($queryString: String!, $cursor: String) { search(query: $queryString, type: REPOSITORY, first: 30, after: $cursor) { repositoryCount edges { cursor node { ... RepositorySearchResult } } } } fragment RepositorySearchResult on Repository { databaseId name owner { avatarUrl(size: 40) login } description stargazers { totalCount } forks { totalCount } updatedAt }

Slide 12

Slide 12 text

Example of Mutation mutation AddStar($input: AddStarInput!) { addStar(input: $input) { starrable { viewerHasStarred } } }

Slide 13

Slide 13 text

Example of Subscription subscription sub { newMessage { ... newMessageFields } } fragment newMessageFields on Message { body sender }

Slide 14

Slide 14 text

※ ここからようやくApollo Client の話

Slide 15

Slide 15 text

DEMO (GitHub Client)

Slide 16

Slide 16 text

実装例にはReact を使います

Slide 17

Slide 17 text

Creating a client and inject via a provider const networkInterface = createNetworkInterface({ uri: 'https://api.github.com/graphql' }); const middleWareInterface: MiddlewareInterface[] = [{ applyMiddleware(req, next) { const headers = req.options.headers || {}; AsyncStorage.getItem('token').then((token) => { headers.authorization = token ? `Bearer ${token}` : ''; req.options.headers = headers; next(); }); } }]; networkInterface.use(middleWareInterface); const client = new ApolloClient({ networkInterface }); export default class App extends React.Component { render() { return ( ) } }

Slide 18

Slide 18 text

Creating a client and inject via a provider 作成したclient はprovider を通じて各コンポーネントで 利用可能となる graphql() を用いてGraohQL Container を作成することが できる GraphQL Container は与えるprops の変化によるfetch を 自動で行う client を直接呼び出して使用することも可能( withApollo())

Slide 19

Slide 19 text

Creating a GraphQL Container function ReleasesPage(props: Props & AppoloProps) { if (props.loading) { return Loading; } return ( ); } const withData: OperationComponent = graphql(RELEASES_QUERY, ({ options: ({ owner, name }) => ( { variables: { owner, name }, notifyOnNetworkStatusChange: true } ), props: (props) => { const { loading, repository } = props.data; return { loading, repository, } } })); export default withData(ReleasesPage);

Slide 20

Slide 20 text

GraphQL では大抵のリソースが 1 つのリクエストで取れる

Slide 21

Slide 21 text

件数が膨大なデータに対しても 1 つのリクエストで取得できる??

Slide 22

Slide 22 text

そんなわけはない

Slide 23

Slide 23 text

件数が多ければ当然 その分レスポンスは遅くなる

Slide 24

Slide 24 text

つまりページネーション が必要

Slide 25

Slide 25 text

data.fetchMore ページネーションを実現するためのAPI 現在のfetching の状態をcache に残したまま、新しく取得し た結果をマージすることができる Relay.QL のcursor のパターンだけではなく、どのページネ ーションのパターンにでも使える

Slide 26

Slide 26 text

Example of Pagination const withData: OperationComponent = graphql(REPOSITORY_QUERY, ({ options: ({ queryString }) => ( { variables: { queryString }, notifyOnNetworkStatusChange: true } ), props: (props) => { const { loading, search, fetchMore } = props.data; return { loading, searchResult: { search }, loadNextPage: (cursor) => { return fetchMore({ variables: { cursor }, updateQuery: (prev, data) => { const { search } = data.fetchMoreResult; search.edges = prev.search.edges.concat(search.edges); return { search }; } }) } } } }));

Slide 27

Slide 27 text

Updating fetched data データの更新には前述の通りMutation を使う Mutation もQuery と同じように graphql() を用いて使用可能 になる Mutaion の場合はQuery とは違い、props の変化に応じて実行 されない

Slide 28

Slide 28 text

Example of Mutaion function StarBadge(props: Props) { return ( rops.repository.viewerHasStarred ? props.removeStar(repository.id) : props.addStar(repository.id)}> {//... 省略} ); } const StarBadgeWithMutations = compose( graphql(ADD_STAR_MUTATION, { props: ({ ownProps, mutate }) => ({ addStar: (id: string) => mutate({ variables: { input: { starrableId: id } }, }) }) }), graphql(REMOVE_STAR_MUTATION, { props: ({ ownProps, mutate }) => ({ removeStar: (id: string) => mutate({ variables: { input: { starrableId: id } }, }) }) }) )(StarBadge);

Slide 29

Slide 29 text

Automatic store updates 例ではMutation の発行のロジックしか書いていないにも関わ らず、store のデータも更新される mutation の結果で返ってきているid と同一のものがstore にあ る場合には、store も同時に更新される mutation RemoveStar($input: RemoveStarInput!) { removeStar(input: $input) { starrable { id stargazers { totalCount } viewerHasStarred } } }

Slide 30

Slide 30 text

if you can't use automatic store updates... mutation のオプションを通じて手動でstore をupdate するこ とが可能 refetchQueries: mutation の後に再度query を発行しデータを 更新する update: mutation の後にstore のcache を直接いじってデータ を更新する updateQueries: deprecated

Slide 31

Slide 31 text

あえてupdate を使ってcache を更新してみた const StarBadgeWithMutations = compose( graphql(ADD_STAR_MUTATION, { props: ({ ownProps, mutate }) => ({ addStar: (id: string) => mutate({ variables: { input: { starrableId: id } }, }) }) }), graphql(REMOVE_STAR_MUTATION, { props: ({ ownProps, mutate }) => { return { removeStar(id: string) { mutate({ variables: { input: { starrableId: id } }, update: (store, { data }) => { const queryOption = { query: RELEASES_QUERY, variables: { owner: ownProps.repository.owner.login, name: ownProps.repository.name } }; const cache = store.readQuery(queryOption); cache.repository.viewerHasStarred = data.removeStar.starrable.viewerHasStarred; cache.repository.stargazers.totalCount -= 1; store.writeQuery(Object.assign({}, queryOption, { data: cache })); } }) } } } }) )(StarBadge);

Slide 32

Slide 32 text

大変なのでなるべく Automatic store updates しましょう!

Slide 33

Slide 33 text

https://www.meetup.com/ja­JP/GraphQL­Tokyo/ (多分)来月meetup するので、ご興味があればぜひ!

Slide 34

Slide 34 text

Thank you for your attention!