Vuex を安全に使うための型定義

Meguro.es #13 @ ラクスル

@ktsn

Vuex?

状態とミューテーション

const store = new Vuex.Store({
  // 状態
  state: {
    count: 0
  },

  // ミューテーション
  mutations: {
    increment (state, payload) {
      state.count += payload.amount
    }
  }
})

// 使う側
store.commit('increment', { amount: 1 })
console.log(store.state.count) // -> 1

モジュール

// カウンターモジュール
const counter = {
  namespaced: true,
  state: { /* ... */ },
  mutations: { /* ... */ }
}

const store = new Vuex.Store({
  modules: { counter }
})

store.commit('counter/increment', { amount: 1 })

コンポーネントで使う

import Vue from 'vue'
import { mapState, mapMutations } from 'vuex'

export default Vue.extend({
  // increment を this に生やす
  methods: mapMutations(['increment']),

  // count を this に生やす
  computed: mapState(['count']),

  created () {
    // ミューテーション
    this.increment({ amount: 1 })

    // 状態
    console.log(this.count)
  }
})

型付けがつらい

const store = new Vuex.Store({
  /* ... */
  mutations: {
    increment (state, payload: { amount: number }) {
      state.count += payload.amount
    }
  }
})

// 有効な mutation 名がわからない
// payload の型がわからない
store.commit('increment', 123)

namespace の型付けが困難

定義するとき

increment (state, payload) { ... }

呼び出すとき

commit('counter/increment', payload)

無理では?

ゼロから型付けできる API で作ったほうが良い

試しに書いてみたら意外にできた

// 状態: 普通に状態の型を定義
interface CounterState {
  count: number
}

// ミューテーション
// キー: ミューテーション名
// 値: ペイロードの型
interface CounterMutations {
  increment: { amount: number } 
}

まず状態とミューテーションの
インターフェースを定義

DefineModule

import { DefineModule } from 'vuex'

// モジュールの型注釈に DefineModule を指定
const counter: DefineModule<CounterState, {}, CounterMutations, {}> = {
  // namespace 付けても良い
  namespaced: true,

  state: {
    count: 0
  },

  mutations: {
    // 型注釈付けなくても全部型がついてる
    increment (state, payload) { 
      state.count += payload.amount
    }
  }
} 

createNamespacedHelpers

import { createNamespacedHelpers } from 'vuex'

// 型パラメーターに状態、ミューテーションのインターフェースを渡す
// 引数には実際の namespace の値を渡す
export const counterHelpers 
  = createNamespacedHelpers<CounterState, {}, CounterMutations, {}>('counter') 
// コンポーネント内でヘルパーを使う
export default Vue.extend({
  methods: counterHelpers.mapMutations([
    'increment' // 名前が正しいかチェックされる!
  ]),

  created () {
    // ちゃんと型付けされる!
    this.increment({ amount: 1 })
  }
})

型安全バンザイ 🎉

Vuex を安全に使うための型定義 #meguroes

By katashin

Vuex を安全に使うための型定義 #meguroes

  • 3,060