Single Page Application の Form 画面における Best Practice を考える(前編)

## あらすじ

Nuxt.js で Form 画面をゴリゴリ作成する案件に少しだけ関わる事になったのですが、少し画面数が多いらしく、複数人のエンジニアがそれぞれ手分けして画面を作成する事になりそうです。さて、そうなると予め関数の設計方法などある程度認識を合わせておかないと、各画面でいろいろな関数が現れてしまい、収拾がつかなくなります。

ところで SPA における Form 画面の作成方法に関する Best Pracitce はどんなものなのでしょうか？残念ながら私は良い感じの文献を発掘できませんでした。

というわけで私が思う Best Practice を考えてみました。例で出てくるコードは Vue.js で書かれていますが、Angular でも React でも応用ができる話になっていると思います。

なお、ここに登場するコードは以下のレポジトリで確認できます。

https://github.com/okamuuu/vue-examples

## Form Validation を考える

Form Validation を実装するのは単調で退屈で作業コストがかかる作業です。今回はこの作業を軽減させるために validatorjs を使用します。以下、Form Validation の簡単な使用例です。

```javascript
const Validator = require('validatorjs')
Validator.useLang('ja')
const rules = { name: 'required' }
const v = new Validator({name: ''}, rules)
const attributeNames = { name: '氏名' }
v.setAttributeNames(attributeNames)
v.passes() // 判定が行われる
console.log(v.errors.errors) // 判定結果: { name: [ '氏名は必須です。' ] }
```

このように良く使われる Validation rule が最初から登録されていて、かつ、エラーメッセージも最初から用意されています。attributeNames は登録が必要ですがこれは関数化して使いやすくすれば良いでしょう。

## カスタムルールを追加する

`姓は全角で入力してください`, や `セイは全角カタカナで入力してください` といった日本特有の処理があるのでその場合は以下のようにして rule を追加できます。ちなみに正規表現の書き方ネットで調べたものをコピペしただけなので間違えてたらごめんなさい。

```javascript
/* eslint-disable no-control-regex */
Validator.register(
  'zenkaku',
  value => /[^\x01-\x7E\xA1-\xDF]+$/.test(value),
  ':attributeは全角で入力してください'
)
Validator.register(
  'zenkakuKana',
  /* eslint-disable-next-line no-irregular-whitespace */
  value => /^[ァ-ヶー　]+$/.test(value),
  ':attributeは全角カナで入力してください'
)
```

## validatorjs を wrap して Vue.js で使いやすくする

というわけで以下のようにしました。birthday に関しては `date` というルールがあるのですが、transpile するときに解釈が異なるらしくエラーとなるので自前で書いています。

https://github.com/skaterdav85/validatorjs/issues/357

```javascript:utils/validator.js
import Validator from 'validatorjs'
import { isValid as isValidDate, parseISO } from 'date-fns'

Validator.useLang('ja')

const rules = {
  lastNameKanji: 'required|zenkaku',
  firstNameKanji: 'required|zenkaku',
  lastNameKana: 'required|zenkakuKana',
  firstNameKana: 'required|zenkakuKana',
  birthday: 'required|birthday',
  zipcode: 'required|zipcode'
}

const attributeNames = {
  lastNameKanji: '姓',
  firstNameKanji: '名',
  lastNameKana: 'セイ',
  firstNameKana: 'メイ',
  birthday: '生年月日',
  zipcode: '郵便番号'
}

/* eslint-disable no-control-regex */
Validator.register(
  'zenkaku',
  value => /[^\x01-\x7E\xA1-\xDF]+$/.test(value),
  ':attributeは全角で入力してください'
)
Validator.register(
  'zenkakuKana',
  /* eslint-disable-next-line no-irregular-whitespace */
  value => /^[ァ-ヶー　]+$/.test(value),
  ':attributeは全角カナで入力してください'
)
Validator.register(
  'birthday',
  value => value.length === 10 && isValidDate(parseISO(value)),
  ':attributeを正しく入力してください'
)
Validator.register(
  'zipcode',
  value => /^\d{3}-?\d{4}$/.test(value),
  ':attributeは7桁で入力してください'
)

/* eslint-eable no-control-regex */
export function validate (obj) {
  const v = new Validator(obj, rules)
  v.setAttributeNames(attributeNames)

  if (v.passes()) {
    return null
  }
  return v.errors.errors
}
```

これを `utils/validator.js` に記述しておくと、Vue.js で Form 画面を作成する時、以下のように form validation の判定を行う事ができます。

```javascript:pages/vue-form-validation/example.vue
import { validate } from '~/utils/validator'

export default {
  data () {
    return {
      fields: {},
      errors: {}
    }
  },
  methods: {
    async handleChangeFields (fields) {
      this.fiedls = { ...this.fields, ...fields }
      this.errors = Object.assign({}, validate(this.fields))
    }
  }
}
```

## まとめ

というわけで単調で面倒な作業であるエラーメッセージの定義を簡略化する事ができました。validatorjs は Node.js でも動作するので、この utils をサーバーサイドエンジニアに託す事によってフロントエンドエンジニアが責務を軽減する事もできます。

次回は Component の作り方を考えます。