以前読んだ、Software Design 2020 年 9 月号 で Vue.js の特集がありました。
この企画の記事の中で、ライブラリを使わないデータストアについての、紹介があります。
小規模アプリなら十分使えそうなものだとその時感じていました。
このライブラリを使わないデータストア相当のものを、Vue 3 Composition API で書けないかと試したので、メモです。
要件
- 外部のライブラリを使わない
- なるべく簡単に使える
- よくある共通の値を参照しているカウンタを作る
実装
準備
開発環境の準備は、viteで用意する。
| 12
 3
 4
 5
 6
 
 | npm init @vitejs/app app --template vue-ts
 cd app
 
 npm install
 npm run dev
 
 | 
これで Vue.js のよく見る初期画面を確認できる。
データストアを作る
次のようにデータストアを作ってみました。
データストアを作るというよりも、データの作成と更新をコンポーネントから切り出した感じですね。
app/src/store/store.ts| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | import { ref, computed, ComputedRef } from "vue";
 export interface store {
 count: ComputedRef<number>;
 up: () => void;
 down: () => void;
 }
 
 const _count = ref(0);
 
 const up = () => {
 _count.value++;
 };
 
 const down = () => {
 _count.value--;
 };
 
 
 const count = computed(() => _count.value);
 
 export default { count, up, down };
 
 | 
 
自作データストアを使う
続けて、自作したデータストアを使ってみます。
app/src/components/counter.vue| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | <template><div class="counter">
 Count: {{ count }}
 <button @click="up">count ++</button>
 <button @click="down">count --</button>
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from "vue";
 
 import store from "../store/store";
 
 export default defineComponent({
 setup: () => {
 
 const { count, up, down } = store;
 return { count, up, down };
 },
 });
 </script>
 
 <style scoped>
 .counter {
 margin: 10px;
 }
 </style>
 
 | 
 
この counter.vue の呼び出し側は次のようになります。
app/src/App.vue| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 
 | <template><div>
 <Counter />
 <Counter />
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from "vue";
 import Counter from "./components/counter.vue";
 
 export default defineComponent({
 name: "App",
 components: {
 Counter,
 },
 });
 </script>
 
 <style>
 #app {
 font-family: Avenir, Helvetica, Arial, sans-serif;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
 text-align: center;
 color: #2c3e50;
 margin-top: 60px;
 }
 </style>
 
 | 
 
動作確認
npm run dev で起動すると次のようになります。

カウンターのコンポーネント 2 つの間で値を共有して取り扱うことができました。
データストアを provide(2021-05-23 追記)
今回より簡単なただ 1 つの変数を provide する記事を前に書いていたことを、この記事公開してから気が付きました。
今回作ったデータストアも provide で公開して、inject で使用してみます。
ストアの修正
呼び出しに使用するキーを記述します。
app/src/store/store.ts| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | import { ref, computed, ComputedRef, InjectionKey } from "vue";
 export interface store {
 count: ComputedRef<number>;
 up: () => void;
 down: () => void;
 }
 
 
 export const storeKey: InjectionKey<store> = Symbol("store");
 
 const _count = ref(0);
 
 const up = () => {
 _count.value++;
 };
 const down = () => {
 _count.value--;
 };
 
 
 const count = computed(() => _count.value);
 
 export default { count, up, down };
 
 | 
 
provide で登録する
app/main.ts| 12
 3
 4
 5
 6
 7
 8
 
 | import { createApp } from "vue";import App from "./App.vue";
 import store, { storeKey } from "./store/store";
 
 const app = createApp(App);
 
 app.provide(storeKey, store);
 app.mount("#app");
 
 | 
 
inject で呼び出す
provide で登録したデータを、inject で呼び出してみます。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | <template><div class="counter">
 Count: {{ count }}
 <button @click="up">count ++</button>
 <button @click="down">count --</button>
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent, inject } from "vue";
 import { store, storeKey } from "../store/store";
 
 export default defineComponent({
 setup: () => {
 
 
 const { count, up, down } = inject(storeKey) as store;
 return { count, up, down };
 },
 });
 </script>
 
 <style scoped>
 .counter {
 margin: 10px;
 }
 </style>
 
 | 
画面を確認すると、先に作ったものと同様に動きます。
データ部分だけの切り出しを行ってみた今回。
リアクティブなデータで結果を共有し、API のレスポンスをキャッシュできるようにしたりすると、より面白そうです。
ではでは。