Babel7 OptionalChainingでLaravelのバリデーションをハンドリングする

f:id:kotamat:20180829111209j:plain# Babel7 OptionalChainingでLaravelのバリデーションをハンドリングする

こんにちは、@kotamatです。

昨日Babel7がリリースされましたね🎉

https://babeljs.io/blog/2018/08/27/7.0.0

Babel6から3年かかってのリリースです。コントリビューターの方々お疲れ様でした。 パッケージ名からの破壊的変更が入っていますが、babelのupgrade用スクリプトが用意されているので、手軽にupgradeすることができます(執筆時点だと、beta版までしかあげられませんが‥)

Babel7のリリースに伴い、TS39の新しいシンタクスがサポートされ、その中でもStage1のOptional Chainingが、Laravelのエラーハンドリングにとても相性がいいので紹介させていただきます。

Laravelのバリデーションエラー

Laravel側でFormRequestを用いて下記のようなバリデーションルールを実装した場合、

class StoreRelationRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'address.prefecture'    =>['required', 'string'],
            'address.zipcode'    =>['required', 'integer'],
            'staff.*.id'    =>['required', 'integer'],
            'staff.*.name'  =>['required', 'string'], 
        ];
    }
}

staffの0, 1がバリデーションに引っかかった場合、下記のようなバリデーションメッセージが帰ってきます

{
    "address.prefecture": ["The prefecture field is required."],
    "address.zipcode": ["The zipcode field is required."],
    "staff.0.id": ["The id field is required."],
    "staff.0.name": ["The name field is required."],
    "staff.1.id": ["The id field is required."],
    "staff.1.name": ["The name field is required."]
}

このままでは、フロント側では扱いづらいので、フロント側で扱いやすい形に整形します

reno-shelter/convert-laravel-validationを使うと、上記メッセージは下記のように修正されます

{
    address: {
      prefecture: ['The prefecture field is required.'],
      zipcode: ['The zipcode field is required.'],
    },
    staff: {
      0: {
        name: ['The name field is required.'],
        id: ['The id field is required.'],
      },
      1: {
        name: ['The name field is required.'],
        id: ['The id field is required.'],
      },
    },
  }

Optional Chainingでメッセージを取り扱う

上記オブジェクトでは、例えば、address.*系のエラーがない場合は、当然ながら参照できない形になってしまうため、メッセージ表示部分で存在可否のチェックを行わないとundefined indexのFatal Errorが発生してしまいます。

そこで便利なのがOptionalChainingです。

Optional chainingの仕組み

const obj = {
  foo: {
    bar: {
      baz: 42,
    },
  },
};

const baz = obj?.foo?.bar?.baz; // 42

const safe = obj?.qux?.baz; // undefined

// Optional chaining and normal chaining can be intermixed
obj?.foo.bar?.baz; // Only access `foo` if `obj` exists, and `baz` if
                   // `bar` exists

このように、存在しないパラメータがチェーンの途中にあるばあいは、そこでundefinedを返し、ランタイムでのエラーを防ぐ事ができます。

<template>
    <div>
        <input v-model="address.prefecture" />
        <error v-for="(msg, key) in error?.address?.prefecture || []" :key="key"  :msg="msg"/>
    </div>
</template>

上記のようにテンプレート内でerror?.address?.prefectureとすることによって、もしerror, error.address, error.address.prefectureがそれぞれ存在しなかったとしても問題なく実行されるため、無駄にif文で分岐して処理を複雑にする必要はなくなります。

インストール方法

babel7が入っている前提で話をすすめます。babel6以前からのアップデートはbabelupdateを参考にしてください。

まずはyarn (or npm)でプラグインをインストールし、

yarn add @babel/plugin-proposal-optional-chaining

.babelrcでpluginの設定をするだけです、

{
  "plugins": ["@babel/plugin-proposal-optional-chaining"]
}

Nuxt.jsであればnuxt.config.jsに下記のように記載します

export default {
  build: {
    babel: {
      plugins: ['@babel/plugin-proposal-optional-chaining']
    }
  }
}

まとめ

Babel7で新たに入ったOptionalChainingの紹介をさせていただきました。まだProposalの段階なので変更が入る可能性はありますが、より実装にフォーカスしたコーディングができるようになるので、是非導入してみてください!