4
4

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 1 year has passed since last update.

見通しを改善!Vue.js でのバリデーション処理のリファクタリング

Posted at

課題

フロントエンド開発において、フォームのバリデーションと入力欄の管理は一般的な作業です。
特に、多くの入力欄を持つフォームでは、コードが複雑になり、メンテナンスが困難になることがあります。

今回の課題は、複雑なフォームのコードをリファクタリングして、より管理しやすく、再利用可能なコードにすることです。

元々のコードは機能していましたが、見通しの悪さが目立ち、特にスキーマの部分とuseFieldの部分で同じ名称が度々出てくること、そしてuseFieldを何行も書かなければならない点が問題でした。

考えた方法

この課題に対処するために、以下の方法を考えました。

  1. useFieldの部分をスキーマ定義を使って簡略化する。
  2. 共通の処理を外部に定義して、どのページでも使えるようにする。
  3. スキーマ設定を外部ファイルに定義する。

前提条件

まず最初に今回リファクタリング対象となっているコードや環境について記述しておきます。

リファクタリングするコード

<template>
  <div class="container">
    <form class="mt-4">
      <div class="mb-3">
        <label for="testName" class="form-label">名前</label>
        <input type="text" class="form-control" v-model="testName" />
        <small v-if="basicInfoErrors.testName" class="text-danger">{{
          basicInfoErrors.testName
        }}</small>
      </div>
      <!-- 以下省略 -->
    </form>
  </div>
</template>

<script setup>
import { useField, useForm } from "vee-validate";
import * as yup from "yup";

// 日付の形式をチェックするカスタムルール
yup.addMethod(yup.string, "datetime", function () {
  return this.test({
    name: "datetime",
    message: ({ label }) => {
      return `${label}はYYYY/MM/DD HH:MM:SS形式で入力してください。`;
    },
    test: (value) => {
      return value.match(/^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/);
    },
  });
});

/* 他のカスタムルールは省略 */

// バリデーション設定
const basicInfoSchema = yup.object({
  // 名前(必須、100文字以内)
  testName: yup.string().required().max(100).label("名前"),
  // 実施期間(必須、YYYY/MM/DD HH:MM:SS形式、開始日と終了日の関係チェック)
  testPeriodFrom: yup.string().required().datetime().label("開始日時"),
  testPeriodTo: yup
    .string()
    .required()
    .datetime() // カスタムルール
    .startEnd(yup.ref("testPeriodFrom")) // カスタムルール
    .label("終了日時"),
// 以下、他の項目の設定
});

// 初期値設定
const initialValues = {
  testName: "サンプル",
// 以下、他の項目の設定
};

// フォーム設定
const { errors: basicInfoErrors, meta: basicInfoMeta } = useForm({
  validationSchema: basicInfoSchema,
  initialValues,
});

// フィールド設定
const { value: testName } = useField("testName");
const { value: testPeriodFrom } = useField("testPeriodFrom");
const { value: testPeriodTo } = useField("testPeriodTo");
// 以下、他の項目の設定
</script>

言語・フレームワーク

Vue.js: メインのフレームワークとして Vue.js を使用しています。
Nuxt3: サイト構築には Nuxt3 を使用。プラグインの作成などに関連する部分があります。

ライブラリ

vee-validate: フォームバリデーションに vee-validate ライブラリを使用しています。
yup: スキーマバリデーションに yup ライブラリを使用。カスタムバリデーションメソッドの追加なども行っています。

手順・実装内容

useFieldの部分の簡略化

まず、useFieldの部分を下記のように変更しました。
shemaのキーでループをさせて、そのキーを元にuseFieldをする事で、重複した記述を回避しています。

const formItems = new Map();
Object.keys(schema).forEach(fieldName => {
  const {value} = useField(fieldName);
  formItems.set(fieldName, value);
});

これによってtemplate側のmodelの指定部分も下記のようにformItemsから取得するように変更しています。

<input
  type="text"
  class="form-control"
  v-model="formItems.get('testName').value"
/>

共通の処理の外部化

次に、先ほど簡略化をした部分など他のページでも使える共通の処理を切り出しました。
今回はNuxt3を使っているのでプラグインとして切り出しています。

import {defineNuxtPlugin} from '#app';
import * as yup from 'yup';
import {useField, useForm} from 'vee-validate';

// 日付の形式をチェックする
yup.addMethod(yup.string, 'datetime', function () {
  return this.test({
    name: 'datetime',
    message: ({label}) => {
      return `${label}はYYYY/MM/DD HH:MM:SS形式で入力してください。`;
    },
    test: value => {
      return value.match(/^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/);
    },
  });
});

/* 他のカスタムルールの部分は省略 */

export default defineNuxtPlugin(nuxtApp => {
  return {
    provide: {
      yup: (schema, initialValues) => {
        const {errors: basicInfoErrors, meta: basicInfoMeta} = useForm({
          validationSchema: yup.object(schema),
          initialValues,
        });

        const formItems = new Map();

        Object.keys(schema).forEach(fieldName => {
          const {value} = useField(fieldName);
          formItems.set(fieldName, value);
        });

        return {
          formItems,
          basicInfoErrors,
          basicInfoMeta,
        };
      },
    },
  };
});

スキーマ設定と初期値設定の外部ファイル化

最後にスキーマ設定を外部ファイルに定義しました(ここでは/validates/test.js)。
(本当は、json形式に変換をしたかったのですが、断念・・・)

import * as yup from 'yup';

export const schema = {
  // テストの名前(必須、100文字以内)
  testName: yup.string().required().max(100).label('テストの名前'),
  // テストの実施期間(必須、YYYY/MM/DD HH:MM:SS形式)
  testPeriodFrom: yup.string().required().datetime().label('テストの開始日時'),
  testPeriodTo: yup.string().required().datetime().startEnd(yup.ref('testPeriodFrom')).label('テストの終了日時'),
};

export const initialValues = {
  testName: 'サンプル'
};

呼び出し側は下記のようにして、これを先ほどのプラグインに渡してあげればOKです。

import {schema, initialValues} from '/validates/test.js';

最終的なコード

リファクタリング後のコードは以下の通りです。スッキリしましたね!

<template>
  <!-- 省略 -->
</template>

<script setup>
import {schema, initialValues} from '/validates/test.js';

const {$yup} = useNuxtApp();
const {formItems, basicInfoErrors, basicInfoMeta} = $yup(schema, initialValues);
</script>

まとめ

このリファクタリングによって、script部分の可読性が向上し、プラグイン化によって他のページでも同様の効果を得ることができるようになりました。
今後の開発では、このようなアプローチを取ることで、効率的な開発が期待できると思います。

リファクタリングは単にコードを綺麗にするだけではなく、将来の変更に対する柔軟性を高め、チーム全体の生産性を向上させる重要なプロセスです。今回の経験は、その価値を改めて認識する良い機会でした。

もっと効率良くできるよ!とか、ここおかしくない?といったことがあればコメントでお知らせください!

4
4
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
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?