はじめに
Svelte5がリリースされました。Runes
という機能が追加されています。
このうち最も基本的な $state
ルーンが class
に対応しているので、リアクティブなバリデーションを実装できそうと思いました。
実装
まずはクラスを定義します。#errors = $state({})
が重要です。
import {z} from 'zod';
const DataSchema =z.object({
name:z.string({ required_error: 'nameを入力してください'})
.nonempty({ message: 'nameを入力してください'}),
age: z.string({required_error: 'ageを入力してください'})
.nonempty({ message: 'ageを入力してください'})
.regex(/^[0-9]+$/,'ageは0以上の整数を入力してください')
});
export class Data {
#id = crypto.randomUUID();
#name;
#age;
#errors = $state({});
constructor(props) {
this.#name = props?.name;
this.#age = props?.age;
this.#validate();
}
get id() {
return this.#id;
}
get name() {
return this.#name;
}
set name(name) {
this.#name = name;
this.#validate('name');
}
get age() {
return this.#age;
}
set age(age) {
this.#age = age;
this.#validate('age');
}
get errors() {
// 変更されないようにコピーを返却
return {...this.#errors};
}
#validate(targetName) {
Object.keys(DataSchema.shape).forEach(key => {
if (!targetName || targetName === key) {
const res = DataSchema.shape[key].safeParse(this[key]);
if (!res.success) {
this.#errors[key] = res.error.issues[0].message;
} else {
delete this.#errors[key];
}
}
});
}
isValid() {
return Object.keys(this.#errors).length === 0;
}
}
入力フォームで使ってみます。ポイントはバリデーションが自動的に実施されるため App.svelte
でバリデーションを実施するコードがない点です。
<script>
import { Data } from './data.svelte.js';
let data = $state(new Data());
function add() {
alert(`登録! ${data.name}、${data.age}`);
data = new Data();
}
</script>
<div>
<label>
<span>名前</span>
<input bind:value={data.name} />
<span class="error">{data.errors.name}</span>
</label>
</div>
<div>
<label>
<span>年齢</span>
<input bind:value={data.age} />
<span class="error">{data.errors.age}</span>
</label>
</div>
<button disabled={!data.isValid()} onclick={add}>登録</button>
<style>
div {
margin-bottom: .5rem;
}
.error {
color: red;
display:block;
height: 1.5rem;
margin-left: 2.5rem;
}
</style>
data.svelte.js
の errors
が変更されると App.svelte
の #errors
を使用している箇所が再レンダリングされる、という動きになります。これが $state
を使う意味になります。ちなみに、#errors
から派生する isValid()
関数を使用している箇所も再レンダリング対象になります。
動作確認
こちら で動作確認できます。data.svelte.js
で $state
を使わない場合にどうなるか、ぜひ試してみてください。
考察
data.svelte.js
の constructor
以下はお決まりの実装なので Preprocessor を自作して自動生成するのもありですね。
ただ、一般的なフォームバリデーションは Superforms のようなバリデーションライブラリを使ったほうが断然楽なので、時と場合によって使い分けるのがよさそうです。
まとめ
クラスのフィールドに $state
ルーンを使うことでリアクティブなバリデーションを実装することができました。これによって App.svelte
ファイルではバリデーションを実行する手間がなくなり、すっきりしたコードを書くことができました。
Runes
が導入されたことで、コードが冗長になってしまうのかなと心配していましたが、杞憂でしたね!