0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Svelte5の$stateを使ったリアクティブなバリデーション

Last updated at Posted at 2024-12-03

はじめに

Svelte5がリリースされました。Runes という機能が追加されています。

このうち最も基本的な $state ルーンが class に対応しているので、リアクティブなバリデーションを実装できそうと思いました。

実装

まずはクラスを定義します。#errors = $state({}) が重要です。

data.svelte.js
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 でバリデーションを実施するコードがない点です。

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.jserrors が変更されると App.svelte#errors を使用している箇所が再レンダリングされる、という動きになります。これが $state を使う意味になります。ちなみに、#errors から派生する isValid() 関数を使用している箇所も再レンダリング対象になります。

動作確認

こちら で動作確認できます。data.svelte.js$state を使わない場合にどうなるか、ぜひ試してみてください。

考察

data.svelte.jsconstructor 以下はお決まりの実装なので Preprocessor を自作して自動生成するのもありですね。

ただ、一般的なフォームバリデーションは Superforms のようなバリデーションライブラリを使ったほうが断然楽なので、時と場合によって使い分けるのがよさそうです。

まとめ

クラスのフィールドに $state ルーンを使うことでリアクティブなバリデーションを実装することができました。これによって App.svelte ファイルではバリデーションを実行する手間がなくなり、すっきりしたコードを書くことができました。

Runes が導入されたことで、コードが冗長になってしまうのかなと心配していましたが、杞憂でしたね!:smiley:

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?