アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

Vue+Vuexにvalidatorjsを組み合わせると自在にバリデーションが書ける!

morishitaです。 今回はJavascriptのバリデーションライブラリvalidatorjsを紹介します。

TL;DR

  • いこレポではクライアントサイドバリデーションにvalidatorjsを使っています。
  • validatorjsはとても柔軟に設定でき使いやすいバリデータです。
  • Vue+Vuexなアプリケーションととても相性がいいです。

いこレポの記事編集機能

いこレポは見ての通り、公開しているWebサイトは特に特別な機能を持たないメディアサイトで、実装もシンプルです。 したがって表側よりも、記事を管理する機能のほうが実装的に複雑です1

その管理機能の中でも最もコード量が多く複雑なのは記事編集機能です。

記事編集機能はVue.jsを利用してます。Vue.jsに加えてVuexも利用しています。 本文以外にも記事を分類するための属性が結構たくさんあります。 それらを公開時にはもれなく設定する必要があるので、ミスなく運用するためにバリデーションが必要です。 サーバサイドのバリデーションももちろんやっていますが、インタラクティブに不足を指摘したほうが使いやすいので クライアントサイドのバリデーションも行っています。

JavaScriptのバリデーションライブラリはたくさんありますが、次の理由からValidatorjsを採用しました。

  • 他のライブラリに依存していない
  • カスタムバリデータの追加が簡単
  • i18n対応もしている(いコレポではあまり使っていないですが...)

Validatorjs とは

Validatorjs はJavaScriptのバリデーションライブラリです2

バリデーションライブラリというと値を1つ渡して、それがバリデーションルールを満たすかを判定するというものが多いです。しかしVaridatorjsは A data validation library を謳っています。

どういうことかというと、JavaScriptのオブジェクトを渡すと、指定したプロパティの値がバリデーションルールを満たしているのか一度に判定してくれるのです。渡したオブジェクトのデータ全体が適正な値であるかを判定してくれます。ネストした深い階層の属性のバリデーションも可能です。

用意されているバリデーションルールも多数あり、必須チェックや文字数のチェックはもちろん、 emailやURLの形式チェックのルールもあります。また関連する属性がある値の場合のときに必須になるrequired_ifのような便利なルールもあります。

利用例

次のようなpersonオブジェクトのバリデーションを考えます。

  • name属性が必須
  • accept_mail_magazine属性がtrueのとき、mail属性が必須
  • mail属性は E-mailアドレスの形式でなければならない

上記を Validatorjs で実装すると次のようになります。

const Validator = require('validatorjs');

// バリデーション対象のオブジェクト
const person = {
  name: 'hogehoge',
  accept_mail_magazine: true,
  email: 'hoge@example.com',
}

// バリデーションルールの定義
const rules = {
  name: 'required',
  email: [{required_if: ['accept_mail_magazine',true]}, 'email'],
}

const v = new Validator(person, rules); // バリデータインスタンスの生成

// バリデーション
check: v.check() // => true
v.errors // バリデーションエラーの情報がすべて取れる

更に足りなければ独自のルールを追加することもできます。

例えば、次のバリデーションを先程のpersonオブジェクトに追加します。

  • languages属性は次の選択肢から少なくとも1つは選択する必要がある。複数選択しても良い。
    • ruby
    • javascript
    • python
    • kotlin
    • swift
    • others

このようなバリデーションルールは標準では用意されていません。

でも、次のようにカスタムバリデータを実装して追加することでバリデーション可能になります。

const Validator = require('validatorjs');

// カスタムバリデータの定義
function someOneTrue(value, requirement, attribute) {
  return Object.values(value).some((val) => val);
}
// 定義したカスタムバリデータを登録
Validator.register(
  'some_one_true', 
  someOneTrue, 
  ':attribute は少なくとも1つはチェックする必要があります'
);

const person = {
  name: 'hogehoge',
  accept_mail_magazine: true,
  email: 'hoge@example.com',
  languages: { // 新たに追加した属性。
    ruby: false,
    javascript: false,
    python: false,
    kotlin: false,
    swift: false,
    others: false,
  }
}
const rules = {
  name: 'required',
  email: [{required_if: ['accept_mail_magazine',true]}, 'email'],
  languages: 'some_one_true', // 追加したカスタムバリデータの使用
}
const v = new Validator(person, rules);

check: v.check() // => true
const errors = v.errors // バリデーションエラーの情報がすべて取れる

上記を実行すると errors の中身は次のようになります。

{"languages":["languages は少なくとも1つはチェックする必要があります"]}

簡単ですね。

そして、Vue+Vuexと組み合わせると、それはもうとてもとても便利なのです。

Vue+Vuex

Vue+VuexでVaridatorjsを利用するとどう便利なのか? ということを説明するためにVuexについて少し説明します。

Vuex は Vue.js アプリケーションのための状態管理パターン + ライブラリです。Vue+Vuexなアプリケーションの場合、データを Vuexのstoreで管理するJSのオブジェクトの形で持ちます。 Flux、 Redux に影響を受けており、単方向データフローでデータの状態を管理、更新します。

f:id:HeRo:20190222161334p:plain
Vuexのドキュメントより引用

VueアプリケーションではVuexのmutationを使ってstateを変更します。 stateの変更はそれを参照するVueコンポーネントにすぐに反映されます。

更にVuexにはゲッター(getters)というVueコンポーネントの算出プロパティ(computed)に対応する機構があります。これによりstateの値をそのまま参照するのではなく計算した結果を参照することも可能です。算出プロパティ同様に、stateが更新されればゲッターにも反映され、ゲッターを参照するコンポーネントにも伝播されます。

Vue+Vuex での Validatorjs

Vue+Vuex アプリケーションでValidatorjsを使ったバリデーションをどこに実装するのが良いのでしょうか?

それは VuexのStoreのゲッターです。

次のコードはVuexストアの実装例です。

import Vue from 'vue';
import Vuex from 'vuex';

// 次のモジュールで前述のカスタムバリデータと
// それを使ったルールを定義しているとします。
import {Validator, rules} from './validator';

Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    person: {
      name: '',
      accept_mail_magazine: true,
      email: 'hoge@example.com',
      languages: {
        ruby: false,
        javascript: false,
        python: false,
        kotlin: false,
        swift: false,
        others: false,
      }
    },
  },
  getters: {
    validationErrors: (state) => {
      const v = new Validator(state.person, rules);
      v.check();
      return v.errors;
    },
  }
});

このStoreをVueのモジュールから参照する例が次のコードです。

export default {
    store,
    computed: {
      validationErrors() {
        return this.$store.getters.validationErrors;
      },
    },
  // 〜 略 〜
}

こうしておくと、算出プロパティvalidationErrorsには storeに格納されたpersonオブジェクトの バリデーション結果が格納されます。

personオブジェクトが更新されるたびにバリデーション結果も更新されるので、 算出プロパティvalidationErrorsを参照するUIにもその結果が即時に反映されます。

便利かつ簡単でしょう? 一度お試しあれ。

最後に

アクトインディでは使いやすいUXを一緒に作っていく エンジニアを募集しています。


  1. 複雑と言っても表側がCRUDのほぼREADしかないのに対して、管理機能ではCRUDを一通り実装しているだけですが。

  2. chriso/validator.js: String validationというよく似た名前のライブラリもあるのでお間違えのないように。