vuex-class-componentを使ってVuexをクラススタイルでタイプセーフに書いてみよう
最新バージョンではこのエントリで紹介している書き方で動きません。修正点については次のエントリを参考にしてください。
vuex-class-componentをアップデートしたらAPIが変わっていました | Developers.IO
こんにちは。サービスグループの武田です。
引き続きVueの勉強中です。Vueアプリケーションで状態(≒データ)を管理しようと思ったとき、選択肢はいくつかありますよね。その中でまず候補に挙がるのはVuexではないでしょうか。
ただ現状のVuexはTypeScriptとの相性がよいとは言えません。せっかくなのでクラススタイルで書きたいですし、タイプセーフに書きたいです。探してみるとサポートしてくれるモジュールがいくつか見つかりました。その中でも今回はvuex-class-componentを使ってみました。
Vuexって何?という方はぜひ公式ドキュメントを参照してください。
環境
今回の検証環境は次のような環境になっています。
$ node -v v10.15.3 $ vue -V 3.8.2 $ sw_vers ProductName: Mac OS X ProductVersion: 10.14.5 BuildVersion: 18F132
そのほか、主なモジュールのバージョンは以下となっています。また、vueコマンドがインストールされていない場合は、npm install -g @vue/cli
でインストールします。
プロジェクトを作成して必要なモジュールを追加
まずはVue CLIを利用してプロジェクトを作成します。
$ vue create example-vuex-counter-app-class-style
presetはManualにして、すべてデフォルトを選択します。
最後の項目を選択するとインストールが始まります。インストールが終わったら、続いてvuex-class-componentを追加します。
$ cd example-vuex-counter-app-class-style $ npm install vuex-class-component
これで準備は完了です。
カウンタアプリケーションの実装
今回は、公式ドキュメントからもリンクされているVuexのサンプル実装をvuex-class-componentを使って実装してみます。
またソースコード全体はGitHubに上げてあります。
stateの書き換え
Vuexでは管理する状態を単一オブジェクトで管理します。サンプル実装では次のように書かれています。
state: { count: 0 },
これをクラススタイルに書き換えると次のようになります。 @getter
アノテーションを使っているのが特徴的です。なお元のアプリケーションではnamespaceは利用していませんが、書き換え後は使用しています。特に深い意味はないです。
@Module({ namespacedPath: 'counter/' }) export class CounterStore extends VuexModule { @getter public count: number = 0;
vuex-class-componentではstore外に公開するかどうかでプロパティを次のように宣言します。
- 公開しない
private count: number = 0;
- 公開する
@getter public count: number = 0;
@getter count: number = 0;
- TypeScriptでは
public
がデフォルトのアクセス修飾子なので省略しても同じ
mutationsの書き換え
Vuexではstateの同期的な変更処理をmutaionとして宣言します。引数にstate
を取るのがポイントで、このオブジェクト経由で値を変更します。
mutations: { increment: state => state.count++, decrement: state => state.count-- }
これをクラススタイルに書き換えると次のようになります。 @mutation
アノテーションを使っているのが特徴的です。stateが引数で渡されることはなくなり、this
を使って直接操作できます。
@mutation public increment() { this.count++; } @mutation public decrement() { this.count--; }
またmutationの呼び出し方も変わります。サンプル実装を見ても分かりますがmutationの呼び出しにはcommit
メソッドを使う必要があります。
methods: { increment () { store.commit('increment') }, decrement () { store.commit('decrement') } }
これをクラススタイルに書き換えると次のようになります。commit
メソッドは消え、またmutationが文字列ではなく普通のメソッドとして呼び出せます。素敵です。
private increment() { vxm.counter.increment(); } private decrement() { vxm.counter.decrement(); }
vxm
という見慣れないオブジェクトが登場していますが、これはREADMEで紹介されている Vuex Manager パターン です(パターンとは書かれてないんですけど)。
export const vxm = { counter: CounterStore.CreateProxy(store, CounterStore), };
書き方が変わったところでmutationの呼び出しにcommit
が必要なことは変わりません。そこでvuex-class-componentではプロキシクラスを提供し、プロキシのメソッドを呼び出すと内部的にcommit
を呼び出してくれます。作成したプロキシをまとめて保持するオブジェクトをvxm
としてexportしているわけです。
actionsの追加
サンプル実装には書かれていませんが、Vuexでは非同期な処理はactionとして宣言します。また直接stateを変更するようなことはせず、mutationをcommitすることで間接的に変更します。例として、「countが奇数の場合のみインクリメントする」「1秒遅れてインクリメントする」という2個のactionを考えてみます。
actions: { incrementIfOdd ({ commit, state }) { if ((state.count + 1) % 2 === 0) { commit('increment') } }, incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
そしてこれらを呼び出すコードは次のようになります。actionの呼び出しはdispatch
メソッドを使います。
incrementIfOdd () { store.dispatch('incrementIfOdd') }, incrementAsync () { store.dispatch('incrementAsync') }
これらをクラススタイルで書き換えたものが次のコードです。@action
アノテーションを付与しているだけで、普通のメソッドです。mutationを呼び出すためのcommit
は不要となりました。
@action() public incrementIfOdd() { if ((this.count + 1) % 2 === 0) { this.increment(); } } @action() public async incrementAsync() { return setTimeout(() => this.increment(), 1000); }
そしてこのactionを呼び出しているのが次のコードです。dispatch
メソッドは不要となり、普通のメソッド呼び出しとして書けます。もちろん内部的にはdispatch
メソッドが呼ばれています。素敵です。
private incrementIfOdd() { vxm.counter.incrementIfOdd(); } private incrementAsync() { vxm.counter.incrementAsync(); }
mutationのときと同様にvxm
経由でアクセスしていますね。
まとめ
VueとVuexのどちらもまだ勉強中ですが、少しずつ理解が進んできました。Vuexをクラススタイルで書こうとするプロダクトはほかにもいくつか見つかりましたが、vuex-class-componentが一番自分にフィットしたので試してみました。引き続き精進します!