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