カテゴリー: FrontEnd

Vue 3とVuex 4とTypeScriptでタイプセーフに開発する

はじめに

Vue 3のComposition APIに対応した、Vuex 4が2月3日にリリースされました。

Vuex 4はTypeScriptをサポートしているため、TypeScriptのサポートが強化された、Vue 3のComposition APIと組み合わせることで、よりタイプセーフなアプリケーションを開発できるようになりました。

そこで、今回は、Vuex 4をComposition API上で、どのように使うことができるか紹介します(Composition APIについては、こちらをご覧ください)

インストール

新規でプロジェクトを作成する場合は、最新のVue CLIでプロジェクトを作成すると、Vuex 4がインストールされるようになっています。

もし、既存のプロジェクトにVuex 4を導入する場合は、以下の通りインストールします。

npm install vuex@next --save
# or
yarn add vuex@next --save

Storeの設定

StoreをTypeScriptで実装する場合は、以下のように、main.tscreateApp()メソッドでstorekeyを渡します。

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { store, key } from "./store";

createApp(App)
  .use(store, key) // storeとInjectionKeyを渡す
  .use(router)
  .mount("#app");

Storeの作成

まず初めに、Storeの作成について見ていきます。複数の本(名前と著者)のデータを保存するStoreをTypeScriptで実装した例がこちらです。

import { InjectionKey } from 'vue'
import { createStore, useStore as baseUseStore, Store } from 'vuex'

export interface Book {
  name: string,
  author: string
}

// ストアの型を定義する
export interface State {
  books: Book[]
}

// InjectionKeyを定義する
export const key: InjectionKey<Store<State>> = Symbol()

// Storeを作成する
export const store = createStore<State>({
  state: {
    books: [{name: 'hoge1', author: 'fuga'}, {name: 'hoge2', author: 'fuga'}]
  },
  getters: {
    getFirstBook: (state) => {
      return state.books ? state.books[0] : {} as Book
    }
  },
  actions: {
    add ({ commit, state }, book: Book) {
      commit('add', { book: book })
    }
  },
  mutations: {
    add (state, { book }) {
      state.books.push(book)
    }
  }
})

// 独自のuserStoreメソッドを定義する
export function useStore () {
  // InjectionKeyをuserStoreメソッドに渡す
  return baseUseStore(key)
}

まず、注目する点は、Storeの作成をcreateStoreメソッドで行うようになった点です。定義したStoreインタフェースを型として渡してます。引数にはStateのほか、GetterActionなどを渡しています。

export const store = createStore<State>({
  state: {
    books: [{name: 'hoge1', author: 'fuga'}, {name: 'hoge2', author: 'fuga'}]
  },
  getters: {
    getFirstBook: (state) => {
      return state.books ? state.books[0] : {} as Book
    }
  },
// 省略

次に注目する点は、InjectionKeyです。StoreをTypeScriptで定義するには、これが必要になります。Storeの型を渡して、InjectionKeyを生成します。

// InjectionKeyを定義する
export const key: InjectionKey<Store<State>> = Symbol()

最後に注目する点は、独自のuseStoreメソッドを定義している箇所です。ComponentのSetup ()メソッド内からStoreにアクセスするには、このuseStoreメソッドを使う必要があります。

加えて、型情報のついたStoreインスタンスを取得するためには、useStore()メソッドに、先ほど生成したInjectionKeyを渡す必要があります。

StoreをComponentから使用する

上記のStoreの使用例はこちらです。

<template>
  <div class="home">
    <div>
      <input v-model="book.name" />
      <input v-model="book.author" />
      <button v-on:click="add">追加</button>
    </div>
    <table>
      <thead>
      <tr>
        <th>著者</th>
        <th>名前</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="book in books" :key="book.name">
        <td>{{ book.author }}</td>
        <td>{{ book.name }}</td>
      </tr>
      </tbody>
    </table>
  </div>
</template>

<script lang="ts">
import { computed, reactive } from "vue"
import { useStore, Book } from '@/store'

export default {
  setup () {
    // 追加する本の入力蘭のv-model
    const book = reactive({
      name: '',
      author: ''
    })
    
    // Storeを取得する
    const store = useStore()

    return {
      book, // v-model
      books: computed(() => store.state.books), // 本の一覧を返す
      add: () => store.dispatch('add', { name: book.name, author: book.author } as Book) // 本を追加する
    }
  }
}
</script>

先ほど述べた通り、ComponentのSetup()メソッド内でStoreにアクセスするためには、useStore()メソッドでStoreインスタンスを取得する必要があります。

// Storeを取得する
const store = useStore()

Storeのパラメータをリアクティブにアクセスしたい場合は、computed()メソッドを定義します。

また、Mutation、もしくは、Actionにアクセスする場合は、methosとして定義します。

return {
  book, // v-model
  books: computed(() => store.state.books), // 本の一覧を返す
  add: () => store.dispatch('add', { name: book.name, author: book.author } as Book) // 本を追加する
}

$storeプロパティに型をつける

$storeプロパティに型をつけるには、型定義ファイルを作る必要があります(型定義ファイルの作成については、こちらをご覧ください)Storeの実装例の型定義ファイルは、以下のこちらです。

import { ComponentCustomProperties } from 'vue'
import { Store } from 'vuex'
import { State } from '@/store'

declare module '@vue/runtime-core' {
  // this.$storeの型を宣言
  interface ComponentCustomProperties {
    $store: Store<State>
  }
}

ComponentCustomPropertiesの型を宣言します。

また、$storeプロパティには以下のようにアクセスします。

<template>
  <div>
    {{ $store.state.books }}
  </div>
</template>

さいごに

Vue 3がリリースされてから大分時間がたちましたが、ようやくVuexがComposition APIに対応してくれました。サードパーティのモジュールを使ってTypeScript対応していた方は、Vuex 4を試してみてはいかがでしょうか。

おすすめ書籍

Hiroki Ono

シェア
執筆者:
Hiroki Ono
タグ: JavaScriptVuejs

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

2週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

4週間 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前