Nuxt.js + Vuex におけるフォームバリデーションのベストプラクティスを考える

📆May 04, 2020

Nuxt.js + Vuex でフォームバリデーションを実装する機会が増えたので、ベストプラクティスについて考えてみました。いろいろな実現方法があるとは思いますが、自分なりの答えが出たので、記事にしておきたいと思います。VeeValidator 等のライブラリ等は使わない前提です。

Action 層でバリデーションをかける

バリデーションの判定は Action 層で行います。Page 層にはロジックは書かない方針で一貫した設計をしているため、Page 層にはバリデーションロジックは書きません。

Action は、以下のようコードになります。

async createTodo({ dispatch }, form: TodoForm): Promise<void> {
    assertTodo(form); // 第二引数で渡ってくる form の妥当性をチェック
		
    // DO SOMETHING
    // form -> model
    // API 通信
    // commit 等
}

Vuex では dispatch したときの第二引数としてオブジェクトを渡すことができるので、このオブジェクトに対し、バリデーション判定を行います。具体的なロジックについては割愛します。

Action 層の責務としては、以下の2つになります。

  • 引数として渡ってきた Form オブジェクトの妥当性をチェックする
  • Form オブジェクトを API 通信に適した型に変換し、処理を行う

図にすると、以下のような形になります。

バリデーションロジックでは、独自のバリデーションエラーを作って throw する

バリデーションにひっかかった場合(👆の例でいう assertTodo で判定して、ひっかかった場合)には、独自のエラーを発生させ、それを投げることとしています。Error を拡張した独自のエラー ValidationError を自前で定義し、フィールドとして、エラーの内容を詰めるようにしています。

以下は、僕が定義している独自エラーの例です。

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 には、エラーに引っかかった要素名と、エラー文言を詰めたオブジェクトの配列を渡すようにしています。

throw new ValidationError(errors);

Page 層でのバリデーションエラーハンドリング

Page から Action の呼び出しは dispatch で行います。dispatch の返り値は Promise なので、例外が投げられた場合の処理を catch ブロックにまとめることが出来ます。

エラーのクラスを instanceof で判定してやって、バリデーションエラーだった場合には、エラーメッセージを出す等の処理を行います。また、想定していない例外(たとえば、API 通信で発生したネットワークエラー等)の場合は、そのまま例外を throw して、Sentry 等に処理を委ねることにしています。

以下はその例です。await/catch で書くのは好み。

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 単位でテストするのが好みなので、こんな感じで。

await store.dispatch('createTodo', form).catch((e: any) => {
  expect(e instanceof ValidationError).toBeTruthy();
})


🔖 NuxtJSVue.js