🦸

SuperformsのProxy objectsは何ができるのか

2024/11/17に公開

SveltekitのフォームライブラリであるSuperformsを使った開発の中でProxy objectsを利用する機会がありました。その際いろいろと触って確認したことを備忘録として残しておきます。
「Proxy objectsって何に使えるの??」な人の参考になれば幸いです。

※Superformsの基本的な使い方がわかる方を想定しているため、Superformsの詳細については記載しません。

Superforms とは

Sveltekitのフォームライブラリです。フォームのバリデーション等をサポートしてくれます。
https://superforms.rocks/

Proxy objects とは

ざっくり言うと、対応するフォームデータとの双方向更新や片方向のデータ変換をしてくれるオブジェクトです。
DateProxyを例にすると、Date型のフォームデータにnew Date('2024-10-01T00:00:00.000Z')と設定したら、それに対応するProxyからは2024/10/01の文字列が取得できます。逆に、Proxyに2024/10/01の文字列を設定したら、フォームデータのDate型に2024/10/01が設定されます。めっちゃ便利。

ちなみに私はProxy objectsの存在を知るまで頑張って双方向変換を実現しようとして無限ループさせたり力技実装したりと時間を無駄にしたので同じ苦しみを味わう人が減って欲しい・・・笑

https://superforms.rocks/api#proxy-objects

Superformsには現時点で以下のプロキシが用意されています。
基本的に指定したフォームフィールドに対する書き込み可能なストアが返却されます。

import {
  // The primitives return a Writable<string>:
  booleanProxy,
  dateProxy,
  intProxy,
  numberProxy,
  stringProxy,
  // File proxies
  fileProxy,
  fileFieldProxy, // formFieldProxy
  // File[] proxies
  filesProxy,
  filesFieldProxy, // arrayProxy
  // The type of the others depends on the field:
  formFieldProxy,
  arrayProxy,
  fieldProxy      
} from 'sveltekit-superforms';

ここからはそれぞれのプロキシについて見ていきましょう。
各オプションの説明には、どっち方向で動作するかも記載しています。
taintオプションはproxy以外で使われる場合と動作が変わらないため取り上げません。

私が検証した時のコードも貼っておくので参考までにどうぞ。
https://www.sveltelab.dev/k0wn858me0qgurx
https://github.com/cacaca-came/sample-superforms-proxy-objects

booleanProxy

boolean型に対するプロキシです。string型のストアになっています。

import { superForm, booleanProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = booleanProxy(form, 'field', { options });
options
{
  trueStringValue = 'true';
  taint?: boolean | 'untaint' | 'untaint-form';
}

trueStringValue オプション($form → $proxy)

$form.fieldtrueの場合に、指定された文字列が$proxyに設定されます。
$form.fieldfalseの場合は、$proxyは空文字になります。

$proxyに対してtrueStringValueオプションに指定した文字列を入力した時に$form.fieldtrueになるオプションではありません。$proxyに何らかの文字列を入れれば通常のbooleanと同じくtrue、空文字であればfalseになります。

ユースケースとしては有効にするしないの選択項目などに使えるのではないでしょうか。

<script>
const proxy = booleanProxy(form, 'field', { trueStringValue: '1' });
</script>

<input type="radio" value="1" bind:group={$proxy} />
<input type="radio" value="" bind:group={$proxy} />

dateProxy

date型に対するプロキシです。string型のストアになっています。

import { superForm, dateProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = dateProxy(form, 'field', { options });
options
{
  format:
    // Extract the part of the date as a substring:
    | 'date' | 'datetime' | 'time'
    // Convert the date to UTC:
    | 'date-utc' | 'datetime-utc' | 'time-utc'
    // Convert the date to local time:
    | 'date-local' | 'datetime-local' | 'time-local'
    // The default ISODateString:
    | 'iso' = 'iso';
  empty?: 'null' | 'undefined';
  taint?: boolean | 'untaint' | 'untaint-form';
}

format オプション($form → $proxy)

$form.fieldの値をどのような文字列に変換するかを指定するオプションです。
それぞれのオプションを設定したときに$proxyに設定される値を記載します。
以下の例では$form.field = new Date('2024-10-01T00:00:00.000Z')が設定されているものとします。ブラウザ側はJSTです。

option $proxy
iso '2024-10-01T00:00:00.000Z'
date '2024-10-01'
datetime '2024-10-01T00:00'
time '00:00'
date-utc '2024-10-01'
datetime-utc '2024-10-01T00:00'
time-utc '00:00'
date-local '2024-10-01'
datetime-local '2024-10-01T09:00'
time-local '09:00'

プロキシに限った話ではありませんが、日付を扱う場合にはタイムゾーンに気をつけて扱いましょう!

empty オプション($form ← $proxy)

$proxyに空文字を入力した際に$form.fieldに設定される値を指定します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined

intProxy

number型に対するプロキシです。string型のストアになっています。
少数を扱う場合はnumberProxyを利用しましょう。
オプションで特殊な変換をしない限りは、基本的にbind:valueで事足りると思います。
(文字列-数値間の変換程度であれば自動で行なってくれるためです)

import { superForm, intProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = intProxy(form, 'field', { options });
options
{ 
  empty?: 'null' | 'undefined' | 'zero';
  initiallyEmptyIfZero?: boolean;
  taint?: boolean | 'untaint' | 'untaint-form';
}

empty オプション($form ← $proxy)

$proxyに空文字を入力した際に$form.fieldに設定される値を指定します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined
{ empty: 'zero' } '' (空文字) 0

initiallyEmptyIfZero オプション($form → $proxy)

$form.fieldの値が0の場合に空文字に変換するか、0のままにするかのオプションです。
初期値に0を入れておきたいがプレースホルダーをユーザーに見せたい時に有効化すると良いでしょう。

option $form.field $proxy
{ initiallyEmptyIfZero: true } 0 '' (空文字)
{ initiallyEmptyIfZero: false } 0 0

numberProxy

number型に対するプロキシです。string型のストアになっています。
正数のみを扱う場合はintProxyを利用しましょう。
オプションで特殊な変換をしない限りは、基本的にbind:valueで事足りると思います。
(文字列-数値間の変換程度であれば自動で行なってくれるためです)

import { superForm, numberProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = numberProxy(form, 'field', { options });
options
{ 
  empty?: 'null' | 'undefined' | 'zero';
  initiallyEmptyIfZero?: boolean;
  delimiter?: "." | "," | undefined;
  taint?: boolean | 'untaint' | 'untaint-form';
}

empty オプション($form ← $proxy)

$proxyに空文字を入力した際に$form.fieldに設定される値を指定します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined
{ empty: 'zero' } '' (空文字) 0

initiallyEmptyIfZero オプション($form → $proxy)

$form.fieldの値が0の場合に空文字に変換するか、0のままにするかのオプションです。
初期値に0を入れておきたいがプレースホルダーをユーザーに見せたい時に有効化すると良いでしょう。

option $form.field $proxy
{ initiallyEmptyIfZero: true } 0 '' (空文字)
{ initiallyEmptyIfZero: false } 0 0

delimiter オプション($form ← $proxy)

少数の区切り文字を指定するオプションです。「.」を指定した場合に「,」を入力した場合には以降の値は切り捨てられますが、「,」を指定した場合は「,」以降は少数として扱われます。小数点をカンマで区切る国の場合、設定すると良いでしょう。

option $proxy $form.field
{ delimiter: '.' } 123.45 123.45
{ delimiter: '.' } 123,45 123
{ delimiter: ',' } 123.45 123.45
{ delimiter: ',' } 123,45 123.45

stringProxy

string型に対するプロキシです。string型のストアになっています。
空文字の変換処理が不要な場合は、基本的にbind:valueで事足りると思います。

import { superForm, stringProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = stringProxy(form, 'field', { options });
options
{
  empty: 'null' | 'undefined';
  taint?: boolean | 'untaint' | 'untaint-form';
}

empty オプション($form ← $proxy)

$proxyに空文字を入力した際に$form.fieldに設定される値を指定します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined
{ empty: 'zero' } '' (空文字) 0

arrayProxy

array型のプロキシです。該当フィールドに対する各種オブジェクト返します。
今まで見てきたProxyと異なりsuperformオブジェクトを引数に渡す必要があります。

<script lang="ts">
  import { superForm, arrayProxy } from 'sveltekit-superforms';

  export let data;

  const superform = superForm(data.form); // The whole superForm object is required
  const { form } = superform; // Deconstruct as usual here

  const { path, values, errors, valueErrors } = arrayProxy(superform, 'field');
</script>
options
{
  taint?: boolean | 'untaint' | 'untaint-all';
}

path, values, errors, valueErrors が利用できます。

説明
path 該当proxyのpath
values(ストア) proxy先の配列
errors(ストア) 配列自体のエラー(例:配列内で最低2件選ばなければならないバリデーションエラーなど)
valueErrors(ストア) 配列内のデータに対するエラー(例:配列内のデータは2文字以上でなければならないバリデーションエラーなど)

formFieldProxy

汎用的なプロキシです。該当フィールドに対する各種オブジェクト返します。
こちらもsuperformオブジェクトを引数に渡す必要があります。
※配列のフィールドはこのproxyで使用できないため、arrayProxyを使いましょう。もしくはindex付きの表記であれば配列内の任意のindexに対するproxyにすることができます。(例: field[0])

<script lang="ts">
  import { superForm, formFieldProxy } from 'sveltekit-superforms';

  export let data;

  const superform = superForm(data.form); // The whole superForm object is required
  const { form } = superform; // Deconstruct as usual here

  const { path, value, errors, constraints, tainted } = formFieldProxy(superform, 'field');
</script>
options
{
  taint?: boolean | 'untaint' | 'untaint-all';
}

path, value, errors, constraints, tainted が利用できます。

説明
path 該当proxyのpath
value(ストア) proxy先の値
errors(ストア) proxy先のエラー
constraints(ストア) スキーマからマップされた検証プロパティを持つオブジェクト https://superforms.rocks/concepts/client-validation#constraints
tainted(ストア) 変更されているか

なお、このプロキシを使って汎用的なテキストコンポーネントを作成することもできます。
https://superforms.rocks/components#a-typesafe-generic-component


fieldProxy

汎用的なプロキシです。指定したフィールドのデータ型そのままのストアになっています。
例えばフィールドがbooleanであればプロキシもbooleanになるようです。

import { superForm, stringProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = stringProxy(form, 'field', { options });
options
{
  empty: 'null' | 'undefined';
  taint?: boolean | 'untaint' | 'untaint-form';
}

empty オプション($form ← $proxy)

$proxyに空文字を入力した際に$form.fieldに設定される値を指定します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined

fileProxy

FileList型のプロキシです。
mutipulで複数選択しても1個しか値は取れません。その場合はfilesProxyやfilesFieldProxyを利用しましょう。

+page.svelte
import { superForm, fileProxy } from 'sveltekit-superforms';
export let data;

const { form } = superForm(data.form);
const proxy = fileProxy(form, 'field', { options });
options
{ 
  empty?: 'null' | 'undefined';
  taint?: boolean | 'untaint' | 'untaint-form';
}

empty オプション($form ← $proxy)

$proxyに空文字を設定した際に$form.fieldに設定する値を指定します。
※ファイル選択でキャンセルし、ファイル選択状態がクリアされた場合などに動作します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined

fileFieldProxy

ファイルのプロキシです。該当フィールドに対する各種オブジェクト返します。
mutipulで複数選択しても1個しか値は取れません。その場合はfilesProxyやfilesFieldProxyを利用しましょう。

<script lang="ts">
  import type { SampleScheme } from '$lib/schemas';
  import SuperDebug, { fileFieldProxy } from 'sveltekit-superforms';
  import type { SuperForm, SuperFormData } from 'sveltekit-superforms/client';

  export let data;

  const superform = superForm(data.form);

  const { path, value, errors, constraints } = fileFieldProxy(superform, 'fileFieldProxy', { options });
</script>
options
{ 
  empty?: 'null' | 'undefined';
  taint?: boolean | 'untaint' | 'untaint-form';
}

path, value, errors, constraints, tainted が利用できます。

説明
path 該当proxyのpath
value(File|FileList型のストア) proxy先の値
errors(ストア) proxy先のエラー
constraints(ストア) スキーマからマップされた検証プロパティを持つオブジェクト https://superforms.rocks/concepts/client-validation#constraints
tainted(ストア) 変更されているか

empty オプション($form ← $proxy)

$proxyに空文字を設定した際に$form.fieldに設定する値を指定します。
※ファイル選択でキャンセルし、ファイル選択状態がクリアされた場合などに動作します。

option $proxy $form.field
{ empty: 'null' } '' (空文字) null
{ empty: 'undefined' } '' (空文字) undefined

filesProxy

FileList型のプロキシです。
mutipulで複数選択にも対応しています。

<script lang="ts">
  import { superForm, formFieldProxy } from 'sveltekit-superforms';

  export let data;

  const superform = superForm(data.form);

  const proxy = filesProxy(superform, 'field');
</script>

fileFieldsProxy

arrayProxyのファイル版で基本的には同じです。

<script lang="ts">
  import { superForm, fileFieldsProxy } from 'sveltekit-superforms';

  export let data;

  const superform = superForm(data.form);

  const { path, values, errors, valueErrors } = arrayProxy(superform, 'field');
</script>

path, values, errors, valueErrors が利用できます。

説明
path 該当proxyのpath
values(FileList型のストア) proxy先の配列値
errors(ストア) 配列自体のエラー
valueErrors(ストア) 配列内のデータに対するエラー

終わりに

Proxy objectsを使うことで煩雑な変換処理を書かずに済み、今後の開発効率が上がりそうですね。
これから使われる方の参考になれば幸いです。
素敵なSuperformsライフをお過ごしください!

Discussion