Help us understand the problem. What is going on with this article?

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

Vuexのドキュメントを読んでいて、mapStateの箇所で少し詰まった。理解すればすごい単純なことなのだが、あまりWEB上にドキュメントもなかったので、残しておく。

サンプルも作ってあります。記事中のコードは見やすさのためにimport文などを削ってあるので、完全なコードはサンプルからご確認ください。Vue CLI3を使って構築したため、vue-loaderを使っています。

まずはcomputedの理解から

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

以下では、computed内にcount1()を定義しているため、<script>内ではthis.count1で、<template>内ではcount1this.$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を使うことで、その中ではstatethis.$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に渡すことができる。
つまり、

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内に記述することが可能になる。以下が使い方の全体像。

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()が使えると...

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

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

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

参考: Don’t understand how to use mapState from the docs

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away