=== 追記 ===
この記事を書いたのはだいぶ前ですが、それ以降Vueを触ることはなく、基本 React + TypeScript で開発しているので、現在の Vue の状況はよくわかりません。
が、Vuexのmapヘルパーはだいぶ型に弱そうとの話を見たため、追記させていただきます。
型は命!
===========
Vuexのドキュメントを読んでいて、mapState
の箇所で少し詰まった。理解すればすごい単純なことなのだが、あまりWEB上にドキュメントもなかったので、残しておく。
サンプルも作ってあります。記事中のコードは見やすさのためにimport文などを削ってあるので、完全なコードはサンプルからご確認ください。Vue CLI3を使って構築したため、vue-loaderを使っています。
まずはcomputedの理解から
computedを使うことで、「今までthis.$store.state.count
と書いていたものが、this.count1
と書けるようになるため、楽」というのがcomputedの役割の1つ。
以下では、computed内にcount1()
を定義しているため、<script>
内ではthis.count1
で、<template>
内ではcount1
でthis.$store.state.count
を呼び出すことが可能になっている。もちろん、定義していないthis.count
は呼び出せずundefinedとなる。
<template>
<div>
<p>$store.state.count is <span>{{ $store.state.count }}</span></p>
<p>count is <span>{{ count }}</span></p> <!-- これは undefined。なぜなら、computed で count は定義されていないため -->
<p>count1 is <span>{{ count1 }}</span></p> <!-- computed で count1 を定義しているため、これは表示される -->
<button v-on:click="click1">this.count</button> <!-- これは undefined -->
<button v-on:click="click2">this.count1</button>
</div>
</template>
<script>
export default {
computed: {
count1() {
return this.$store.state.count;
}
},
methods: {
click1: function () {
// これは undefined
// なぜなら、this.count は computed の定義にないから
alert('count is ' + this.count);
},
click2: function () {
// computed で count1 を定義しておくことで、
// いちいち this.$store.state.count と書かなくてよくなる
alert('count is ' + this.count1);
}
}
};
</script>
mapState
を使う
mapState
を使うことで、その中ではstate
にthis.$store.state
が代入される。そのため、state.count
と書くだけでthis.$store.state.count
と同意になる。
さらに、"count"
はstate => state.count
と同意にもなるよう設計されている。
それにより、以下のように使うことでさらに楽にcomputedが書けるようになる。
<template>
<div>
<h2>MapState1</h2>
<p>count is <span>{{ count }}</span></p> <!-- これは undefined -->
<p>count1 is <span>{{ count1 }}</span></p>
<p>countPlusLocalState is <span>{{ countPlusLocalState }}</span></p>
<button v-on:click="click">MapState1</button>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
props: {
localCount: Number // 親から渡されたprops
},
computed: mapState({
// mapState内では、state === this.$store.state となる
count1: state => state.count,
// 書き方の問題で、
// 以下も `state => state.count` と同じ意味になる
count2: "count",
// 普通のメソッドと同じで、this使いたい時はアロー関数ではダメで function で書く
countPlusLocalState (state) {
return state.count + this.localCount;
}
}),
methods: {
click: function () {
// computed に mapState しておくことで、
// メソッドからも $store.state.count とすることなく呼び出せる
alert('count is ' + this.count2);
}
}
};
</script>
mapStateに文字列配列を入れることでさらに簡略化
算出プロパティの名前がstateの名前と同じ場合は、配列としてmapState
に渡すことができる。
つまり、
computed: mapState({
hello: this.$store.state.hello,
world: this.$store.state.world
}),
これは、以下と同じということ。
computed: mapState(["hello", "world"]),
これにより、stateをマッピングするだけならかなり簡単に書けるようになる。
mapState
を展開して使う
mapState
を使う場合の多くは、スプレッド演算子で展開してから使う。
その意味を理解するためにはまず、mapState()
が何をしているのかを知る必要がある。
mapState()
は何をしているのか?
mapState()
は、オブジェクトを返す。
mapState({
hello: this.$store.state.hello,
world: this.$store.state.world
})
// これでも同じ意味 => mapState(['hello', 'world'])
// 返り値:
{
hello () {
return this.$store.state.hello
},
world () {
return this.$store.state.world
}
}
そして、computedの値(つまり、computed: ここのこと
)には、オブジェクトが入る必要がある。そのため、以下のようなものはエラーとなる。なぜなら、展開すると、オブジェクトがネストした状態になってしまうからである。
computed: {
mapState(['hello', 'world']),
otherFunction () {
return "something";
}
}
// これは下と同意になる
computed: {
{
hello () {
return this.$store.state.hello
},
world () {
return this.$store.state.world
}
},
otherFunction () {
return "something";
}
}
そこで、スプレッド演算子の登場!
mapState()
がオブジェクトを返すのなら、それをスプレッド演算子で展開してあげれば良い。...mapState()
とするのである。
computed: {
...mapState(['hello', 'world']),
otherFunction () {
return "something";
}
}
// これは下と同意になる
computed: {
hello () {
return this.$store.state.hello
},
world () {
return this.$store.state.world
},
otherFunction () {
return "something";
}
}
こうすることで、他の算出プロパティ(上記で言うotherFunction ()
)もcomputed内に記述することが可能になる。以下が使い方の全体像。
<template>
<div>
<h2>MapState2</h2>
<p>count is <span>{{ count }}</span></p>
<p>count1 is <span>{{ count1 }}</span></p>
<p>hello is <span>{{ hello }}</span></p>
</div>
</template>
<script>
import { mapState } from "vuex";
export default {
name: "MapState2",
props: {
localCount: Number
},
computed: {
...mapState({
count1: state => state.count,
}),
hello () {
return "Hello" + this.count1;
},
...mapState(["count"]),
},
};
</script>
...mapState()
が真価を発揮する時
...mapState()
が「すげー楽!神!」となるのは、複数のstateをコンポーネントで使いたいときである。
stateを使う全ての箇所でthis.$store.state.someName
とするのは地獄なので、computedを使うことになるが、その際に...mapState()
が使えると...
computed: {
...mapState(["count", "isLoading", "otheState"])
}
これだけで済む。これを書くだけで、コンポ―ネント内ではthis.count
などのように呼び出しが可能となるため、重宝するのである。
以上、わかってみたらすごく単純、アホでもわかりそうな内容であった。
自分と同じVue Noobのために、捧げます。