お題
前回は表題におけるバックエンド部分を実装し、GraphQLのプレイグラウンドでの実行上で動作確認をした。
今回は、フロントエンドでGraphQLクライアントを用いた実装を行う。
ページング機能含むテーブルを持つ画面のデザインはVuetifyのData tablesを採用。
機能
- Searchテキストボックスによる一覧表示レコードの絞り込み(部分一致)
- 「TODO」欄、「Done」欄、「CreatedAt」欄、「User」欄の昇降順ソート
- 一覧表示件数の切り替え(「5件」、「10件」、「15件」、「全件」)
- 前後ページへのページング
イメージとしては↓のような感じ。
◆1ページ目
◆2ページ目
関連記事索引
- 第12回「GraphQLにおけるRelayスタイルによるページング実装再考(Window関数使用版)」
- 第11回「Dataloadersを使ったN+1問題への対応」
- 第10回「GraphQL(gqlgen)エラーハンドリング」
- 第9回「GraphQLにおける認証認可事例(Auth0 RBAC仕立て)」
- 第8回「GraphQL/Nuxt.js(TypeScript/Vuetify/Apollo)/Golang(gqlgen)/Google Cloud Storageの組み合わせで動画ファイルアップロード実装例」
- 第7回「GraphQLにおけるRelayスタイルによるページング実装(後編:フロントエンド)」
- 第6回「GraphQLにおけるRelayスタイルによるページング実装(前編:バックエンド)」
- 第5回「DB接続付きGraphQLサーバ(by Golang)をローカルマシン上でDockerコンテナ起動」
- 第4回「graphql-codegenでフロントエンドをGraphQLスキーマファースト」
- 第3回「go+gqlgenでGraphQLサーバを作る(GORM使ってDB接続)」
- 第2回「NuxtJS(with Apollo)のTypeScript対応」
- 第1回「frontendに「nuxtjs/apollo」、backendに「go+gqlgen」の組み合わせでGraphQLサービスを作る」
開発環境
# OS - Linux(Ubuntu)
$ cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
# フロントエンド
Nuxt.js
$ cat yarn.lock | grep "@nuxt/vue-app"
"@nuxt/vue-app" "2.11.0"
"@nuxt/vue-app@2.11.0":
resolved "https://registry.yarnpkg.com/@nuxt/vue-app/-/vue-app-2.11.0.tgz#05aa5fd7cc69bcf6a763b89c51df3bd27b58869e"
パッケージマネージャ - Yarn
$ yarn -v
1.19.2
IDE - WebStorm
WebStorm 2019.3
Build #WS-193.5233.80, built on November 25, 2019
実践
■全ソース
■プロジェクト構成
$ pwd
/home/sky0621/src/github.com/sky0621/study-graphql/src/frontend
$
$ tree -L 3
.
├── apollo
│ └── queries
│ └── todoConnection.gql
├── codegen.yml
├── components
│ └── TodoPaging.vue
├── gql-types.d.ts
├── layouts
│ ├── default.vue
│ └── error.vue
├── nuxt.config.js
├── package.json
├── pages
│ └── index.vue
├── plugins
│ └── apollo-error-handler.js
├── tsconfig.json
├── types
│ └── vuetify
│ └── index.d.ts
├── vue-shim.d.ts
└── yarn.lock
■DBの中身
user
テーブル
todo
テーブル
■ソース解説
GraphQLスキーマに合わせた型定義ファイル自動生成
やり方は以下参照。
「graphql-codegenでフロントエンドをGraphQLスキーマファースト」
コマンド実行と型定義ファイル自動生成による差分
$ npm run codegen
> frontend@1.0.0 codegen /home/sky0621/src/github.com/sky0621/study-graphql/src/frontend
> graphql-codegen
✔ Parse configuration
✔ Generate outputs
╭────────────────────────────────────────────────────────────────╮
│ │
│ New patch version of npm available! 6.13.1 → 6.13.7 │
│ Changelog: https://github.com/npm/cli/releases/tag/v6.13.7 │
│ Run npm install -g npm to update! │
│ │
╰────────────────────────────────────────────────────────────────╯
$
$ git statusOn branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: gql-types.d.ts
no changes added to commit (use "git add" and/or "git commit -a")
$
$ git diff
diff --git a/src/frontend/gql-types.d.ts b/src/frontend/gql-types.d.ts
index 36495a0..8db10de 100644
--- a/src/frontend/gql-types.d.ts
+++ b/src/frontend/gql-types.d.ts
@@ -6,15 +6,73 @@ export type Scalars = {
Boolean: boolean,
Int: number,
Float: number,
+ /** カーソル(1レコードをユニークに特定する識別子) */
+ Cursor: any,
};
+/** 前ページ遷移条件 */
+export type BackwardPagination = {
+ /** 取得件数 */
+ last: Scalars['Int'],
+ /** 取得対象識別用カーソル(※前ページ遷移時にこのカーソルよりも前にあるレコードが取得対象) */
+ before?: Maybe<Scalars['Cursor']>,
+};
+
+/** ページングを伴う結果返却用 */
+export type Connection = {
+ /** ページ情報 */
+ pageInfo: PageInfo,
+ /** 結果一覧(※カーソル情報を含む) */
+ edges: Array<Edge>,
+ /** 検索結果の全件数 */
+ totalCount: Scalars['Int'],
+};
+
+
+/** 検索結果一覧(※カーソル情報を含む) */
+export type Edge = {
+ /** Nodeインタフェースを実装したtypeなら代入可能 */
+ node?: Maybe<Node>,
+ cursor: Scalars['Cursor'],
+};
+
+/** 並び替え条件 */
+export type EdgeOrder = {
+ /** 並べ替えキー項目 */
+ key: OrderKey,
+ /** ソート方向 */
+ direction: OrderDirection,
+};
+
+/** 次ページ遷移条件 */
+export type ForwardPagination = {
+ /** 取得件数 */
+ first: Scalars['Int'],
+ /** 取得対象識別用カーソル(※次ページ遷移時にこのカーソルよりも後ろにあるレコードが取得対象) */
+ after?: Maybe<Scalars['Cursor']>,
+};
+
+/** マッチングパターン種別(※要件次第で「前方一致」や「後方一致」も追加) */
+export enum MatchingPattern {
+ /** 部分一致 */
+ PartialMatch = 'PARTIAL_MATCH',
+ /** 完全一致 */
+ ExactMatch = 'EXACT_MATCH'
+}
+
export type Mutation = {
__typename?: 'Mutation',
+ noop?: Maybe<NoopPayload>,
createTodo: Scalars['ID'],
createUser: Scalars['ID'],
};
+export type MutationNoopArgs = {
+ input?: Maybe<NoopInput>
+};
+
+
export type MutationCreateTodoArgs = {
input: NewTodo
};
@@ -33,32 +91,150 @@ export type NewUser = {
name: Scalars['String'],
};
+export type Node = {
+ id: Scalars['ID'],
+};
+
+export type NoopInput = {
+ clientMutationId?: Maybe<Scalars['String']>,
+};
+
+export type NoopPayload = {
+ __typename?: 'NoopPayload',
+ clientMutationId?: Maybe<Scalars['String']>,
+};
+
+/** 並べ替え方向 */
+export enum OrderDirection {
+ /** 昇順 */
+ Asc = 'ASC',
+ /** 降順 */
+ Desc = 'DESC'
+}
+
+/**
+ * 並べ替えのキー
+ * 汎用的な構造にしたいが以下はGraphQLの仕様として不可だった。
+ * ・enum・・・汎化機能がない。
+ * ・interface・・・inputには実装機能がない。
+ * ・union・・・inputでは要素に持てない。
+ * とはいえ、並べ替えも共通の仕組みとして提供したく、結果として機能毎に enum フィールドを列挙
+ */
+export type OrderKey = {
+ /** TODO一覧の並べ替えキー */
+ todoOrderKey?: Maybe<TodoOrderKey>,
+};
+
+/** ページング条件 */
+export type PageCondition = {
+ /** 前ページ遷移条件 */
+ backward?: Maybe<BackwardPagination>,
+ /** 次ページ遷移条件 */
+ forward?: Maybe<ForwardPagination>,
+ /** 現在ページ番号(今回のページング実行前の時点のもの) */
+ nowPageNo: Scalars['Int'],
+ /** 1ページ表示件数 */
+ initialLimit?: Maybe<Scalars['Int']>,
+};
+
+/** ページ情報 */
+export type PageInfo = {
+ __typename?: 'PageInfo',
+ /** 次ページ有無 */
+ hasNextPage: Scalars['Boolean'],
+ /** 前ページ有無 */
+ hasPreviousPage: Scalars['Boolean'],
+ /** 当該ページの1レコード目 */
+ startCursor: Scalars['Cursor'],
+ /** 当該ページの最終レコード */
+ endCursor: Scalars['Cursor'],
+};
+
export type Query = {
__typename?: 'Query',
+ node?: Maybe<Node>,
todos: Array<Todo>,
todo: Todo,
+ /** Relay準拠ページング対応検索によるTODO一覧取得 */
+ todoConnection?: Maybe<TodoConnection>,
users: Array<User>,
user: User,
};
+export type QueryNodeArgs = {
+ id: Scalars['ID']
+};
+
+
export type QueryTodoArgs = {
id: Scalars['ID']
};
+export type QueryTodoConnectionArgs = {
+ filterWord?: Maybe<TextFilterCondition>,
+ pageCondition?: Maybe<PageCondition>,
+ edgeOrder?: Maybe<EdgeOrder>
+};
+
+
export type QueryUserArgs = {
id: Scalars['ID']
};
-export type Todo = {
+/** 文字列フィルタ条件 */
+export type TextFilterCondition = {
+ /** フィルタ文字列 */
+ filterWord: Scalars['String'],
+ /** マッチングパターン(※オプション。指定無しの場合は「部分一致」となる。) */
+ matchingPattern?: Maybe<MatchingPattern>,
+};
+
+export type Todo = Node & {
__typename?: 'Todo',
+ /** ID */
id: Scalars['ID'],
+ /** TODO */
text: Scalars['String'],
+ /** 済みフラグ */
done: Scalars['Boolean'],
+ /** 作成日時 */
+ createdAt: Scalars['Int'],
+ /** ユーザー情報 */
user: User,
};
+/** ページングを伴う結果返却用 */
+export type TodoConnection = Connection & {
+ __typename?: 'TodoConnection',
+ /** ページ情報 */
+ pageInfo: PageInfo,
+ /** 検索結果一覧(※カーソル情報を含む) */
+ edges: Array<TodoEdge>,
+ /** 検索結果の全件数 */
+ totalCount: Scalars['Int'],
+};
+
+/** 検索結果一覧(※カーソル情報を含む) */
+export type TodoEdge = Edge & {
+ __typename?: 'TodoEdge',
+ node?: Maybe<Todo>,
+ cursor: Scalars['Cursor'],
+};
+
+/** TODO並べ替えキー */
+export enum TodoOrderKey {
+ /** TODO */
+ Text = 'TEXT',
+ /** 済みフラグ */
+ Done = 'DONE',
+ /** 作成日時(初期表示時のデフォルト) */
+ CreatedAt = 'CREATED_AT',
+ /** ユーザー名 */
+ UserName = 'USER_NAME'
+}
+
export type User = {
__typename?: 'User',
id: Scalars['ID'],
GraphQLクエリ
GraphQLサーバ側のスキーマが↓のようになっている。
https://qiita.com/sky0621/items/1e8823200633f2c46013#graphqlスキーマ-1
なので、クエリは、こうなる。
query todoConnection(
$filterWord: TextFilterCondition
$pageCondition: PageCondition
$edgeOrder: EdgeOrder
) {
todoConnection(
filterWord: $filterWord
pageCondition: $pageCondition
edgeOrder: $edgeOrder
) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
node {
id
text
done
createdAt
user {
id
name
}
}
cursor
}
totalCount
}
}
ページ
ページング実装を施した一覧を表示するページ。
ここは単純にコンポーネント「TodoPaging
」を呼ぶだけ。
<template>
<div>
<TodoPaging />
</div>
</template>
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import TodoPaging from '~/components/TodoPaging.vue'
@Component({
components: { TodoPaging }
})
export default class IndexPage extends Vue {}
</script>
コンポーネント
初期段階
さて、今回の主役である「TodoPaging
」コンポーネント。
ただ、いきなりすべて実装しきってから提示すると、若干わかりづらいものになる気がするので、段階を経てみることにする。
まずは、以下の部分だけ実装したコンポーネントを作ってみる。
・「文字列フィルタ」テキストボックス
・ヘッダだけ定義して、あとはデフォルト設定の空データのv-data-table
ソースは下記のような感じ。
v-data-table
は標準でいろいろ付いているので今回レベルで言うとヘッダを定義するコードを書くだけでいい。
<template>
<v-form>
<!-- 「文字列フィルタ」テキストボックス表示エリア -->
<v-row>
<v-col col="5">
<v-card class="pa-4">
<v-text-field v-model="search" label="Search"></v-text-field>
</v-card>
</v-col>
</v-row>
<!-- ページング込みの一覧テーブル表示エリア -->
<v-row>
<v-col col="9">
<v-card>
<v-data-table :search="search" :headers="headers" fixed-header>
</v-data-table>
</v-card>
</v-col>
</v-row>
</v-form>
</template>
<script lang="ts">
import { Component, Vue } from '@/node_modules/nuxt-property-decorator'
// eslint-disable-next-line no-unused-vars
import { DataTableHeader } from '@/types/vuetify'
// v-data-tableにおけるヘッダーの定義用
class DataTableHeaderImpl implements DataTableHeader {
text: string
value: string
sortable: boolean
width: number
constructor(text: string, value: string, sortable: boolean, width: number) {
this.text = text
this.value = value
this.sortable = sortable
this.width = width
}
}
@Component({})
export default class TodoPaging extends Vue {
// 文字列フィルタ入力値の受け口
private readonly search = ''
// 一覧テーブルのヘッダー表示要素の配列
private readonly headers: DataTableHeader[] = [
new DataTableHeaderImpl('ID', 'id', false, 50),
new DataTableHeaderImpl('TODO', 'text', true, 50),
new DataTableHeaderImpl('Done', 'done', true, 50),
new DataTableHeaderImpl('CreatedAt(UnixTimestamp)', 'createdAt', true, 50),
new DataTableHeaderImpl('User', 'user.name', false, 50)
]
}
</script>
第2段階
次は、GraphQLサーバからTODO一覧を取得してv-data-table
に表示する。
ただし、この段階では、ページングや文字列フィルタによる絞り込み機能等は入れず、単純に全件を取得する。
ソースは下記のような感じ。
<template>
<v-form>
<!-- 「文字列フィルタ」テキストボックス表示エリア -->
〜〜省略〜〜
<!-- ページング込みの一覧テーブル表示エリア -->
<v-row>
<v-col col="9">
<v-card>
<v-data-table
:search="search"
:headers="headers"
:items="items"
:options.sync="options"
:server-items-length="totalCount"
fixed-header
>
</v-data-table>
〜〜省略〜〜
</template>
<script lang="ts">
import { Component, Vue, Watch } from '~/node_modules/nuxt-property-decorator'
// eslint-disable-next-line no-unused-vars
import { DataTableHeader } from '~/types/vuetify'
import todoConnection from '~/apollo/queries/todoConnection.gql'
// v-data-tableにおけるヘッダーの定義用
〜〜省略〜〜
// v-data-tableにおけるページング・ソート条件値の受け取り用
class DataTableOptions {
public page: number = 1
public itemsPerPage: number = 10
// MEMO: 現状では一度に指定できるソートキーは1つ
public sortBy: Array<string> = []
public sortDesc: Array<boolean> = []
}
@Component({})
export default class TodoPaging extends Vue {
// 文字列フィルタ入力値の受け口
private readonly search = ''
// 一覧テーブルのヘッダー表示要素の配列
〜〜省略〜〜
// 一覧テーブルのデータ(v-data-tableの状態変更をウォッチし、その変更を契機にGraphQLクエリ発行→結果を格納)
// eslint-disable-next-line no-array-constructor
private items = new Array<Node>()
// v-data-tableの状態変更をウォッチするための受け皿
private options = new DataTableOptions()
// ページングに依らない検索条件に合致する総件数を保持
private totalCount: number = 0
// v-data-tableの状態変更をウォッチし、その変更を契機にconnection関数をコール
@Watch('options')
watchOptions() {
this.connection()
}
// Apolloライブラリを使ってGraphQLサーバにクエリ発行
private async connection() {
try {
// $apollo.query()がPromiseを返すのでasync/awaitで受け取り
// まずは、ページング・並べ替え条件等を指定せず、単純にクエリを叩く
const res = await this.$apollo.query({
query: todoConnection
})
if (res && res.data && res.data.todoConnection) {
const conn = res.data.todoConnection
// 一覧表示するデータを抜き出す
// edges [ node {id, text, done, ...} ]
this.items = conn.edges.filter((e) => e.node).map((e) => e.node)
// ページングに依らない検索条件に合致する総件数を保持
this.totalCount = conn.totalCount
} else {
console.log('no result')
}
} catch (e) {
console.log(e)
}
}
}
</script>
最終段階
いよいよ、文字列フィルタ、ページング条件、並べ替え条件にも対応する。
初期表示では、以下のように表示される。
次へボタン押下すると、
2ページ目(18件しかないところを10件ずつ表示しているので、2ページ目=最終ページ)が表示される。
この動き自体は今回の対応前(つまりページングでなく初回に全レコードをフロントで取得済み=つどつどサーバに通信いかない)と同じなので、ちゃんと通信いってるのか確認。
うん、大丈夫なようだ。
続いて、文字列フィルタを試してみる。
残念ながら全フィールドに適用されるフィルタではないのだけど、とりあえず適用先として実装済みな「TODO」欄をターゲットに「taro
」で絞り込んでみる。
期待通り、6件が表示されている。ボタンの活性制御も大丈夫なようだ。
↓ちゃんと検索条件としてリクエストに積まれていた。
文字列フィルタを解除すると、元通り全件を対象とした1ページ目の表示に戻った。
お次は、並べ替え。これも残念ながら全フィールドに適用しているわけではないのだけど、適用先として実装済みな「TODO」欄を昇順にしてみる。
うん、よい。
この並べ替えのままで次のページに遷移してみる。
まあ、当たり前だけど、ちゃんと昇順のまま次のページが表示される。
あとは、1ページあたりの表示件数を変えてみるぐらいかな。5件に変えてみる。
↓うん、並べ替え順も維持されているまま、表示が5件になった。
念の為、次のページにも遷移してみる。
よい。
次へ、次へ、最後まで。
いいね。
念には念を入れ、ここから前のページに戻ってみる。
では、最後。1ページあたりの表示件数を「ALL」に変えてみよう。
〜〜省略〜〜
ソースは下記のような感じ。最終段階なので全量を記載。
↓はテンプレート部分。
<template>
<v-form>
<!-- 「文字列フィルタ」テキストボックス表示エリア -->
<v-row>
<v-col col="5">
<v-card class="pa-4">
<v-text-field v-model="search" label="Search"></v-text-field>
</v-card>
</v-col>
</v-row>
<!-- ページング込みの一覧テーブル表示エリア -->
<v-row>
<v-col col="9">
<v-card>
<v-data-table
:search="search"
:headers="headers"
:items="items"
:options.sync="options"
:server-items-length="totalCount"
fixed-header
>
</v-data-table>
</v-card>
</v-col>
</v-row>
</v-form>
</template>
↓スクリプト部分の開始。各種import文やVueコンポーネントで使うクラス群を定義。
<script lang="ts">
import { Component, Vue, Watch } from '~/node_modules/nuxt-property-decorator'
// eslint-disable-next-line no-unused-vars
import { DataTableHeader } from '~/types/vuetify'
import todoConnection from '~/apollo/queries/todoConnection.gql'
// eslint-disable-next-line no-unused-vars
import { Edge, EdgeOrder, PageCondition } from '~/gql-types'
// v-data-tableにおけるヘッダーの定義用
class DataTableHeaderImpl implements DataTableHeader {
text: string
value: string
sortable: boolean
width: number
constructor(text: string, value: string, sortable: boolean, width: number) {
this.text = text
this.value = value
this.sortable = sortable
this.width = width
}
}
// v-data-tableにおけるページング・ソート条件値の受け取り用
class DataTableOptions {
public page: number = 1
public itemsPerPage: number = 10
// MEMO: 現状では一度に指定できるソートキーは1つ
public sortBy: Array<string> = []
public sortDesc: Array<boolean> = []
}
↓ここからようやくVueコンポーネント自身を表す定義。
@Component({})
export default class TodoPaging extends Vue {
// 文字列フィルタ入力値の受け口
private readonly search = ''
// 一覧テーブルのヘッダー表示要素の配列
private readonly headers: DataTableHeader[] = [
new DataTableHeaderImpl('ID', 'id', false, 50),
new DataTableHeaderImpl('TODO', 'text', true, 50),
new DataTableHeaderImpl('Done', 'done', true, 50),
new DataTableHeaderImpl('CreatedAt(UnixTimestamp)', 'createdAt', true, 50),
new DataTableHeaderImpl('User', 'user.name', false, 50)
]
// 一覧テーブルのデータ(v-data-tableの状態変更をウォッチし、その変更を契機にGraphQLクエリ発行→結果を格納)
// eslint-disable-next-line no-array-constructor
private items = new Array<Node>()
// v-data-tableの状態変更をウォッチするための受け皿
private options = new DataTableOptions()
// ページングに依らない検索条件に合致する総件数を保持
private totalCount: number = 0
// 今回のページの1番目のレコードを表す識別子
private startCursor: string | null = null
// 今回のページの最後のレコードを表す識別子
private endCursor: string | null = null
// 現在のページを表す(これも、GraphQLサーバに渡すパラメータとして必要)
private nowPage: number = 1
↓は、文字列フィルタでの入力状況をウォッチするための定義と、v-data-table
の状態変更をウォッチするための定義。
// 文字列フィルタ欄の入力を監視
@Watch('search')
watchSearchWord() {
this.initPageParam()
this.connection()
}
// v-data-tableの状態変更をウォッチし、その変更を契機にconnection関数をコール
@Watch('options')
watchOptions() {
// MEMO: ソートや1ページあたり表示件数の変更時は「1」が渡される。
if (this.options.page === 1) {
this.initPageParam()
}
this.connection()
}
// 初期表示時やページング条件をクリアしたいタイミングでコールする関数
private initPageParam(): void {
this.nowPage = 1
this.options.page = 1
}
↓Apolloライブラリを使ったGraphQLサーバへのクエリ発行。
// Apolloライブラリを使ってGraphQLサーバにクエリ発行
private async connection() {
try {
// $apollo.query()がPromiseを返すのでasync/awaitで受け取り
const res = await this.$apollo.query({
query: todoConnection,
variables: {
// 文字列フィルタ条件
filterWord: { filterWord: this.search },
// ページング条件
pageCondition: this.createPageCondition(
this.nowPage, // 現在のページ
this.options.page, // 遷移先のページ
this.options.itemsPerPage, // 1ページあたりの表示件数指定
this.startCursor,
this.endCursor
),
// 並び替え条件
edgeOrder: this.createEdgeOrder(
this.options.sortBy,
this.options.sortDesc
)
}
})
if (res && res.data && res.data.todoConnection) {
const conn = res.data.todoConnection
// 一覧表示するデータを抜き出す
// edges [ node {id, text, done, ...} ]
this.items = conn.edges
.filter((e: Edge) => e.node)
.map((e: Edge) => e.node)
// ページングに依らない検索条件に合致する総件数を保持
this.totalCount = conn.totalCount
// v-data-tableのoptions変更に影響する各種ページ情報を保持
const pageInfo = conn.pageInfo
this.startCursor = pageInfo.startCursor
this.endCursor = pageInfo.endCursor
this.nowPage = this.options.page
} else {
console.log('no result')
}
} catch (e) {
console.log(e)
}
}
↓は、GraphQLクエリ発行時のパラメータ生成関数群。
private createPageCondition(
nowPage: number,
nextPage: number,
limit: number,
startCursor: string | null,
endCursor: string | null
): PageCondition {
// 現在のページと遷移指示先のページとの比較によって「次へ(forward)」なのか「前へ(backward)」なのか判別
return {
forward: nowPage < nextPage ? { first: limit, after: endCursor } : null,
backward:
nowPage > nextPage ? { last: limit, before: startCursor } : null,
nowPageNo: nowPage,
initialLimit: limit > 0 ? limit : null
}
}
private createEdgeOrder(
sortBy: Array<string>,
sortDesc: Array<boolean>
): EdgeOrder | null {
if (sortBy && sortDesc) {
// MEMO: 現状では一度に指定できるソートキーは1つ
if (sortBy.length !== 1 || sortDesc.length !== 1) {
return null
}
// TODO: enum値を指定するとビルドが通らなくなるので、やむなく文字列で指定
const direction = sortDesc[0] ? 'DESC' : 'ASC'
switch (sortBy[0]) {
case 'text':
return { key: { todoOrderKey: 'TEXT' }, direction }
case 'done':
return { key: { todoOrderKey: 'DONE' }, direction }
case 'createdAt':
return { key: { todoOrderKey: 'CREATED_AT' }, direction }
}
}
return null
}
}
</script>
まとめ
正直、まだバグはあると思う。たぶん。文字列フィルタとページ遷移と1ページ表示件数の変更などいろいろ組み合わせた時などのケースで。
それにしても、TypeScript難しい。いや、Nuxt.jsとの組み合わせだからなのかな・・・。
あらためまして、今回分の全ソースは下記。
https://github.com/sky0621/study-graphql/tree/v0.8.0