Vuex: mapStateの使い方を理解する

=== 追記 ===

この記事を書いたのはだいぶ前ですが、それ以降Vueを触ることはなく、基本 React + TypeScript で開発しているので、現在の Vue の状況はよくわかりません。

が、Vuexのmapヘルパーは[だいぶ型に弱そう](https://qiita.com/potato4d/items/c1d8de45d54ab612a541)との話を見たため、追記させていただきます。
型は命！

===========

[Vuexのドキュメント](https://vuex.vuejs.org/ja/guide/state.html#mapstate-ヘルパー)を読んでいて、`mapState`の箇所で少し詰まった。理解すればすごい単純なことなのだが、あまりWEB上にドキュメントもなかったので、残しておく。

[サンプル](https://github.com/Mizumaki/samples/tree/master/vue-sample)も作ってあります。記事中のコードは見やすさのためにimport文などを削ってあるので、完全なコードは[サンプル](https://github.com/Mizumaki/samples/tree/master/vue-sample)からご確認ください。Vue CLI3を使って構築したため、[vue-loader](https://vue-loader-v14.vuejs.org/ja/)を使っています。

# まずはcomputedの理解から
computedを使うことで、「今まで`this.$store.state.count`と書いていたものが、`this.count1`と書けるようになるため、楽」というのがcomputedの役割の1つ。

以下では、computed内に`count1()`を定義しているため、`<script>`内では`this.count1`で、`<template>`内では`count1`で`this.$store.state.count`を呼び出すことが可能になっている。もちろん、定義していない`this.count`は呼び出せずundefinedとなる。

```src/views/MapStateTest.vue
<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が書けるようになる。

```src/components/MapState1.vue
<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`に渡すことができる。
つまり、

```javascript
computed: mapState({
  hello: this.$store.state.hello,
  world: this.$store.state.world
}),
```

これは、以下と同じということ。

```javascript
computed: mapState(["hello", "world"]),
```

これにより、stateをマッピングするだけならかなり簡単に書けるようになる。

# `mapState`を展開して使う
`mapState`を使う場合の多くは、[スプレッド演算子](https://qiita.com/Nossa/items/e6f503cbb95c8e6967f8)で展開してから使う。
その意味を理解するためにはまず、`mapState()`が何をしているのかを知る必要がある。

## `mapState()`は何をしているのか？
`mapState()`は、オブジェクトを返す。

```javascript
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: ここのこと`）には、オブジェクトが入る必要がある。そのため、以下のようなものはエラーとなる。なぜなら、展開すると、オブジェクトがネストした状態になってしまうからである。

```javascript
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()`とするのである。

```javascript
computed: {
  ...mapState(['hello', 'world']),
  otherFunction () {
    return "something";
  }
}

// これは下と同意になる

computed: {
  hello () {
    return this.$store.state.hello
  },
  world () {
    return this.$store.state.world
  },
  otherFunction () {
    return "something";
  }
}
```

こうすることで、他の[算出プロパティ](https://jp.vuejs.org/v2/guide/computed.html)（上記で言う`otherFunction ()`）もcomputed内に記述することが可能になる。以下が使い方の全体像。

```src/components/MapState2.vue
<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()`が使えると...

```javascript
computed: {
  ...mapState(["count", "isLoading", "otheState"])
}
```

これだけで済む。これを書くだけで、コンポ―ネント内では`this.count`などのように呼び出しが可能となるため、重宝するのである。


以上、わかってみたらすごく単純、アホでもわかりそうな内容であった。
自分と同じVue Noobのために、捧げます。

[参考: Don’t understand how to use mapState from the docs](https://forum.vuejs.org/t/dont-understand-how-to-use-mapstate-from-the-docs/14454)