Skip to content

Instantly share code, notes, and snippets.

@tagty
Last active December 1, 2023 17:07
Show Gist options
  • Save tagty/18c2f7ba437af009f8a907fa52d858c7 to your computer and use it in GitHub Desktop.
Save tagty/18c2f7ba437af009f8a907fa52d858c7 to your computer and use it in GitHub Desktop.
ApolloClientとはなにか?どのように使われるのか?

課題

  • ApolloClient とはなにか?

    • どのように使うのか?
    • どのような要素で構成されるのか?
  • ApolloClient はどのように使われるのか?

主張

  • ApolloClient は以下のように使う

    • uricache などをオプションに渡して使う
  • ApolloClient は以下ような要素で構成される

    • Link
    • Cache
    • LocalState
    • QueryManager
  • ApolloClient はクエリをを実行するのに使われる

    • gql を使って生成したASTを query に渡す
    • queryQueryManager を利用する

論証

Introduction to Apollo Client

スクリーンショット 2021-04-15 7 54 45

Get started

スクリーンショット 2021-04-16 9 23 45

Create a client

import { ApolloClient, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  uri: 'https://48p1r2roz4.sse.codesandbox.io',
  cache: new InMemoryCache()
});

Apollo Client

スクリーンショット 2021-04-16 9 37 36

src/core/ApolloClient.ts

  • GraphQL Document(QueryとMutation)をGraphQLサーバーに送信

  • GraphQLサーバーからのレスポンスをストアにcache

  • uri

    • Apollo Clientが接続するGraphQLのエンドポイント
  • cache

    • ストアに利用されるcache
export class ApolloClient<TCacheShape> implements DataProxy {
  constructor(options: ApolloClientOptions<TCacheShape>) {
    const {
      uri,
      cache,

      // ...

    } = options;
  }

  // ...

  if (!link) {
    link = uri
      ? new HttpLink({ uri, credentials, headers })
      : ApolloLink.empty();
  }

  // ...

  this.link = link;
  this.cache = cache;

  // ...

  this.localState = new LocalState({
    cache,
    client: this,

    //...

  });

  this.queryManager = new QueryManager({
    cache: this.cache,
    link: this.link,

    // ...

    localState: this.localState,

    // ...

  });
}

Link

src/core/ApolloClient.ts

export class ApolloClient<TCacheShape> implements DataProxy {
  constructor(options: ApolloClientOptions<TCacheShape>) {
    const {
      uri,

      // ...

    } = options;
  }

  // ...

  if (!link) {
    link = uri
      ? new HttpLink({ uri, credentials, headers })
      : ApolloLink.empty();
  }

  // ...

  });
}

src/link/http/HttpLink.ts

export class HttpLink extends ApolloLink {
  public requester: RequestHandler;
  constructor(public options: HttpOptions = {}) {
    super(createHttpLink(options).request);
  }
}

src/link/core/ApolloLink.ts

export class ApolloLink {

  // ...

  constructor(request?: RequestHandler) {
    if (request) this.request = request;
  }

  // ...

}

Apollo Link overview

スクリーンショット 2021-04-16 9 51 06

  • ApolloクライアントとGraphQLサーバー間のデータフローをカスタマイズ
  • クライアントのネットワークのふるまいをLinkオブジェクトのチェーンとして定義可能
  • Headerの変更やログの記録など

スクリーンショット 2021-04-12 9 21 33

    • 1.デバッグのためのログの記録

    • 2.認証のためのHTTPヘッダーを追加

    • 3.終端のリンク

      • HTTP経由でGraphQLサーバーなどにリクエスト
    • 4.レスポンスは各リンクに逆の順序で受け渡される

      • データがcacheされる前に、レスポンスの変更や他のアクションの実行も可能
  • デフォルトでは、ApolloLinkのHttpLinkを使用

    • HTTP経由でGraphQLの操作をサーバーにリクエスト
    • 多くの場合はHttpLinkで対応可能
  • 拡張や置換をするには

    • カスタムリンクを定義
    • ApolloClientのconstructorで実行順序を指定

src/core/ApolloClient.ts

export class ApolloClient<TCacheShape> implements DataProxy {
  constructor(options: ApolloClientOptions<TCacheShape>) {
    const {
      uri,

      // ...

    } = options;
  }

  // ...

  if (!link) {
    link = uri
      ? new HttpLink({ uri, credentials, headers })
      : ApolloLink.empty();
  }

  // ...

}

src/link/http/HttpLink.ts

export class HttpLink extends ApolloLink {
  public requester: RequestHandler;
  constructor(public options: HttpOptions = {}) {
    super(createHttpLink(options).request);
  }
}

src/link/core/ApolloLink.ts

export class ApolloLink {

  // ...

  constructor(request?: RequestHandler) {
    if (request) this.request = request;
  }

  // ...

}

src/link/http/createHttpLink.ts

export const createHttpLink = (linkOptions: HttpOptions = {}) => {
  let {
    uri = '/graphql',

    // ...

  } = linkOptions;
}

src/link/http/__tests__/HttpLink.ts

it('supports using a GET request', done => {
  const variables = { params: 'stub' };
  const extensions = { myExtension: 'foo' };

  const link = createHttpLink({
    uri: '/data',
    fetchOptions: { method: 'GET' },
    includeExtensions: true,
    includeUnusedVariables: true,
  });

  execute(link, { query: sampleQuery, variables, extensions }).subscribe({
    next: makeCallback(done, () => {
      const [uri, options] = fetchMock.lastCall()!;
      const { method, body } = options!;
      expect(body).toBeUndefined();
      expect(method).toBe('GET');
      expect(uri).toBe(
        '/data?query=query%20SampleQuery%20%7B%0A%20%20stub%20%7B%0A%20%20%20%20id%0A%20%20%7D%0A%7D%0A&operationName=SampleQuery&variables=%7B%22params%22%3A%22stub%22%7D&extensions=%7B%22myExtension%22%3A%22foo%22%7D',
      );
    }),
    error: error => done.fail(error),
  });
});

LocalState

src/core/ApolloClient.ts

export class ApolloClient<TCacheShape> implements DataProxy {
  constructor(options: ApolloClientOptions<TCacheShape>) {
    const {

      // ...

      cache,

      // ...

    } = options;
  }

  // ...

  this.localState = new LocalState({
    cache,
    client: this,

    //...

  });

  // ...

}

Managing local state

How it works

  • localのstateは、任意の方法でストアが可能

    • localStorage やApollo Client cache
  • 特定のフィールドにクエリを実行するとき

    • localのデータをfetch、上書きするロジックを指定
  • localのフィールドとremoteからfetchされたフィールドの両方を一つのQueryに含めることが可能

スクリーンショット 2021-04-13 8 48 01

src/core/ApolloClient.ts

export class ApolloClient<TCacheShape> implements DataProxy {
  constructor(options: ApolloClientOptions<TCacheShape>) {
    const {

      // ...

      cache,

      // ...

    } = options;
  }

  // ...

  this.cache = cache;

  // ...

  this.localState = new LocalState({
    cache,
    client: this,

    //...

  });

  // ...

}

src/core/LocalState.ts

export class LocalState<TCacheShape> {
  constructor({
    cache,
    client,

    // ...

  }: LocalStateOptions<TCacheShape>) {
    this.cache = cache;

    if (client) {
      this.client = client;
    }
  }
}

QueryManager

src/core/ApolloClient.ts

export class ApolloClient<TCacheShape> implements DataProxy {
  constructor(options: ApolloClientOptions<TCacheShape>) {
    const {
      uri,
      cache,

      // ...

    } = options;
  }

  // ...

  if (!link) {
    link = uri
      ? new HttpLink({ uri, credentials, headers })
      : ApolloLink.empty();
  }

  // ...

  this.link = link;
  this.cache = cache;

  // ...

  this.localState = new LocalState({
    cache,
    client: this,

    //...

  });

  this.queryManager = new QueryManager({
    cache: this.cache,
    link: this.link,

    // ...

    localState: this.localState,

    // ...    

  });
}

src/core/QueryManager.ts

export class QueryManager<TStore> {
  constructor({
    cache,
    link,

    // ...

    localState,

    // ...

  }: {
    cache: ApolloCache<TStore>;
    link: ApolloLink;

    // ...

    localState?: LocalState<TStore>;

    // ...

  }) {
    this.cache = cache;
    this.link = link;

    // ...

    this.localState = localState || new LocalState({ cache });

    // ...

  }
}

Create a client

import { gql } from '@apollo/client';

// const client = ...

const query = gql`
  query GetRates {
    rates(currency: "USD") {
      currency
    }
  }
`;

client.query({ query: query }).then((result) => console.log(result));
  • gql
  • client.query()

graphql-tag

import gql from 'graphql-tag';

const query = gql`
  {
    user(id: 5) {
      firstName
      lastName
    }
  }
`
  • 生成されるsyntax tree
{
  "kind": "Document",
  "definitions": [
    {
      "kind": "OperationDefinition",
      "operation": "query",
      "name": null,
      "variableDefinitions": null,
      "directives": [],
      "selectionSet": {
        "kind": "SelectionSet",
        "selections": [
          {
            "kind": "Field",
            "alias": null,
            "name": {
              "kind": "Name",
              "value": "user",
              ...
            }
          }
        ]
      }
    }
  ]
}
import { gql } from "@apollo/client";

const query = gql`
  query GetRates {
    rates(currency: "USD") {
      currency
    }
  }
`;

console.log(query);

スクリーンショット 2021-04-11 16 23 12

import { gql } from '@apollo/client';

// const client = ...

const query = gql`
  query GetRates {
    rates(currency: "USD") {
      currency
    }
  }
`;

client.query({ query: query }).then((result) => console.log(result));

src/core/ApolloClient.ts

public query<T = any, TVariables = OperationVariables>(
  options: QueryOptions<TVariables, T>,
): Promise<ApolloQueryResult<T>> {

  // ...

  return this.queryManager.query<T, TVariables>(options);
}
export class ApolloClient<TCacheShape> implements DataProxy {

  //...


  this.queryManager = new QueryManager({
    cache: this.cache,
    link: this.link,

    // ...

    localState: this.localState,

    // ...    

  });
}

src/core/QueryManager.ts

public query<TData, TVars = OperationVariables>(
  options: QueryOptions<TVars, TData>,
): Promise<ApolloQueryResult<TData>> {

  // ...

  const queryId = this.generateQueryId();
  return this.fetchQuery<TData, TVars>(
    queryId,
    options,
  ).finally(() => this.stopQuery(queryId));
}
  • 単一のQueryを実行
  • Promiseをreturn

スクリーンショット 2021-04-15 9 18 06

結論

  • ApolloClient は以下のように使う

    • uricache などをオプションに渡して使う
  • ApolloClient は以下ような要素で構成される

    • Link
    • Cache
    • LocalState
    • QueryManager
  • ApolloClient はクエリをを実行するのに使われる

    • gql を使って生成したASTを query に渡す
    • queryQueryManager を利用する
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment