はじめに
前回の記事では、Svelteの概要について紹介しました。そこで、今回はSvelteのStoreについて、どのように定義してどのように使用するかを紹介します。
SvelteにおけるStoreの概要
始めに、SvelteにおけるStoreについて、概要を説明します。
Storeの条件
Svelteでは、以下の条件に当てはまるものがStoreとみなされます。
- constとして定義されている
- subscribe関数および、unsubscribe関数が実装されている
また、subscribe関数および、unsubscribe関数には、それぞれ守るべき条件が定められていますが、ここでは割愛します。詳しく知りたい方は、こちらを参照してください。
Storeの種類
Svelteでは、できることが異なる3種類のStoreが定義できます。
- writable(値の更新および取得が可能)
- readable(値の取得のみ可能)
- derived(他の複数のStoreと依存関係にある)
次からは、これらのStoreの実装と使い方を説明します。
Storeの実装と使い方
これらのStoreは、svelte/storeモジュールを使って実装します(このモジュールを使わずに実装することもできます。詳しくはこちらを参照してください)
始めに、最も基本的なwritableのStoreについて、公式サイトの実装例を見ていきます。
Writable Store
外部のモジュールから設定可能な値を持つStoreです。
実装例
1 2 3 | import { writable } from 'svelte/store'; export const count = writable(0); |
Storeの作成は
writable(value: any, (set: (value: any) => void) => () => void)
関数で行ます。第1引数は初期値で、第2引数はsubscriberが0から1に変わった時にのみ実行される関数(省略可能)です。
1 2 3 4 5 6 | const count = writable(0, () => { // 何らかの処理(subscribeされている数が0から1に変わった時のみ実行される) return () => { // 何らかの処理(subscribeされている数が0なった時のみ実行される) } }); |
使用例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <script> // 1. import { onDestroy } from 'svelte'; import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte'; // 2. let count_value; // 3. const unsubscribe = count.subscribe(value => { console.log(value); count_value = value; }); // 4. onDestroy(unsubscribe); </script> <h1>The count is {count_value}</h1> <Incrementer/> <Decrementer/> <Resetter/> |
1 2 3 4 5 6 7 8 9 10 11 12 | <script> import { count } from './stores.js'; function decrement() { // 5. count.update(n => n - 1); } </script> <button on:click={decrement}> - </button> |
1 2 3 4 5 6 7 8 9 10 11 12 | <script> import { count } from './stores.js'; function increment() { // 6. count.update(n => n + 1); } </script> <button on:click={increment}> + </button> |
1 2 3 4 5 6 7 8 9 10 11 12 | <script> import { count } from './stores.js'; function reset() { // 7. count.set(0); } </script> <button on:click={reset}> reset </button> |
それぞれの処理を個別に説明します。
- 必要なモジュールを読み込みます
- Storeの値を反映させる変数を定義します
- Storeをsubscribeし、unsubscribe関数を受取ります
- モジュールの解放時に、Storeをunsubscribeします
- Storeの値をincrementします
- Storeの値をdecrementします
- Storeの値をresetします
使用例2
使用例1では、Storeの値を反映させる変数をわざわざ定義しましたが、実はこれは不要です。以下の例のように、
$count
で直接参照することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <script> import { count } from './stores.js'; import Incrementer from './Incrementer.svelte'; import Decrementer from './Decrementer.svelte'; import Resetter from './Resetter.svelte'; </script> <!-- $countで直接参照できる --> <h1>The count is {$count}</h1> <!-- $countでbindingできる --> <input bind:value={$count}> <Incrementer/> <Decrementer/> <Resetter/> |
ただし、注意点として、このようにStoreを参照するには、Store側がトップレベルスコープで宣言されている必要があります。また、$で始まる名前はすべてStoreを参照しているものとみなされるので、変数名として使用することはできません。
また、通常の変数と同じように、bindingすることができます。
Readable Store
外部のモジュールから参照可能な値を持つStoreです。外部のモジュールから値を更新することはできません。
実装例
1 2 3 4 5 6 7 8 9 10 11 | import { readable } from 'svelte/store'; export const time = readable(new Date(), function start(set) { const interval = setInterval(() => { set(new Date()); }, 1000); return function stop() { clearInterval(interval); }; }); |
Storeの作成は
readable(value: any, (set: (value: any) => void) => () => void)
関数で行ます。引数の内容はwritableを同じですが、第二引数が必須となっています(Storeを更新する手段がなくなってしまうため)
例では、1秒毎に現在時刻に上書きされます。
使用例
1 2 3 4 5 6 7 8 9 10 11 12 | <script> import { time } from './stores.js'; const formatter = new Intl.DateTimeFormat('en', { hour12: true, hour: 'numeric', minute: '2-digit', second: '2-digit' }); </script> <h1>The time is {formatter.format($time)}</h1> |
使い方については、Writable Storeの使用例2と変わりません。
Derived Store
他の複数のStoreと依存関係を持つStoreです。これらの依存関係が更新されるたびにコールバックが実行されます。
実装例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import { readable, derived } from 'svelte/store'; // 1. export const time = readable(new Date(), function start(set) { const interval = setInterval(() => { set(new Date()); }, 1000); return function stop() { clearInterval(interval); }; }); // 2. const start = new Date(); // 3. export const elapsed = derived( time, $time => Math.round(($time - start) / 1000) // 4. ); |
Storeの作成は
derived(a, callback: (a: any, set: (value: any) => void) => void | () => void, initial_value: any)
関数で行ます。
それぞれの処理を個別に説明します。
- 参照のみ可能なStoreを定義する
- 計算にしようするため、開始時刻を保存しておく
- 1で定義したStoreと依存関係のあるStoreを定義する
- Storeの値を1の値(現在時刻)から開始時刻を引いた値に更新する
実装例2
また、引数には配列を渡すこともできますし、readableと同様に、Set関数のコールバックを渡すこともできます。
1 2 3 4 5 6 7 8 9 10 11 | import { derived } from 'svelte/store'; const storeC = derived([storeA, storeB], ([$storeA, $storeB], set) => { const interval = setInterval(() => { set($storeA + $storeB); }, 1000); return () => { clearInterval(interval); }; }, 0); |
使用例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <script> import { time, elapsed } from './stores.js'; const formatter = new Intl.DateTimeFormat('en', { hour12: true, hour: 'numeric', minute: '2-digit', second: '2-digit' }); </script> <h1>The time is {formatter.format($time)}</h1> <p> This page has been open for {$elapsed} {$elapsed === 1 ? 'second' : 'seconds'} </p> |
こちらも使い方については、Writable Storeの使用例2と変わりません。
Storeの応用
Storeにはsubscribe関数のような必須の関数の他に、独自の関数を定義することもできます。
実装例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import { writable } from 'svelte/store'; function createCount() { const { subscribe, set, update } = writable(0); return { subscribe, increment: () => update(n => n + 1), decrement: () => update(n => n - 1), reset: () => set(0) }; } export const count = createCount(); |
例のように、increment、decrement、resetの関数を含むことができ、外部のモジュールからの利用を制限することができます。
使用例
1 2 3 4 5 6 7 8 9 | <script> import { count } from './stores.js'; </script> <h1>The count is {$count}</h1> <button on:click={count.increment}>+</button> <button on:click={count.decrement}>-</button> <button on:click={count.reset}>reset</button> |
このように、外部のモジュールからは値の参照、increment、decrement、resetのみが可能です。
さいごに
SvelteのStoreの実装と使い方について紹介しました。Svelte自体にStore機能が内包されているため、Vuexのように外部モジュールを導入しなくて良い点もポイントだと思います。