State

Single State Tree

単一の state 用 tree

Vuex は single state tree を用います。つまり、この単一のオブジェクトに、アプリケーションの全てのレベルの state を持たせ、そしてこのオブジェクトが "single source of truth" としての役割を与えます。(訳注: 大げさな言い回しだが、単にこのオブジェクトを参照すれば全てがわかりますよ、という状態にしておきますよ、ということ。) ですので、通常であれば各 App には、一つだけの store を用いるということです。single state tree になっていることによって、state の各部分がツリーのどの部分なのかということが容易にわかりますし、デバッグのために現在の App の状態のスナップショットを取るのも簡単です。

single state tree と、modularity (分割運用可能性) とは相反するものではありません。おって state と mutations を sub module へと分割していく手法についても説明します。

Getting Vuex State into Vue Components

Vuex の state を Vue Component に注入する

では store の中の state を Vue Component の中で表示するにはどうしたらいいでしょうか。Vuex の store は reactive なので、state を「読みだす」一番簡単な方法は computed property の中で store の state を return することです。

// let's create a Counter component
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

store.state.count が変更されるたびに、computed property が再評価されますので、結果としてこれが関連づけられている DOM が更新されます。

しかし上記のようなコードを書いてしまうと、グローバルにある store という変数を用いているので、モジュール化したコンポーネントの中で store を使おうとすると、全てのコンポーネントの中で store を import しなくてはいけなくなります。またコンポーネントのテストをする際にも、mock が必要になってしまいます。(訳注: const store = Vuex.store() と store を作ったファイルの中では store にアクセスできるが、別ファイルに切り出した component でこれを使おうと思ったら、store を import しなくてはいけない。これは現実的ではないし、面倒だ。ということ。)

そこで、Vuex は store を子 component に「注入(inject)」する機構を用意しています。ルートコンポーネントに store オプションを与えることでこれを実施できます。(ただし Vue.use(Vuex) を行っておいてください)

const app = new Vue({
  el: '#app',
  // provide the store using the "store" option.
  // this will inject the store instance to all child components.
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

(訳注: store: store, としなくてもいいのは新しいシンタックス)

ルートインスタンスに store オプションを与えることで、子コンポーネント全てに store が注入されます。そして各コンポーネント内で this.$store という形でアクセスできるようになります。では、さっそく Counter が store をこの手法に使えるように書き換えてみましょう。

MapState ヘルパー

component が複数の store state を使う場合に、なんども computed properties の中で宣言するのは冗長です。そういう場合には mapState を用いてください。これによって getter function を作ることができます。

// in full builds helpers are exposed as Vuex.mapState
import { mapState } from 'vuex'

export default {
  // ...
  computed: mapState({
    // arrow functions can make the code very succinct!
    count: state => state.count,

    // passing the string value 'count' is same as `state => state.count`
    countAlias: 'count',

    // to access local state with `this`, a normal function must be used
    countPlusLocalState (state) {
      return state.count + this.localCount
    }
  })
}

mapState に文字列の配列を渡すこともできます。ただし、mapped computed property の名前と state の sub tree の名前が同じ場合だけ、このように書くことができます。

computed: mapState([
  // map this.count to store.state.count
  'count'
])

Object Spread Operator

mapState は object を返します。では、他の local computed property とこの mapState で返されたオブジェクトを computed プロパティの中で組み合わせて使うには、どうしたらいいのでしょうか? 通常であれば複数のオブジェクトを一つにまとめる何かを使って、それでできたオブジェクトを computed プロパティに渡せばいいわけです。一番簡単なのは、object spread operator を使うことです。

computed: {
  localComputed () { /* ... */ },
  // mix this into the outer object with the object spread operator
  ...mapState({
    // ...
  })
}

Components Can Still Have Local State

local の state を持ってもよい

Vuex は全ての state を Vuex の中で管理するように、と強制しているわけではありません。もちろんより多くの state を Vuex で管理した方が、state の変更は明示的になりデバッグも簡単になります。とはいえ、時には煩雑で直感的でなくなってしまう場合もあります。もし state が単一のコンポーネントしか紐づいていないのであれば、それはローカルステイトとして切り離してしまった方がいいでしょう。常にこのトレードオフを判断し、アプリケーションに応じた手法を選択する必要があります。

Last updated