Upgrade to Pro — share decks privately, control downloads, hide ads and more …

createGlobalState と createSharedComposable #vue...

Avatar for Ryutaro Yako Ryutaro Yako
October 30, 2026
2

createGlobalState と createSharedComposable #vuefes_aftertalk

2026年10月30日(金)に開催された「Vue Fes Japan 2026 After Talk」での発表資料です。

https://uniquevision.connpass.com/event/409085/

Avatar for Ryutaro Yako

Ryutaro Yako

October 30, 2026

Transcript

  1. createGlobalState と createSharedComposable 2026.10.30 Vue Fes Japan 2026 After Talk

    / RyutaroYako Copyright ©Unique Vision Company. All Rights Reserved.
  2. 事業内容 当社はソーシャルメディアを利用した、ソーシャルメディア総合管理ツールや X・LINE・Instagram・TikTokなどSNSマーケティングツールを多数提供しています。 Belugaスタジオ Beluga キャンペーン その他 X/Facebook/Instagramなどのアカウ フォロー&リポストに代表されるXのイ SNSキャンペーンに連動した、生成AIを

    ントを一元管理できるソーシャルメディ ンスタントウィンをはじめとしたキャン 活用したシステムやプラットフォームが ア総合管理ツールです。企業のSNS運用 ペーンを複数展開しています。質の高い 提供する独自ツールやシステム開発も順 をサポートします。 キャンペーンの実施を支援しています。 次行っています。 Copyright ©Unique Vision Company. All Rights Reserved.
  3. 実装: ref をキャッシュしてシングルトン化 題材: ライト/ダークのテーマ設定を localStorage に保存する type Theme =

    "light" | "dark"; let theme: Ref<Theme> | undefined; export function useTheme() { if (!theme) { theme = useLocalStorage<Theme>("theme", "light"); } return theme; } useLocalStorage は localStorage と同期する ref を返し、値が変わると内部の watch が localStorage に書き込む 初回だけ useLocalStorage を呼び、作った ref をモジュールスコープの変数にキャッシュする 2 回目以降は、キャッシュした ref をそのまま返す ポイント: useLocalStorage が呼ばれるのは、最初の 1 回だけ Copyright ©Unique Vision Company. All Rights Reserved.
  4. 期待したこと: どこで切り替えても localStorage に保存される <!-- ページ A --> <script setup

    lang="ts"> const theme = useTheme(); // ← ここで初めて呼ばれる </script> <template> <select v-model="theme"> <option value="light">ライト</option> <option value="dark">ダーク</option> </select> </template> <!-- ページ B --> <script setup lang="ts"> const theme = useTheme(); // キャッシュ済みの ref を受け取る const setDark = () => { // localStorage に保存されるはず theme.value = "dark"; }; </script> どのコンポーネントからでも ref を共有できる どこで値を変えても localStorage に保存される Copyright ©Unique Vision Company. All Rights Reserved.
  5. 起こったこと: localStorage の値が変わらない 再現手順 ページ A を開く ページ B に移動してダークに切り替える

    ページ B での挙動: 切り替え前 切り替え後 ref の値 "light" localStorage の値 "light" theme.value = "dark" ref の値 "dark" localStorage の値 "light" 画面の表示(ref の値)は切り替わった localStorage に値が保存されなかった エラーは出ず画面も正しく見えるので、ずっと問題に気づけなかった Copyright ©Unique Vision Company. All Rights Reserved. 変わらない
  6. 起こっていたこと: ページ遷移によって localStorage への保存が止まっていた ページ A を開く ページ B へ移動する

    useTheme() を初めて呼ぶ localStorage に保存するための watch が作られる ページ A が unmount される watch も一緒に止まる ダークに切り替える ref の値は変わる localStorage には保存されない キャッシュされていたのは ref だけで、watch はページ A と一緒に止まってしまっていた 2 回目以降の呼び出しは ref を返すだけで、watch は作り直されない その結果…localStorage には保存されない Copyright ©Unique Vision Company. All Rights Reserved.
  7. 原因: watch はコンポーネントの effectScope に登録される Vue はエフェクトを effectScope という単位でまとめて管理している コンポーネントは、それぞれ自分の

    effectScope を持っている unmount されると effectScope が停止し、登録された watch も一緒に破棄される effectScope(動作中) effectScope(停止) unmount const count = ref(0); watch(count, () => { /* ... */ }); scope.stop() const count = ref(0); watch(count, () => { /* ... */ }); ページ A の watch は、ページ A の effectScope と一緒に止まっていた Copyright ©Unique Vision Company. All Rights Reserved.
  8. 直し方: VueUse の createGlobalState を使う // useTheme.ts type Theme =

    "light" | "dark"; export const useTheme = createGlobalState(() => { return useLocalStorage<Theme>("theme", "light"); }); 初回だけ初期化してキャッシュする点は、自前の実装と同じ 違いは、 createGlobalState では初期化が effectScope(true) の中で行われること effectScope(true) とすると、どのコンポーネントにも属さず、unmount の影響を受けなく なる そのため、localStorage に保存するための watch は、ページ A と一緒に止まらない Copyright ©Unique Vision Company. All Rights Reserved.
  9. もう 1 つの似た仕組み: createSharedComposable // useTheme.ts type Theme = "light"

    | "dark"; export const useTheme = createSharedComposable(() => { return useLocalStorage<Theme>("theme", "light"); }); VueUse には、composable を共有するための仕組みがもう 1 つある 使い方は createGlobalState とほぼ同じ 違いは、使っているコンポーネントがなくなったときに状態を破棄すること 次に呼ばれたときは、最初から作り直される このバグは、createSharedComposable でも直るのか…? Copyright ©Unique Vision Company. All Rights Reserved.
  10. createSharedComposable の実装: 使っているコンポーネントを数える function createSharedComposable(composable) { let subscribers = 0;

    let state, scope; const dispose = () => { subscribers -= 1; if (scope && subscribers <= 0) { scope.stop(); state = scope = undefined; } }; return (...args) => { subscribers += 1; if (!scope) { scope = effectScope(true); state = scope.run(() => composable(...args)); } tryOnScopeDispose(dispose); return state; }; } 呼ばれるたびに、利用者の数を 1 増やす 最初の 1 回だけ、 effectScope(true) の中で composable を実行する( createGlobalState と 同じ) tryOnScopeDispose で、 呼び出したコンポーネントの effectScope に dispose を登録する そのコンポーネントが unmount されると、 dispose が呼ばれる dispose は利用者の数を 1 減らし、0 人になった ら effectScope を止めて状態を捨てる VueUse 14.3.0 の実装から、SSR 用の分岐を省いて簡略化したもの Copyright ©Unique Vision Company. All Rights Reserved.
  11. 内部では 2 種類の effectScope が使い分けられている watch などのエフェクトは、どこにも属さない effectScope の中で動かす いつ破棄するかは、呼び出したコンポーネントの

    effectScope を使って数える ページ A の effectScope effectScope(true) tryOnScopeDispose(dispose); unmount で利用者を 1 減らす ページ B の effectScope tryOnScopeDispose(dispose); useLocalStorage("theme", "light"); // localStorage に保存するための watch 0 人になったら scope.stop() 利用者: 2 人 ほかのコンポーネントが使っている間は、watch は止まらない Copyright ©Unique Vision Company. All Rights Reserved.
  12. ページを移動すると: 利用者が 0 人になり、作り直される ページ A を開く 利用者が 1 人になる

    localStorage に保存するための watch が作られる ページ B へ移動する ページ A が unmount される 利用者が 0 人になり、状態が破棄さ れる ページ B の setup 利用者が 1 人になる ref と watch が作り直される 移動先の setup 実行よりも先に移動元の effectScope が停止する そのため、ページ A と B の両方で使っていても、利用者は一度 0 人になる バグは watch が止まってしまうことが原因だったので、これでも直る! Copyright ©Unique Vision Company. All Rights Reserved.
  13. もともとの実装との違い: ref と watch がまとめて作り直される // useTheme.ts type Theme =

    "light" | "dark"; export const useTheme = createSharedComposable(() => { return useLocalStorage<Theme>("theme", "light"); }); ページを移動すると、ref と watch がまとめて捨てられ、作り直される useLocalStorage は、作り直されるときに localStorage から値を読み直してくれる Copyright ©Unique Vision Company. All Rights Reserved.
  14. まとめ 共有する composable は、状態をいつまで持ち続けたいかで選ぶ createGlobalState createSharedComposable 状態を破棄するタイミング 破棄しない 利用者が 0

    人になったとき ページ移動後の状態 引き継ぐ 作り直す 向いているもの アプリ全体で使う状態 使われている間だけ動かす処理 Copyright ©Unique Vision Company. All Rights Reserved.