Svelte 5の「Function bindings」はいいぞ

に公開

こんにちは、かめです。

今回はSvelte 5から登場した「Function bindings」を使う機会があったのですが、参考になる記事があまり見当たらなかったので書いてみます。

対象読者

  • Svelte 5 の Function bindings機能の使い方について知りたい方
  • コンポーネントと変数の型が異なっている時に、簡単に変換したい方

Function bindings

Function bindingsは、Svelte 5.9.0から登場した機能です。
bind:property={get, set}を使用して、getsetを関数として指定し、バリデーションや変換を行うことができます。(サイトより引用)
https://svelte.jp/docs/svelte/bind#Function-bindings

とてもシンプルで、使いやすそうな機能ですね。

使い方

少しわかりにくいのですが、bind:property={get, set}のget/setはinputやコンポーネントのプロパティから見た視点になるので注意が必要です。

  • getは、プロパティが値を取得するための関数を設定
    • 開発者から見たらプロパティへのset
  • setは、プロパティが値を設定するための関数を指定
    • 開発者から見たらプロパティからのget
<input bind:value={
  () => value, // value変数の値をvalueプロパティに設定する
  (v) => value = v.toLowerCase()} // valueプロパティの値を小文字に変換してvalue変数に代入
/>

読み取り専用バインディングをしたい場合は、getにnullを設定し、値を設定する関数を渡さないことで実現できます。

<div
  bind:clientWidth={null, redraw}
  bind:clientHeight={null, redraw}
>...</div>

想定ユースケース

ドキュメントにあるように、変換・バリデーションに向いていそうです。
変換は、以下のようなケースが考えられます。

ケース 変数 プロパティ
数値 ↔️ 数値(百万単位) number型 number型 1000000 ↔️ 1
文字列 ↔️ 真偽値 string型 boolean型 "1" ↔️ true
文字列 ↔️ 日付 string型 Date型 '2025/07/25' ↔️ Date(2025, 7, 25)
文字列 ↔️ 配列 string型 string型配列 '田中太郎, 山田花子' ↔️ ['田中太郎', '山田花子']

バリデーションは、入力値がエラーになった際に、エラーメッセージを表示したり、トーストを表示したりといったことに使えそうです。

今まではどうしていたか

Function bindingsがないときの双方向変換は、無限ループにならないよう考慮する必要があり面倒でした。
私は、Superformsが使える場合には、proxy objectsを使い変換していました。使えない場合は無限ループにならないように注意してリアクティブな変数やイベントで変換していました。
より簡単にできるのであれば、とても喜ばしいことです。

(手前味噌ですが、superforms の proxy objectsの使い方については以下を参照ください。)
https://zenn.dev/macloud/articles/bf4a6bd753f6b8

やってみる (変換編)

検証時のSvelteのバージョン:v5.36.17

さっそくユースケースに上げたパターンを試してみます。

数値 ↔️ 数値(単位変換)

例では入力簡略化のために100万単位で入力できるようにし、変数には実数値が入るように変換しています。

<script lang="ts">
  let numberValue = $state<number>(1000000)
</script>

<div>numberValue: {numberValue}</div>
<input
  type="number"
  bind:value={
    () => numberValue / 1000000,
    (v: number) => numberValue = v * 1000000}
/>百万円

文字列 ↔️ 真偽値

例では、文字列の"0"をfalseに、文字列の"1"をtrueに変換しています。

<script lang="ts">
  let stringValue = $state<'1'|'0'>('1');
</script>

<div>stringValue: {stringValue}</div>
<input
  type="checkbox"
  bind:checked={
    () => stringValue === "0" ? false : true,
    (v: boolean) => stringValue = v ? "1" : "0"
  }
/>

文字列 ↔️ 日付

例では、文字列とDateを変換しています。(タイムゾーンには気をつけましょう)

<script lang="ts">
  let dateValue = $state<Date | undefined>(new Date(2025, 6, 25));
</script>

<div>dateValue: {dateValue ? JSON.stringify(dateValue) : 'undefined'}</div>
<input
  type="date"
  bind:value={
    () => dateValue?.toISOString().split('T')[0],
    (v: string | undefined) => dateValue = v ? new Date(v) : undefined
  }
/>

文字列 ↔️ 配列

例では半角カンマを区切り文字として、文字列と配列を変換しています。

<script lang="ts">
  let arrayValue = $state(['田中太郎', '山田花子']);
</script>

<div>arrayValue: {JSON.stringify(arrayValue)}</div>
<input
  type="text"
  bind:value={
    () => arrayValue.join(', '),
    (v: string) => arrayValue = v.split(',').map(item => item.trim())
  }
/>

やってみる (バリデーション編)

バリデーションライブラリを使っていれば、わざわざ使うことはないと思いますが、例として1つ挙げておきます。

チェックボックスがチェックされていない場合にメッセージを表示する

例では、checkがfalseの場合に、メッセージを表示しています。

<script lang="ts">
  let booleanValue = $state(true);
  let booleanMessage = $state('');
</script>

<div>booleanValue: {booleanValue}</div>
<input
  type="checkbox"
  bind:checked={
    () => booleanValue,
    (v: boolean) => {
      booleanValue = v
      if (v) {
        booleanMessage = '';
      } else {
        booleanMessage = 'チェックは必須です。';
      }
    }
  }
/>
<div>{booleanMessage}</div>

その他のよく使うbind値の検証

bind:group

以下のエラーが出てしまいます。どうやら使えないようです。

`bind:group` can only bind to an Identifier or MemberExpression
https://svelte.dev/e/bind_group_invalid_expressionsvelte(bind_group_invalid_expression)

まとめ

Svelte 5.9.0から登場したFunction bindingsを試してみました。
頻繁に使うような機能ではないですが、痒いところに手が届く機能なので覚えておいて損はないかと思います。
まだまだSvelte5のキャッチアップができていないので、また便利なものがあったら取り上げようと思います。

雑記

AIエージェント開発時代になって、ブログを書く必要性を感じなくなっていました。
ただ、最近は

  • AIに書かせてもうまくいかない
  • まだAIの学習が進んでいないもの
  • 記事に書けば将来の自分が楽になる(かも)!
    という考えに変わってきて、記事を書くモチベーションが多少上がりました。
    待っていろAI、今餌をやるからな・・・という気持ちでこの記事を書いています。
    餌を与えて自分の仕事を楽にしよう。
    ※この記事は校正にのみAIを使用しています。

Discussion