34
31

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.

VuexをTypeScriptで利用するのに悩んだ

Last updated at Posted at 2018-08-30

概要

Vue.jsをTypeScriptで開発する際にVuexを利用するのにしっくりくる実装方法を模索中で、いくつか方法を試してみました。

GitHubに利用したプロジェクトをUPしています。実際に試してみたい方どうぞ^^

準備

ここではDockerを利用して環境構築していますが、ローカルで構築してもらってもOKです。

> mkdir 任意のディレクトリ
> cd 任意のディレクトリ
> vi Dockerfile
> vi docker-compose.yml
Dockerfile
FROM node:10.8.0-stretch

RUN npm install --global @vue/cli

WORKDIR /projects
docker-compose.yml
version: '3'

services:
  app:
    build: .
    ports:
      - "8080:8080"
    volumes:
      - ".:/projects"
    tty: true
> docker-compose up -d
> docker-compose exec app bash
コンテナ内
> vue create app

Vue CLI v3.0.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Vuex, Linter, Unit
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript for auto-detected polyfills? Yes
? Pick a linter / formatter config: TSLint
? Pick additional lint features: Lint on save
? Pick a unit testing solution: Mocha
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
? Pick the package manager to use when installing dependencies: (Use arrow keys)
❯ Use Yarn
  Use NPM
コンテナ内
> cd app
> yarn serve

これで実装の準備が整いました。

Vue-Cli標準

vue create コマンドでプロジェクトを作成するとsrc直下にstore.tsが作成されているので、そこに実装をいれて利用するパターンです。

stateにcounter ってのを持っていて、それをインクリメントするアクションがあるだけです。

src/store.ts
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

interface State {
  conuter: number;
}

export default new Vuex.Store({
  state: {
    conuter: 0,
  } as State,
  getters: {
    getCounter: (state, getters) => () => {
      return state.conuter;
    },
  },
  mutations: {
    increment(state, payload) {
      state.conuter += 1;
    },
  },
  actions: {
    incrementAction(context) {
      context.commit('increment');
    },
  },
});

App.vueで使ってみます。
超適当ですが、画像にclickイベント定義して、HelloWorldコンポーネントでstateに定義しているcounter を表示してます。

src/App.vue
<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" @click="increment">
    <HelloWorld :msg="`Welcome to Your Vue.js + TypeScript App ${this.counter}`"/>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';

@Component({
  components: {
    HelloWorld,
  },
})
export default class App extends Vue {
  private get counter(): number {
    return this.$store.getters.getCounter();
  }

  private increment(): void {
    this.$store.dispatch('incrementAction');
  }
}
</script>

(略)

ブラウザで確認すると、画像をクリックするとcounterがインクリメントするのが、確認できます。

スクリーンショット_2018-08-28_10_28_43.png

気になる点

実装はシンプルで良いのですが、規模が大きくなってくると、store.tsが肥大化してくるのが目に見えます。辛い。

this.$store を利用するので、メソッド名などを間違えていても実行時にしかエラーにならないので、せっかくTypeScript使っているのになーです。

src/App.vue
export default class App extends Vue {
  private get counter(): number {
    return this.$store.getters.getCounter2(); // 実行時にエラー
  }

  private increment(): void {
    this.$store.dispatch('incrementAction2'); // 実行時にエラー
  }
}

vuex-type-helperを利用する

下記記事で紹介されていたvuex-type-helperを利用してみます。

Vue.js + Vuex + TypeScript を試行錯誤してみた
https://logs.utahta.com/blog/2017/09/02/110000

ktsn/vuex-type-helper
https://github.com/ktsn/vuex-type-helper

vuex-type-helperとvuex-classってのを追加します。

コンテナ内
> yarn add vuex-type-helper vuex-class

storeの実装を追加します。ここではモジュール化してみます。

> mkdir -pv store2/modules
> touch store2/index.ts
> touch store2/modules/app.ts
store2/index.ts
import Vue from 'vue';
import Vuex from 'vuex';
import { app } from '@/store2/modules/app';

Vue.use(Vuex);

export default new Vuex.Store({
  modules: {
    app,
  },
});

store2/modules/app.ts
import Vuex, { createNamespacedHelpers } from 'vuex';
import { DefineActions, DefineGetters, DefineMutations } from 'vuex-type-helper';

export interface State {
  counter: number;
}

export interface Getters {
  counter: number;
}

export interface Mutations {
  increment: {};
}

export interface Actions {
  incrementAction: {};
}

export const state: State = {
  counter: 0,
};

export const getters: DefineGetters<Getters, State> = {
  counter: (state) => state.counter,
};

export const mutations: DefineMutations<Mutations, State> = {
  increment(state, {}) {
    state.counter += 1;
  },
};

export const actions: DefineActions<Actions, State, Mutations, Getters> = {
  incrementAction({ commit }, payload) {
    commit('increment', payload);
  },
};

export const {
  mapState,
  mapGetters,
  mapMutations,
  mapActions,
} = createNamespacedHelpers<State, Getters, Mutations, Actions>('app');

export const app = {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};

利用できるようにmain.tsとApp.vueを編集します。

src/main.ts
import Vue from 'vue';
import App from './App.vue';
-import store from './store';
+import store from './store2';

src/App.vue
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from './components/HelloWorld.vue';

import { Getter } from 'vuex-class';
import * as app from './store2/modules/app';

@Component({
  components: {
    HelloWorld,
  },
  methods: {
    ...app.mapActions(['incrementAction']),
  },
})
export default class App extends Vue {

  @Getter('app/counter') private counter!: number;
  private incrementAction!: (payload: {}) => void;
  private increment(): void {
    this.incrementAction({});
  }
}
()

はい。

気になる点

こちらの利点としてはモジュール化しやすい点と、アクション名を間違えてたときにビルドエラー吐いてくれる点でしょうか。

メソッド名間違い
  methods: {
    ...app.mapActions(['incrementAction2']),
  },
エラー出してくれる
Argument of type '"incrementAction2"[]' is not assignable to parameter of type '"incrementAction"[]'.
  Type '"incrementAction2"' is not assignable to type '"incrementAction"'.

getterで間違ってる場合は、ブラウザ側でエラーになります。惜しい。

プロパティ名間違い
  @Getter('app/counter2') private counter!: number;

ブラウザでエラー

スクリーンショット 2018-08-28 11.10.13.png

vuex-classを利用するとActionなんかも以下のような定義ができるけれど、getterと同様にメソッド名間違いがブラウザでしか検知できないので、微妙。

vuex-classでAction定義
   @Action('app/incrementAction') private incrementAction!: (payload: {}) => void;

うーん。独自実装いれたらもっとブラウザエラーを回避できそうですが、どこまで実装しようか悩ましいところです。

Vue.js+TypeScriptで開発するときの参考記事まとめ
https://qiita.com/kai_kou/items/19b494a41023d84bacc7

34
31
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
34
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?