2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

TypescriptでVuex。direct-vuexでシンプルに

Posted at

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>

direct-vue3.gif

うまく動作しています。

カウンターのストアをモジュールに切り出してみる

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>

まとめ

簡単。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?