課題
フロントエンド開発において、フォームのバリデーションと入力欄の管理は一般的な作業です。
特に、多くの入力欄を持つフォームでは、コードが複雑になり、メンテナンスが困難になることがあります。
今回の課題は、複雑なフォームのコードをリファクタリングして、より管理しやすく、再利用可能なコードにすることです。
元々のコードは機能していましたが、見通しの悪さが目立ち、特にスキーマの部分とuseFieldの部分で同じ名称が度々出てくること、そしてuseFieldを何行も書かなければならない点が問題でした。
考えた方法
この課題に対処するために、以下の方法を考えました。
- useFieldの部分をスキーマ定義を使って簡略化する。
- 共通の処理を外部に定義して、どのページでも使えるようにする。
- スキーマ設定を外部ファイルに定義する。
前提条件
まず最初に今回リファクタリング対象となっているコードや環境について記述しておきます。
リファクタリングするコード
<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部分の可読性が向上し、プラグイン化によって他のページでも同様の効果を得ることができるようになりました。
今後の開発では、このようなアプローチを取ることで、効率的な開発が期待できると思います。
リファクタリングは単にコードを綺麗にするだけではなく、将来の変更に対する柔軟性を高め、チーム全体の生産性を向上させる重要なプロセスです。今回の経験は、その価値を改めて認識する良い機会でした。
もっと効率良くできるよ!とか、ここおかしくない?といったことがあればコメントでお知らせください!