TL;DR
svelte/storeを使いましょう。
はじめに
カスタムフックは次のようなやつです。
// カスタムフックの定義
const useCounter = () => {
const [count, setCount] = useCounter(0)
const increment = () => setCount(prev => prev + 1)
return { count, increment }
}
// 使う側
const App = () => {
const { count, increment } = useCounter()
return (
<>
<span>{count}</span>
<button onClick={increment}>+1</button>
</>
)
}
(シンプルさを求めて簡略化しています。実際はuseCallbackを使うかなどを考える必要があると思います。)
カスタムフックにより、ステートとロジックを外部に切り出して再利用可能にしたり、複雑なロジックを隠蔽して見通しを良くしたりすることができます。
これを、Svelteでもやりたい。VueだとCompositionAPIでカスタムフックを実現できるますが、Svelteではどうでしょうか。
普通に書いてみると(うまく行かない例)
SvelteではReactでいうステートを表現するのにletを使います
<script>
let count = 0
</script>
これを外部のファイルに切り出して見ましょう。
export const useCounter = () => {
let count = 0
const increment = () => count++
return { count, increment }
}
svelteファイルで使ってみます。
<script>
import { useCounter } from './counter'
const { count, increment } = useCounter()
</script>
<span>{count}</span>
<button on:click={increment}>+1</button>
実行結果
カウントが全く増えません。
svelteファイルのscript部分でletを使えばリアクティブな値になりますが、外部のjsファイルでletで宣言してもただの変数です。
外部に切り出せるものといえば、、、
Svelteで外部に切り出せる、リアクティブな値といえば標準搭載されているstoreです。
storeを使えば、以下のように動作させることができます。
import { writable } from 'svelte/store'
export const useCounter = () => {
const { subscribe, update } = writable(0)
const increment = () => update(prev => prev + 1)
return { count: { subscribe }, increment }
}
<script>
import { useCounter } from './counter'
const { count, increment } = useCounter()
</script>
<span>{$count}</span>
<button on:click={increment}>+1</button>
こちらで試せます。(REPLで共有しやすいのもsvelteの良いところ)
https://svelte.dev/repl/7580c4426c1947d8aa3d149a05bdc895?version=3.38.3
svelteファイル内で使うときは、$count
と$マークを付ける必要があることに注意しましょう。
また、subscribeの挙動がわからない場合は、公式ドキュメントのcustom-storeの章を読みましょう。
ちなみに、このようにsvelte/storeを使って外部にリアクティブな値を切り出しているSvelteライブラリも多いです。
フォームライブラリの felteやsvelte-use-formなどが一例です。
あとがき
Svelteでカスタムフックを使いたくなったらstoreを使うのが一つの方法です。
つまるところ、SvelteのstoreはReduxやVuexのストアみたいなグローバルステート以外の役割も持っているということです。
ということもあり、筆者はSvelteのstoreをグローバルステートとして使う場合は~Store
もしくは~State
などの命名するのが良いと考えています。こうすることで、カスタムフックで使うstore(グローバルステートではない)と区別しやすくなるはずです。