Help us understand the problem. What is going on with this article?

vuex-simpleを使ってTypeScriptでvuexを書く

More than 1 year has passed since last update.

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使ったことある人ならコード読めば何やってるかわかる。

store/sample.ts
// 適当な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にも対応できる
}
store/index.ts
// 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での利用

views/Home.vue
<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/

store/index.js
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

store/sample.ts
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 });
  }
}
views/Home.vue
@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を使えそうな感じがするので(未検証)、プロジェクトにより使ったら良いのかな。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away