Vue 3 + vue-apollo 4 を組み合わせた場合の使い方を説明します。
vue-apollo 3 と 4 では書き方が大きく変わっていますのでご注意ください。
バージョン
- Vue 3.2.37
- @vue/apollo-composable: 4.0.0-alpha.19
- Vite 3.0.0
環境としては、https://kasyalog.site/blog/vite-vue-vuex-storybook-init/ で作った Vite + Vu3 環境をベースにしています。
セットアップ
vue-apollo は名前の通り、apollo を利用します。ので、まずは @apollo/client
をインストール。
yarn add graphql graphql-tag @apollo/client
インストール後、ApolloClient
のインスタンスを作成します。
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client/core'
// HTTP connection to the API
const httpLink = createHttpLink({
uri: 'https://api.github.com/graphql',
fetch: (uri: RequestInfo, options: RequestInit) => {
const headers = {
Authorization: 'Bearer <Github Token>'
};
options.headers = headers
return fetch(uri, options);
},
})
// Cache implementation
const cache = new InMemoryCache()
// Create the apollo client
const apolloClient = new ApolloClient({
link: httpLink,
cache,
})
export default apolloClient
5行目の uri ではGarphQLのエンドポイントを指定します。ここでは、まずはフロントだけで確認できるよう、Github の GraphQL エンドポイントを指定しています。8行明の <Github Token>
の部分は、https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token に従って、トークンを発行したものを指定すればOKです。
その後、vue apollo をインストールします。
// Comosition API
yarn add @vue/apollo-composable
main.ts
は次のようになります。上記で生成したものを DefaultApolloClient として指定します。
import { createApp, provide, h } from "vue";
import { DefaultApolloClient } from "@vue/apollo-composable";
import apolloClient from "./plugins/apolloclient";
import "./style.css";
import App from "./App.vue";
createApp({
setup() {
provide(DefaultApolloClient, apolloClient);
},
render: () => h(App),
}).mount("#app");
これだけで準備は完了です。
GraphQL のクエリ実行
取得結果の表示(result)
準備ができたので、実際に Github に対してクエリを実行します。クエリを実行する HelloGraphQL.vue を作り、それを App.vue で表示させるようにしています。
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloGraphQL />
</template>
<script setup lang="ts">
import HelloGraphQL from "./components/HelloGraphQL.vue";
</script>
HelloGraphQL.vue は以下になります。長いですが、特に難しいことはしておらず、ここでは、「vuejs」の Github organizations のうち、スター数が多いものから 50件を取得して一覧として表示させています。
<template>
<div v-if="result">
<table :border="1" style="border-collapse: collapse">
<thead>
<tr>
<th>No</th>
<th>name</th>
<th>description</th>
<th>createdAt</th>
<th>star</th>
<th>url</th>
</tr>
</thead>
<tbody>
<tr
v-for="(v, index) in result.organization.repositories.nodes"
:key="v.url"
>
<td>{{ index + 1 }}</td>
<td>{{ v.name }}</td>
<td>{{ v.description }}</td>
<td>{{ new Date(v.createdAt).toLocaleDateString() }}</td>
<td>{{ v.stargazers.totalCount }}</td>
<td>{{ v.url }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import { useQuery } from "@vue/apollo-composable";
import gql from "graphql-tag";
const { result } = useQuery(gql`
query {
organization(login: "vuejs") {
repositories(first: 50, orderBy: { field: STARGAZERS, direction: DESC }) {
nodes {
name
description
createdAt
updatedAt
url
stargazers {
totalCount
}
}
}
}
}
`);
</script>
なお、Github で指定ユーザの一覧を取得したい場合には、query { user(login: <ユーザ名>) { ...} }
になります。organizationの場合は、query { organization(login: <組織名>) { ...} }
です。
Github スター数は stargazers の totalCount で取得でき、これを orderBy することで多い順に取得ができます。
実行すると結果が取得できます。なお、上の通り、useQuery
を記述した時点でリクエストは実行がされます。
簡単に GraphQL を使うことができます。useQuery は result 以外にも、loading(処理中), error(エラー有無) といった実際に開発するときには必須な情報も持っています。
読み込み中(loading), エラー(error)
取得状況やAPIエラー時も簡単に制御できます。loading
は取得中のみ true
になります。error
は200以外の場合です。
以下は、loading
, error
を追加しつつ、エラーになるよう、取得件数を 50から101にします(Github では 101以上を指定するとエラーになります)。
<template>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<div v-else-if="result">
.... 以降は同じため省略
</template>
<script setup lang="ts">
import { useQuery } from "@vue/apollo-composable";
import gql from "graphql-tag";
const { result, loading, error } = useQuery(gql`
query {
organization(login: "vuejs") {
repositories(first: 101, orderBy: { field: STARGAZERS, direction: DESC })
... 以降は同じため省略
}
`);
</script>
一瞬ですが、取得中は「loading…」が表示されます。
エラーメッセージもちゃんと表示されています。
このように、vue-apollo を利用することで、クエリの実行も、読み込み中の判断も、エラー時のハンドリングも簡単に行うことができます。
パラメータの変数化
もちろんですが、通常のアプリケーションでは、クエリに指定するパラメータは動的です。userQuery
の第2引数に指定することでパラメータが渡せます。以下では、select 要素を追加して、クエリに渡す情報を動的に変えるようにしました。画面表示時には初期値が vuejs
なので同じ値が表示されます。
<template>
<select @change="(v) => (variables.login = v.target.value)">
<option>vuejs</option>
<option>facebook</option>
<option>angular</option>
</select>
... 以降は同じ
</template>
<script setup lang="ts">
import { useQuery } from "@vue/apollo-composable";
import gql from "graphql-tag";
import { ref } from "vue";
const variables = ref({
login: "vuejs",
});
const { result, loading, error } = useQuery(
gql`
query ($login: String!) {
organization(login: $login) {
... ここは同じ
}
}
`,
variables
);
</script>
画面表示後、select で値を変えると選択した organization の一覧を取得します(以下は angular の例)
ページネーション
変数化までいけたので、次はページネーションです。実際には利用する GraphQL サービスの実装によりますが、一般的なものは、https://relay.dev/graphql/connections.htm にある Relay Cursor と呼ばれる方式です。Github もこちらです。これに関しては調べたらたくさんでてきます。
やることは大きくは以下の3つです。これらはコード中にコメント記載しています。
- クエリ実行時に cursor 位置を取得するようクエリ修正
- 次を読み込む場合に上の cursor を指定して
fetchMore
を実行 - 次を読み込んだ後は、前に取得した結果をマージする
<template>
...ここまで変更なし
<button @click="loadNext()">追加読み込み</button>
</div>
</template>
<script setup lang="ts">
import { useQuery } from "@vue/apollo-composable";
import gql from "graphql-tag";
import { ref } from "vue";
// パラメータに cursor を指定します。最初は先頭からなので null にします。
const variables = ref({
login: "vuejs",
cursor: null,
});
// 追加読み込み処理を行うために fetchMore を参照できるようにします。
const { result, loading, error, fetchMore } = useQuery(
gql`
// 上の cursor をクエリに指定します。指定した cursor から 50件を取得する動きになります。
query ($login: String!, $cursor: String) {
organization(login: $login) {
repositories(
first: 50
after: $cursor
orderBy: { field: STARGAZERS, direction: DESC }
) {
nodes {
name
description
createdAt
updatedAt
url
stargazers {
totalCount
}
}
// ページ情報を取得します
pageInfo {
endCursor
hasNextPage
}
}
}
}
`,
variables
);
// ボタン押下時の処理です
const loadNext = () => {
// 追加取得をします。variables に指定するのは cursor などページ情報のみでOKです。
// 再度 login を指定する必要はありません。
fetchMore({
variables: {
cursor: result.value.organization.repositories.pageInfo.endCursor,
},
// 取得後のコールバック処理です
// 追加取得前の結果と、今回取得したデータが引き数にあります
updateQuery: (previoiusResult, { fetchMoreResult }) => {
const { nodes: newNodes, pageInfo: newPageInfo } =
fetchMoreResult.organization.repositories;
// キャッシュを更新した結果を返します。上のクエリに記載はありませんが、
// __typename なども含むようにマージしないとエラーになります。
// やっていることは、追加読み込みの一覧を付け足し、pageInfo は最新のものに置き換えをしています
return {
organization: {
...previoiusResult.organization,
repositories: {
...previoiusResult.organization.repositories,
nodes: [
...previoiusResult.organization.repositories.nodes,
...newNodes,
],
pageInfo: newPageInfo,
},
},
};
},
});
};
</script>
上記を実行すると最新の状態(1回追加読み込みすると100件)の一覧が表示されます。
データ取得、エラー時のカスタム処理
追加読み込み時 fetchMore
の updateQuery のように、データ取得時に任意の処理を行うことも可能です。
onResult((queryResult) => {
console.log("データ取得しました");
console.log(queryResult); // 取得できたデータが参照できます
});
onError((queryResult) => {
console.log("取得エラーです");
console.log(queryResult); // エラー情報が参照できます
});
これでクエリの実行に関しては、基本的な部分は実行できるようになったと思います。その他にも vue-apollo には様々な機能があり、例えば 今回は useQuery を行うとすぐに API が実行されましたが、何らかの条件(例:クエリに渡すパラメータを先に取得する必要がある)があり、それを満たした後に実行させる、ということもオプションを使うと可能です。
思ったより長くなったので mutation は別の記事として記載したいと思います。
コメント