NuxtJS+VuexとTypeScriptを組み合わせるときの不満は主に:
-
this.$store
は型情報がない=補完もチェックも効かない - ミューテーションを
'increment'
みたいに文字列でcommit
する=補完もチェックも効かない
これじゃあせっかくTypeScriptにする意味が半減してしまう。そこで、公式ページに紹介されていたvuex-class-componentを使ってみる。1
サンプル(ライブラリ導入前)
よくあるシンプルなカウンターのサンプル。
import { MutationTree } from 'vuex';
// 本当はクラスで書きたい……
export const state = () => ({
count: 0
});
export type RootState = ReturnType<typeof state>;
export const mutations: MutationTree<RootState> = {
increment: state => state.count++
};
export const state = () => ({});
<template>
<div>
<p>count: {{ count }}</p>
<button @click="increment()">
+
</button>
</div>
</template>
<style></style>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component
export default class Counter extends Vue {
public get count(): number {
return this.$store.state.counter.count; // $storeはany 補完やチェックが効かない
}
public increment(): void {
this.$store.commit('counter/increment'); // 文字列なので補完やチェックが効かない
}
}
</script>
ライブラリ導入
$ yarn add -D vuex-class-component
ストア | Nuxt TypeScriptによると、バージョン2.2.1現在、New APIの実装はまだNuxtJSとの互換性が完璧ではなく、今回はOld APIを使う必要がありそうだ。
import { Module, mutation, VuexModule } from 'vuex-class-component';
// クラスで書けた!
@Module({ namespacedPath: 'counter', target: 'nuxt' })
export class CounterStore extends VuexModule {
public count = 0;
@mutation increment(): void {
this.count++;
}
}
import Vuex from 'vuex';
import { CounterStore } from './counter';
export const store = new Vuex.Store({
modules: {
counter: CounterStore.ExtractVuexModule(CounterStore)
},
strict: false // 下記のVXMを使うのに必要
});
export interface VXM {
counter: CounterStore;
}
// ストアに型情報つきでアクセスできるプロキシオブジェクト
export const vxm: VXM = {
counter: CounterStore.CreateProxy(store, CounterStore)
};
上記のvxm
を使うと、以下のように型情報つきでストアにアクセスできる。
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
import { vxm } from '@/store';
@Component
export default class Counter extends Vue {
public get count(): number {
return vxm.counter.count; // 補完とチェックが効く!
}
public increment(): void {
vxm.counter.increment(); // メソッドとして呼び出せる!
}
}
</script>
ストアに新しいクラスを追加するたびにstore/index.ts
のstore
とVXM
とvxm
を修正する必要はあるが、まぁこれは許容範囲とする。
VXMをNuxtJSのプラグイン化する
コンポーネントで毎回vmx
をインポートするのは面倒なので、NuxtJSのプラグイン機能を使って、this.$store
と同じように、this.$vxm
でアクセスできるようにしてみる。
import Vue from 'vue';
import { vxm } from '@/store';
Vue.prototype.$vxm = vxm;
- plugins: [],
+ plugins: ['~/plugins/vxm.ts'],
これでプラグインの追加は完了なのだが、Vue
インターフェースには$vxm
の情報がないため、TypeScript的には使えない。
そこで、前回追加したvue-shim.d.ts
ファイルにvxm
の型定義を追加し、以下のようにする。
import Vue from 'vue';
import { VXM } from './store';
declare module '*.vue' {
export default Vue;
}
// 既存の型定義に追加する記法
declare module 'vue/types/vue' {
interface Vue {
$vxm: VXM;
}
}
コンポーネント側は以下のように修正。
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
-import { vxm } from '@/store';
@Component
export default class Counter extends Vue {
public get count(): number {
- return vxm.counter.count; // 補完とチェックが効く!
+ return this.$vxm.counter.count; // 補完とチェックが効く!
}
public increment(): void {
- vxm.counter.increment(); // メソッドとして呼び出せる!
+ this.$vxm.counter.increment(); // メソッドとして呼び出せる!
}
}
</script>
これで、this.$store
と同じような感覚で使える型情報つきのthis.$vxm
が手に入った。
とりあえずこの構成で開発してみる。
参考にしたページ
- ストア | Nuxt TypeScript
- nuxt+typescript+vuex-class-component+vuexfireに成功しました
- vuex-class-componentを使ってVuexをクラススタイルでタイプセーフに書いてみよう
-
vuex-module-decoratorsの方が有名なようなのだが、Usageに
@Action({ commit: 'increment' })
と文字列で指定してるところがあったので、希望に沿わなかった。 ↩