VueとGraphQLの

アプリケーション開発について

Daiki Urata (Fusic Co.,Ltd.)
Twitter: @daiki7nohe

話すこと

  • GraphQLについて

  • Apolloについて

  • Apollo ClientのCache

  • Vue + GraphQLのLocal State Management

GraphQLについて

  • API用のクエリ言語

  • 一つのエンドポイント

  • 型がある

  • 受け取り側でスキーマを定義

  • 要求側でQueryを投げてデータ取得

  • サーバーへの変更はMutation

Schema (受け取り側)

type Query {
  todos: [Todo]
}

type Post {
  id: ID!
  title: String
  body: String
}

Query (要求側)

query GetPosts {
  posts {
    id
    title
  }
}

Mutation (要求側)

mutation CreateTodo ($title: String, $body: String) {
  createTodo (title: $title, body: $body) {
    id
  }
}

Apolloについて

  • GraphQLのプラットフォーム

  • Apollo Server, Apollo Clientなどのオープンソース

  • Schema registryなどのクラウドサービス

  • その他IDEプラグインやブラウザ拡張ツールなど

Apollo Client

  • GraphQLクライアント

  • react-apollo, apollo-angular, vue-apollo

  • キャッシュされる

  • Vue CLIならvue-cli-plugin-apolloが簡単

// apollo.js
import { ApolloClient } from "apollo-client";
const apolloClient = new ApolloClient({ ... })

export default apolloClient;
// main.js

import VueApollo from "vue-apollo";
import apolloClient from "./apollo";

Vue.use(VueApollo);

const apolloProvider = new VueApollo({
  defaultClient: apolloClient
});

new Vue({
  apolloProvider,
  render: h => h(App)
}).$mount("#app");

vue-apollo (apolloオプション)

import gql from "graphql-tag";
export default {
  apollo: {
    todos: {
      query: gql`
       query getTodos {
          todos {
            id
            title
            body
          }
        }
      `
    }
  },
  data () {
    return {
      todos: []
    }
  }
}

vue-apollo ($apollo)

export default {
  created () {
    this.$apollo.query({
      query: gql`
        query getTodos {
          todos {
            id
            title
            body
          }
        }
      `
    })
    .then(result => {
      // Your code here...
    })
  }
}

vue-apollo (ApolloQueryコンポーネント)

<ApolloQuery
  :query="require('../graphql/GetTodos.gql')"
>
  <template slot-scope="{ result: { loading, error, data } }">
    <!-- Loading -->
    <div v-if="loading">Loading...</div>
    <!-- Error -->
    <div v-else-if="error">An error occured</div>
    <!-- Result -->
    <ul v-else-if="data">
      <li v-for="todo in data.todos" :key="todo.id">
        Title: {{ todo.title }}, Body: {{ todo.body }}
      </li>
    </ul>
    <!-- No result -->
    <div v-else>No result :(</div>
  </template>
</ApolloQuery>

Apollo ClientのCache

  • 一度投げたクエリ結果はキャッシュされる

  • クエリに変更があればサーバーへリクエスト

  • fetchPolicyで変えられる

Cacheの有効化

$ npm install apollo-cache-inmemory
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { ApolloClient } from 'apollo-client';

const cache = new InMemoryCache();

const client = new ApolloClient({
  link: new HttpLink(),
  cache
});

DEMO

fetchPolicy

  • cache-first : クエリ結果をキャッシュし、その後はキャッシュを使う

  • cache-and-network : キャッシュも使いつつ、サーバーへはリクエスト

  • network-only : キャッシュなし。必ずサーバーへリクエスト

  • cache-only : キャッシュだけにアクセス

  • no-cache : network-only とほぼ同じだが、クエリ結果すらキャッシュに残さない

便利だなー :blush:

状態の二重管理では? :thinking:

  • ある程度大規模になるとVuexで状態管理したい

  • キャッシュデータをまたVuexで?

  • Vuexからキャッシュへわざわざアクセス?

キャッシュだけで状態管理は?

Apollo Clientでの状態管理

  • Apolloのキャッシュでローカルの状態も一括管理

  • 元々は apollo-link-state (今はdeprecated)

  • Apollo Client >= 2.5ではそのままでOK

キャッシュへの書き込み方法1

import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';

const cache = new InMemoryCache();
const client = new ApolloClient({
  cache,
  resolvers: {
    Mutation: {
      setSelectedId(_root, { id }, { cache }) {
        cache.writeData({ data: { selectedId: id } });
        return null;
      }
    }
  },
});

// 初期値をセット
cache.writeData({
  data: {
    selectedId: 1
  }
});

キャッシュへの書き込み方法1

this.$apollo.mutate({
  mutation: gql`
    mutation SetSelectedId($id: ID) {
      setSelectedId(id: $id) @client
    }
  `,
  variables: {
    id
  }
});

キャッシュへの書き込み方法2

this.$apollo.getClient().writeQuery({
  query: gql`
    {
      selectedId
    }
  `,
  data: {
    selectedId: id
  }
});

キャッシュから状態取得方法1

export default {
  apollo: {
    selectedId: {
      query: gql`
        query GetSelectedID {
          selectedId @client
        }
      `
    }
  },
  data() {
    return {
      selectedId: null
    };
  },
}

キャッシュから状態取得方法2

this.$apollo.query({
  query: gql`
    query GetSelectedID {
      selectedId @client
    }
  `
}).then(result => {
  this.todos = result.data.todos
});

キャッシュから状態取得方法3

this.todos = this.$apollo.getClient().readQuery({
  query: gql`
    query GetSelectedID {
      selectedId @client
    }
  `
});

DEMO

悲しい部分

Local Stateのスキーマは定義できるがバリデーションまではしてくれない

new ApolloClient({
  typeDefs: gql`
      extend type Query {
        selectedId: Boolean! // <- エラーにならない
      }
    `
})

まとめ

  • Apolloキャッシュ便利

  • ただ状態管理は色々と考える部分あり

参考