svelte5の$stateをexportする

更新


プログラミング/svelte

目次

$state ルーンについて

svelte5 では「変更したら表示を更新してほしい変数」おまじない(Rune)を付けることになった。

例えば次の例で title は $state というおまじないがかかっているので表示がリアクティブに変化するけれど、title2 の方はおまじないがないので変更されても表示は変化しない。

+page.svelte → [試すにはこちら]

LANG:html
<script>
  let title=$state("こんにちは"); // こちらは変更のたびリアクティブに表示が変化する
  let title2="こんにちは";        // こちらは変更が表示の更新をトリガーしない
</script>

<h1>{title}</h1> <!-- 変化する -->
<button onclick={()=> title='クリックしましたね?'}>クリック</button>

<h1>{title2}</h1> <!-- 変化しない -->
<button onclick={()=> title2='クリックしましたね?'}>クリック</button>

$state 変数を export することでこれまで store を使っていたような使用用途に使える

使えるのだけれど、いろいろ制約があるので何も知らないとエラーが出まくる。

// The `$state` rune is only available inside `.svelte` and `.svelte.js/ts` files at Module.rune_outside_svelte 
// $state ルーンは .svelte または .svelte.js/ts ファイルでのみ使える
// つまり、コンポーネント以外から使いたければファイルに .svelte.ts という拡張子を付ける必要がある

// Cannot export state from a module if it is reassigned. Either export a function returning the state value or only mutate the state value's properties
// let で定義された再代入可能な $state 変数は export できない
// $state の値を返す関数を export するか、$state 変数のプロパティを変更する形にする必要がある

// `$state(...)` can only be used as a variable declaration initializer or a class field
// `$state(...)` は変数定義時の初期化あるいはクラスフィールドにしか使えない

みたいのが出て困った。

ということで、使い方としては以下の3通り?

[試すにはこちら]

リンク先では test.svelte.ts というファイル名が使えなかったので test.svelte となっている。

test.svelte.ts

LANG:ts
// let で定義した $state 変数の値を返す関数や、値を変更する関数を export する
// これらを使った表示部分はリアクティブになる
let counter = $state(0);
export const s = { 
  get count() { return counter; },
  increment() { counter += 1 },
};

// クラスフィールドとして $state 変数を定義し、クラスオブジェクトを export する
// この値はリアクティブなだけでなく直接書き換え可能になる 
class T {
  count = $state(0);
  increment() { this.count += 1 };
}
export const t = new T();

// $state オブジェクトを export して、そのプロパティを書き換える
export const u = $state({ count: 0 });

どの場合でも、それらを使った表示はちゃんとリアクティブになる。

+page.svelte

LANG:html
<script lang="ts">
  import { s, t, u } from './test.svelte';
</script>

{s.count} <br/>
<br/>
<button onclick={()=>s.increment()}>Increment</button><br/>
<br/>

{t.count} <br/>
<br/>
<button onclick={()=>t.increment()}>Increment</button><br/> 
<button onclick={()=>t.count -= 1}>Decrement</button><br/> <!-- 直接変更も可能 -->
<br/>

{u.count} <br/>
<br/>
<button onclick={()=>u.count += 1}>Increment</button><br/> <!-- 直接変更可能 --> 
<button onclick={()=>u.count -= 1}>Decrement</button><br/> <!-- 直接変更可能 -->
<br/>

雑感

  • 確かにこれなら stores は必要なくなる
  • 自由に書き換えて構わないなら $state({count: 0}) の形が楽。
  • 変更方法を縛りたければアクセサーのみを export することになる

手抜き国際化のテストコード

  • 日本語と英語に対応するサイトを作り、locale の切り替えで表示を変化させたい
  • 国際化文字列は "English text.<>日本語文書" のように <> で区切って前半が英語環境用、後半が日本語環境用にしてある
  • localize の l を取って、関数に i18n.l("English text.<>日本語文書") のように渡すと、現在のロケール設定に従って "English text." または "日本語文書" が返る
  • ロケール設定は i18n.locale: "en" | "ja" で指定する

i18n を $state としておくと、i18n.locale の書き換え時にすべての i18n.l( ) への参照がリアクティブに再評価されることになる。

[テストするにはこちら]

i18n.svelte.ts

LANG:ts
export const i18n = $state({
  // 現在のロケール設定 'en' または 'ja'
  locale: 'en' as 'en' | 'ja',

  // 国際化文字列 s に対して、ロケール設定に応じて <> の前または後を返す
  l: (s: string) => s.split('<>')[i18n.locale == 'en' ? 0 : 1]
});

+page.svelte

LANG:html
<script lang="ts">
  import { i18n } from './i18n.svelte';
  const { l } = i18n; // 短い名前でアクセスできるようにしておく

  function switchLocale() {
    i18n.locale = (i18n.locale == 'en') ? 'ja' : 'en';
  }
</script>

{l('Hello!<>こんにちは!')}<br />

<button onclick={switchLocale}>{l('日本語に変換<>Switch to English')}</button>

いい感じだ。

コメント・質問





Counter: 153 (from 2010/06/03), today: 1, yesterday: 3