トップ/記事一覧
Nuxt.js + Vuex におけるフォームバリデーションのベストプラクティスを考える
⚠ この記事は最終更新日から1年以上が経過しています。内容が古い箇所がある可能性があるためご注意ください。
Nuxt.js + Vuex でフォームバリデーションを実装する機会が増えたので、ベストプラクティスについて考えてみました。いろいろな実現方法があるとは思いますが、自分なりの答えが出たので、記事にしておきたいと思います。VeeValidator 等のライブラリ等は使わない前提です。
Action 層でバリデーションをかける
バリデーションの判定は Action 層で行います。Page 層にはロジックは書かない方針で一貫した設計をしているため、Page 層にはバリデーションロジックは書きません。
Action は、以下のようコードになります。
JavaScript
async createTodo({ dispatch }, form: TodoForm): Promise<void> { assertTodo(form); // 第二引数で渡ってくる form の妥当性をチェック // DO SOMETHING // form -> model // API 通信 // commit 等 }
Vuex では dispatch したときの第二引数としてオブジェクトを渡すことができるので、このオブジェクトに対し、バリデーション判定を行います。具体的なロジックについては割愛します。
Action 層の責務としては、以下の2つになります。
図にすると、以下のような形になります。
バリデーションロジックでは、独自のバリデーションエラーを作って throw する
バリデーションにひっかかった場合(👆の例でいう assertTodo で判定して、ひっかかった場合)には、独自のエラーを発生させ、それを投げることとしています。Error を拡張した独自のエラー ValidationError を自前で定義し、フィールドとして、エラーの内容を詰めるようにしています。
以下は、僕が定義している独自エラーの例です。
JavaScript
export class ValidationError extends Error { errors: Array<ValidationErrorObj>; constructor(errors: Array<ValidationErrorObj>, message: string = 'ValidationError') { super(message); this.errors = errors this.name = 'ValidationError'; } }
バリデーションエラーに引っかかった場合には、以下のようにエラーを throw するようにしています。errors には、エラーに引っかかった要素名と、エラー文言を詰めたオブジェクトの配列を渡すようにしています。
JavaScript
throw new ValidationError(errors);
Page 層でのバリデーションエラーハンドリング
Page から Action の呼び出しは dispatch で行います。dispatch の返り値は Promise なので、例外が投げられた場合の処理を catch ブロックにまとめることが出来ます。
エラーのクラスを instanceof で判定してやって、バリデーションエラーだった場合には、エラーメッセージを出す等の処理を行います。また、想定していない例外(たとえば、API 通信で発生したネットワークエラー等)の場合は、そのまま例外を throw して、Sentry 等に処理を委ねることにしています。
以下はその例です。await/catch で書くのは好み。
JavaScript
const res = await this.$store.dispatch('todo/createTodo', this.form) .catch((e) => { if (e instanceof ValidationError) { this.errors = e.errors; } else { throw e; } });
errors フィールドに、エラーの内容を詰めているので、Page 側で、エラーの内容を表示させたりすることができます。ここはアラート出すなり、ビューに表示するなり、お好みでどうぞ。
ちなみに、 Jest を使ったバリデーションのテストは以下のようになります。Action 単位でテストするのが好みなので、こんな感じで。
JavaScript
await store.dispatch('createTodo', form).catch((e: any) => { expect(e instanceof ValidationError).toBeTruthy(); })