はじめに
Web開発において、フォームは開発者の悩みのタネの一つです。とくに、ユーザーの入力中にリアルタイムでバリデーションを行い、エラーメッセージを表示するようなフォームは自前で実装するのが大変です。全部HTML標準のバリデーションに任せたいところですが、デザインの都合上、そうも行かないことも多いでしょう。
そういったときに必要になるのがフォームライブラリです。執筆時点(2022年2月)で,
Reactでは React Hook Formが覇権を握っているようです。VueではVeeValidateあたりが有名所でしょうか。
さて、近年その勢いを増しているSvelteですが、フォームライブラリが乱立しています。本記事ではSvelteのフォームライブラリを比較していきます。
注意点
- https://bestofsvelte.com/t/form こちらのサイトに乗っているフォームライブラリを比較します。
- 各ライブラリは2022年2月時点での最新バージョンを扱います。
- 筆者のSvelte歴は業務で1年ほど使ったくらいです。
- 10段階評価をしていますが、ざっくりと以下の点を見ています
- ボイラープレートの少なさ(Svelteの重要な思想だと思ってます)
- TypeScriptサポート(昨今のフロントエンドにおいて、TypeScriptの重要さは言うまでもありません)
- テストケースが書かれているか
- なお、GitHubのスター数やnpmのダウンロード数はあまり参考にしません。筆者の経験上、Svelte界隈のライブラリはスター数やダウンロード数とクオリティが一致していないことが多いためです。(Svelteがまだ比較的新しいフレームワークで、フォームライブラリが乱立しているのが原因と考えています。)
選手権スタート
エントリーNo. 1 Svelte forms lib
評価:7/10
ReactのフォームライブラリであるFomikに近いAPIであることをうたっています。yupのスキーマと連携できます。TypeScriptの型定義もありますが、input要素すべてにon:keyup={handleChange}
と指定する必要があるのはマイナスポイントです。
<script>
import { createForm } from "svelte-forms-lib";
const {
form,
errors,
state,
touched,
isValid,
isSubmitting,
isValidating,
// handlers
handleBlur,
handleChange,
handleSubmit
} = createForm({
initialValues: { name: "", email: "" },
validationSchema: yup.object().shape({
name: yup.string().required(),
email: yup.string().email().required()
}),
onSubmit: values => { /** ... */ }
});
</script>
<form class:valid={$isValid} on:submit={handleSubmit}>
<label>name</label>
<input name="name" on:keyup={handleChange} />
{#if $errors.name && $touched.name}
<small>{$errors.name}</small>
{/if}
<label>email</label>
<input name="email" on:keyup={handleChange} />
{#if $errors.email && $touched.email}
<small>{$errors.email}</small>
{/if}
<button type="submit" disabled={!$isValid}>
{#if $isSubmitting}loading...{:else}submit{/if}
</button>
</form>
(https://svelte-forms-lib-sapper-docs.vercel.app/basic より抜粋)
エントリーNo. 2 Svelte Final Form
評価:6/10
Final Formという、フレームワークに関係なく使えるフォームライブラリをSvelteで使いやすくしたラッパーです。on:blur
, on:focus
をいちいち指定する手間がかかるのが難点です。TypeScriptの型定義はあります。リポジトリを見るとまだテストケースがないようです。
<Form {onSubmit} {validate} {initialValues} let:form let:state>
<form on:submit|preventDefault={form.submit}>
<Field name="firstName" let:input let:meta>
<label for="firstName">First Name</label>
<input
name={input.name}
on:blur={input.onBlur}
on:focus={input.onFocus}
on:input={(e) => input.onChange(e.target.value)}
type="text"
placeholder="Last Name"
value={input.value} />
{#if meta.touched && meta.error}
<div>{meta.error}</div>
{/if}
</Field>
(https://github.com/jetrockets/svelte-final-form より抜粋)
エントリーNo. 3 Svelte Formly
評価:3/10
なんと、フォーム要素をオブジェクトで宣言する設計になっています。scriptとtemplateを分業したい人には全くもってマッチしないでしょう。「フォーム要素の間に何か違う要素を入れたい」という細かい要望に対処できないのがなかなか厳しそう。TypeScriptの型定義がないのもいただけないです。
<script>
import { get } from "svelte/store";
import { valuesForm, Field } from "svelte-formly";
const fields = [
{
type: 'input',
name: 'firstname',
value: '',
attributes: {
type: 'text',
label: 'Username',
id: 'firstname',
classes: ['form-control'],
placeholder: 'Tap your first name',
},
rules: ['required', 'min:6'],
messages: {
required: 'Firstname field is required!',
min: 'First name field must have more that 6 caracters!',
},
},
{
type: 'input',
name: 'email',
value: '',
attributes: {
type: 'email',
id: 'email',
placeholder: 'Tap your email',
},
rules: ['required', 'email'],
},
// 略
</script>
<form
on:submit|preventDefault="{onSubmit}"
class="custom-form"
style="--theme-color: {color}"
>
<Field {fields} />
<button class="btn btn-primary" type="submit">Submit</button>
</form>
(https://github.com/arabdevelop/svelte-formly#usage より)
エントリーNo. 4 Svelte Use Form
評価:8/10
圧倒的にシンプルな書き心地が特徴です。formという変数がstore($formと使う)とaction(use:formと使う)の両方の振る舞いをするというのが面白いです。初めて見たときは天才かと思いました。TypeScriptの型定義もあります。
ただ、現時点でテストケースがありません。
<script>
import { useForm, Hint, validators, minLength } from "svelte-use-form";
const form = useForm();
</script>
<form use:form>
<input name="title" use:validators={[minLength(5)]} />
<Hint for="title" on="minLength" let:value>
The title requires at least {value} characters.
</Hint>
<button disabled={!$form.valid}>Submit</button> <br />
</form>
(https://github.com/noahsalvi/svelte-use-form#minimal-example-repl より)
エントリーNo. 5 Felte
評価:10/10
Svelte Use Formと似た書き心地です。ドキュメントが非常に整備されています。また、動的にフォーム要素が増える場合に対応していたり、yupだけでなくzodやsuperstructといったスキーマに対応していたりと痒いところに手が届く仕様になっています。アクセシビリティについても考慮されていたり、公式が用意しているプラグインによって、エラーの表示形式が変更できたりと至りつくせりです。
TypeScriptで開発されており、型定義も標準装備されています。テストケースもしっかり書かれており、そのカバレッジは99%と非常に高いです。
<script>
import { createForm } from 'felte';
const { form } = createForm({
onSubmit: (values) => {
// ...
},
})
</script>
<form use:form>
<input type="text" name="email">
<input type="password" name="password">
<input type="submit" value="Sign in">
</form>
(https://felte.dev/docs/svelte/getting-started より)
エントリーNo. 6 svelte-forms
評価9/10
フィールド1つ1つに対して、field
メソッドを使ってストアを作る必要があります。変数が増えてしまうというデメリットはあるものの、ボイラープレートが多すぎるというわけではなく、書き味は悪くないです。他のフォームライブラリとの違いは、bind:value
などsvelteのreactive機能を使う方針をとっていることです。この方法にはUIライブラリと組み合わせやすいというメリットがあります。UIライブラリが提供するフォームコンポーネントはbind:value
などbindを使う前提のものも多いためです。
TypeScriptの型定義もあり、テストケースも書かれている点も嬉しいです。
<script>
import { form, field } from 'svelte-forms';
import { required } from 'svelte-forms/validators';
const name = field('name', '', [required()]);
const myForm = form(name);
</script>
<section>
<input type="text" bind:value={$name.value} />
<button disabled={!$myForm.valid}>Send form</button>
</section>
(https://chainlist.github.io/svelte-forms/ より)
結果発表
Svelte フォームライブラリ選手権 優勝はFelteです。
抽象化が非常にうまくなされている印象で、ベースが使いやすいだけでなく、細かいユースケースにも対応できる作りになっています。またTypeScriptで開発されていること、テストケースもしっかり書かれていることから、保守性も高いです。
Felteを使ってみて気に入った方はGitHubでスターをつけて開発者を応援しましょう。
更新履歴
2022/02/14
再調査しました。
エントリーNo. 6 svelte-formsを追加しました。
エントリNo. 1~4のライブラリに特に大きな変化はなく、相変わらずfelteが優勝でした。
felteは1.0.0が無事リリースされました。
https://github.com/pablo-abc/felte/blob/main/packages/felte/CHANGELOG.md#100