8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Vue】Element-UIでフォーム全体のバリデーションを行う【TypeScript】

Last updated at Posted at 2019-05-12

これまたすんなり行かなかったので共有しとく

#その1 - Elementフォームのvalidateメソッドを参照したい
フォーム全体のバリデーションを行う方法については、Element-UI公式でCustom validation rulesのサンプルコードを丹念に読めば大体のところが把握できる。
が、TypeScriptでそのまま書こうとするとVSCodeに怒られてしまう。

サンプルコード(抜粋)
this.$refs[formName].validate(callback);
VSCodeでのエラーメッセージ
Property 'validate' does not exist on type 'Vue | Element | Vue[] | Element[]'.
  Property 'validate' does not exist on type 'Vue'.Vetur(2339)

$refsの定義を確認すると

node_modules/vue/types/vue.d.ts(抜粋)
export interface Vue {
  readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
}

となっているが、実際に$refs下に登録されているのはElementUIComponent経由でVueを継承したElFormであり、そこに定義されているvalidateメソッドはVueからは参照できない。

#その1の解決法 - $refsを宣言し直す
以下、Vue.js公式のTypeScript SupportにあるClass-Style Vue Componentsを前提に進める。

まずElFormの定義を取り込む。

任意のVueファイル(抜粋)
<script lang="ts">

import { ElForm } from 'element-ui/types/form';

次に、インスタンスプロパティ$refsについて下記のように宣言。

任意のVueファイル(抜粋)
export default class MyComponent extends Vue {
  /* data */
  public $refs!: { [key: string]: ElForm };

定義を被せる都合上、$refsプロパティはVueに合わせてpublicで宣言する必要がある。TypeScriptでは、public修飾子(デフォルト)は省略可だが、筆者の趣味で明示的に記述しておいた。

プロパティ名の後ろに付く!Definite Assignment Assertionsというモノ。
本来であれば宣言時もしくはコンストラクタ内で初期化する必要のあるインスタンスプロパティが、コレを付けると「既に代入済みであり初期化不要」という扱いになる。このように記述した場合、当該のプロパティが$dataオブジェクトに取り込まれることもない。

$refsへ登録されるフォーム名はel-formタグのref属性で指定するが、名称が固定であれば以下のように宣言しても構わない。

el-formタグ
<el-form ref="myForm" :rules="myRules" :model="myModel">
フォーム名が固定の場合
export default class MyComponent extends Vue {
  /* data */
  public $refs!: { myForm: ElForm };

以上で、サンプルコードと同じような記述が可能となる。

記述例
this.$refs[formName].validate(callback);
/* あるいは */
this.$refs.myForm.validate(callback);

#その2 - バリデーション結果を画面に反映させたい
フォーム全体のバリデーション結果を、リアルタイムで画面上(ボタンの使用可否など)に反映させたい、というような欲求はあるかと思う。
フォームのvalidateメソッドが、テンプレート内から直に呼び出すだけで結果(boolean)を取得できるような作りなら話は簡単だが、残念ながらそうなってはおらず上手く行かない。

node_modules/element-ui/types/form.d.ts(抜粋)
  /**
   * Validate the whole form
   *
   * @param callback A callback to tell the validation result
   */
  validate (callback: ValidateCallback): void
  validate (): Promise<boolean>

コールバックを渡して呼び出せば同期的にバリデーション結果を用いた処理を実行できるが、戻り値はvoidに限定される。
引数無しで呼び出せばバリデーション結果(boolean)を載せたPromiseを返すが、いかんせん非同期。
したがってどちらも使えない。

#その2の解決法 - ライフサイクルフックを使う
テンプレートからメソッドを直接呼び出しても結果が取得できないとなると、残る手段はインスタンスプロパティを介して参照する以外に無い。そのためにはライフサイクルフックを利用することになる。

$dataオブジェクト下のデータがいずれかの入力フィールドに紐づいている場合、そのデータに変更が加わると紐付く入力フィールドで個別のバリデーションが実行される。しかる後に、そのバリデーション結果も踏まえたバーチャルDOMの再描画・書き替えが行われるが、その前後で動作するライフサイクルフックがbeforeUpdateupdatedである。今回はbeforeUpdateを使用するのが適切だろう。

validateメソッドについては同期・非同期どちらを用いても良いが、前者の方がテストを書くのも楽だし、もし必要なら入力フィールド毎のエラー詳細も利用できるので、今回はそちらを採用する。

まずvalidateメソッドに渡すコールバックValidateCallbackの型定義を取り込む。

任意のVueファイル(抜粋)
<script lang="ts">

import { ElForm, ValidateCallback } from 'element-ui/types/form';
node_modules\element-ui\types\form.d.ts(抜粋)
export interface ValidateCallback {
  /**
   * The callback to tell the validation result
   *
   * @param isValid Whether the form is valid
   * @param invalidFields fields that fail validation
   */
  (isValid: boolean, invalidFields: object): void
}

次に、バリデーションの結果を受け取るインスタンスプロパティを用意する。
初期化については入力フィールドのデフォルト値に合わせて決め打ちでも良いが、遷移で諸々入力済みの状態から開始するような場合はバリデーション未実施の状態を区別するために nullable な型(boolean | null)で宣言した上nullで初期化する手もある。

初期化決め打ち
export default class MyComponent extends Vue {
  /* data */
  public $refs!: { [key: string]: ElForm };
  private validationResult: boolean = false;
nullで初期化
export default class MyComponent extends Vue {
  /* data */
  public $refs!: { [key: string]: ElForm };
  private validationResult: boolean | null = null;

最後に、クラス直下へbeforeUpdateメソッドを追加し、その中でフォームのvalidateメソッドを実行する。
引数としては、バリデーションの結果をインスタンスプロパティへとセットするコールバックを用意する。

任意のVueファイル(抜粋)
  private beforeUpdate() {
    this.updateValidationResult();
  }

  private updateValidationResult() {
    const callback: ValidateCallback = (isValid, invalidFields) => {
      this.validationResult = isValid;
    };

    this.$refs.myForm.validate(callback);
  }

以上、分かってしまえば超簡単。

ライフサイクルフックを検証するテストの書き方については別記事で書いた。
【Vue】ライフサイクルフックのテストを書く【TypeScript】

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?