Vuex with T
TypescriptでVuexを使うためのライブラリはいろいろあって、悩みどころです。
その中の一つ、direct-vuexは普通のVuexの書き方に近いので、Vuexのサイトを見たことがある人なら理解しやすいと思います。
カウンターを作ってみる
Vue Cli (@vue/cli) で作ったプロジェクトで、定番のカウンターを作ってみます。
説明がほぼありませんが、ソースコードを見ればわかると思います。
ストアの作成
state
の書き方に制限があります。参照
src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import { createDirectStore } from 'direct-vuex'
Vue.use(Vuex)
export interface CounterState {
count: number
}
const { store, rootActionContext, moduleActionContext } = createDirectStore({
state: (): CounterState => {
return {
count: 0,
}
},
mutations: {
INCREMENT(state) {
state.count += 1
},
},
actions: {
// 値を返す時は、戻り値の型を書く必要がある
increment(context): number {
const { commit, state } = rootActionContext(context)
commit.INCREMENT()
return state.count
},
},
})
export default store
export { rootActionContext, moduleActionContext }
export type AppStore = typeof store
declare module 'vuex' {
interface Store<S> {
direct: AppStore
}
}
ストアの登録箇所を変更
store.original
とする必要があります。
src/main.ts
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store: store.original, // 変更
render: (h) => h(App),
}).$mount('#app')
コンポーネントの作成
ポイントは、this.$store.direct
で型付けされたラッパーを取り出すところです。
src/components/Counter.vue
<template>
<div>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
computed: {
count() {
return this.$store.direct.state.count
},
},
methods: {
async increment() {
const count = await this.$store.direct.dispatch.increment()
console.log(count)
},
},
})
</script>
実行
とりあえず動作確認のため、src/views/Home.vue
にCounterを2つ張り付けてみます。
src/views/Home.vue
<template>
<div class="home">
<Counter />
<Counter />
<img alt="Vue logo" src="../assets/logo.png" />
<HelloWorld msg="Welcome to Your Vue.js App" />
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
import Counter from '@/components/Counter.vue'
export default {
name: 'home',
components: {
HelloWorld,
Counter,
},
}
</script>
うまく動作しています。
カウンターのストアをモジュールに切り出してみる
src/store/counter.ts
にモジュールを作成します。
src/store/counter.ts
import { createModule } from 'direct-vuex'
import { moduleActionContext } from './index'
export interface CounterState {
count: number
}
const counter = createModule({
namespaced: true,
state: (): CounterState => {
return {
count: 0,
}
},
mutations: {
INCREMENT(state) {
state.count += 1
},
},
actions: {
increment(context): number {
const { commit, state } = counterActionContext(context) // rootCommitなどもあります
commit.INCREMENT()
return state.count
},
},
})
export default counter
export const counterActionContext = (context: any) => moduleActionContext(context, counter)
モジュールを登録
src/store/index.ts
を書き換えて、modules
で登録します。
src/store/index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import { createDirectStore } from 'direct-vuex'
import counter from './counter'
Vue.use(Vuex)
const { store, rootActionContext, moduleActionContext } = createDirectStore({
modules: {
counter,
},
})
export default store
export { rootActionContext, moduleActionContext }
export type AppStore = typeof store
declare module 'vuex' {
interface Store<S> {
direct: AppStore
}
}
コンポーネントの変更
namespaced: true
でモジュールを作ったので、namespace
経由でアクセスするように変更して完成です。
src/components/Counter.vue
<template>
<div>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
computed: {
count() {
return this.$store.direct.state.counter.count
},
},
methods: {
async increment() {
const count = await this.$store.direct.dispatch.counter.increment()
console.log(count)
},
},
})
</script>
Composition API を使う場合
ストアのラッパーをcontext.root.$store.direct
で取得するだけです。
src/components/Counter.vue
<template>
<div>
<span>{{ count }}</span>
<button @click="increment">+</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import VueCompositionApi, { createComponent, computed } from '@vue/composition-api'
Vue.use(VueCompositionApi) // src/main.ts とかに書きます。
export default createComponent({
setup(props, context) {
const store = context.root.$store.direct
const count = computed(() => store.state.counter.count)
async function increment() {
const count = await store.dispatch.counter.increment()
console.log(count)
}
return {
count,
increment,
}
},
})
</script>
まとめ
簡単。