トップ/記事一覧

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

📆May 04, 2020🔖 NuxtJSVue.js

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();
    })