2018.11.27
TypeScriptでのvuexコーディングについてはまだデファクトスタンダードな書き方が無い。2018/10/4に出来たばかりのvuex-simple
が書きやすそうだったので試した記録。
https://github.com/sascha245/vuex-simple
前提
vue-cli(^3.0.0)によりプロジェクトをsetupしてます。
$ vue create ts-project
# 色々聞かれるけど、vue-router, vuex, typescriptあたりを有効にする
# "vue": "^2.5.17",
# "vue-router": "^3.0.1",
# "vuex": "^3.0.1",
# "vuex-simple": "^2.0.1"
使い方
インストール
普通
$ npm i vuex-simple
# or
$ yarn add vuex-simple
storeの書き方
とてもわかりやすい。vuex使ったことある人ならコード読めば何やってるかわかる。
// 適当なvuex module定義
import { Getter, State, Mutation, Action } from 'vuex-simple';
export class SampleModule {
@State() public param1: int = 3;
@State() public param2: string | null = null;
@Mutation()
public setParam1(param: int) {
this.param1 = param;
}
@Getter()
public get param1x3() { return this.param1 * 3; }
@Action()
public async increment1() {
await new Promise(r => setTimeout(r, 200));
this.setParam1(this.param1 + 1);
}
// @Module()でnested moduleにも対応できる
}
// moduleをまとめ上げて、vuex-simpleで初期化
import Vue from 'vue';
import Vuex from 'vuex';
import { State, Module, createVuexStore, Mutation } from 'vuex-simple';
import { SampleModule } from './sample';
// まとめのstoreクラス
export class VStore {
@Module() public sample = new SampleModule();
@State() public hoge = 'xxxxx';
// v2.0.1ではrootモジュールのmutation/action/getterは動かない(直接vuexでmutation操作は可能)
@Mutation()
public setHoge(hoge: string) {
this.hoge = hoge;
}
}
// vuex初期化
Vue.use(Vuex);
const isDev = process.env.NODE_ENV === 'development';
const store = new VStore();
export default createVuexStore(store, {
// 開発中はtrue, 本番はfalseにする
strict: isDev,
modules: {},
plugins: []
});
Componentでの利用
<template>
<div>
<div>store.sample.param1 is "{{ store.sample.param1 }}"</div>
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { useStore } from 'vuex-simple';
import { VStore } from '@/store';
@Component({})
export default class Home extends Vue {
public store: VStore = useStore(this.$store);
}
</script>
Tips的なもの
template内でstoreに直接アクセスしたくない
componentでcomputed
プロパティを作れば良いかな。
@Component({})
export default class Home extends Vue {
public store: VStore = useStore(this.$store);
public get sample() { return this.store.sample }
public get sampleParam1() { return this.store.sample.param1 }
}
あるいは、次項のvuex-typediを使ってDIすれば良い。
vuex-typediについて
vuex-simpleと同じ作者が作った、vuex-typedi
パッケージを使って、依存性注入することが出来る。
https://github.com/sascha245/vuex-simple#example-with-dependency-injections
https://github.com/sascha245/vue-typedi
手間の割にメリットが少ないような気がするなあ。プロジェクト全体でtypediを使ってるなら有用なのか?あとはsubmoduleを直接DIできるので不必要なmoduleに触らせたくない・規模の大きめなプロジェクトだと有用とか?
stateがpublicなのが嫌だ。外からはreadonlyにしたい
vuexのstrict
を開発時にtrue
にしてれば、実行時にエラー吐いてくれるので、基本的にはpublicのままで対応出来る。
どうしてもprivateにしたいなら
export class SampleModule {
@State() private _param1: int = 3;
@Mutation()
public setParam1(param: int) {
this._param1 = param;
}
@Getter()
public param1() { return this._param1; }
@Action()
public async increment1() {
await new Promise(r => setTimeout(r, 200));
this.setParam1(this.param1 + 1);
}
}
みたいにすればまあいけるのかな。なんか昔のphpぽい。
とはいえ実行時には_param1
にアクセスできる(TypeScriptの仕様)のであまり意味はない。
Nuxt.jsで使いたい
nuxtが勝手にvuexの初期化をしてくれるモジュールモード
でなく、クラシックモード
を使えば良さそう。(未検証)
https://ja.nuxtjs.org/guide/vuex-store/
import { createVuexStore } from 'vuex-simple';
// ~~~ 省略
const createStore = () => {
const isDev = process.env.NODE_ENV === 'development';
return createVuexStore(store, {
strict: isDev,
modules: {},
plugins: []
});
}
export default createStore
action, mutationのpayloadに型を持たせたい
payloadの型を定義してメソッドの引数で指定すればok
export interface MSetHogePayload {
hoge: int;
additionalHoge: int;
}
export interface AUpdateHogePayload {
hoge: int;
}
export class SampleModule {
@State() public hoge: int = 3;
@Mutation()
public setHoge(payload: MSetHogePayload) {
this.hoge = payload.hoge + payload.additionalHoge;
}
@Action()
public async updateHoge(payload: AUpdateHogePayload) {
await new Promise(r => setTimeout(r, 200));
this.setHoge({ hoge: payload.hoge, additionalHoge: 100 });
}
}
@Component({})
export default class Home extends Vue {
public store: VStore = useStore(this.$store);
public mounted() {
// payload interfaceの定義を満たしてればOK
this.store.sample.updateHoge({ hoge: 2 }); // OK
this.store.sample.updateHoge({ hoge: "string" }); // NG
// またはimport {AUpdateHogePayload} from '@/store/sample'して明示的に型変換しても
this.store.sample.updateHoge({ hoge: 3 } as AUpdateHogePayload);
}
}
所感
名前の通りsimpleで使いやすい。
他のライブラリだと、vuex-classはcomponentへのbinding helperなので、vuex自体については別途考える必要があるし、vuex-type-helperはおまじないが多くてつらかった。
component側での利用時はvuex-classを使えそうな感じがするので(未検証)、プロジェクトにより使ったら良いのかな。