vue 2.2.1
vuex 2.3.1
でお送りします。
準備
今回はvue-cliのwebpackで試してみます。
インストールしていない場合は下記で vue-cli をグローバルにインストール。
$ sudo npm install -g vue-cli
これでvueコマンドが使えるので、プロジェクトを作成しカレントにします。
$ vue init webpack-simple vuex-test
$ cd vuex-test
npmパッケージをプロジェクトにインストールします。
vuexとbabel-plugin-transform-object-rest-spreadを追加でインストールしましょう。
$ npm install
$ npm install vuex -S
$ npm install babel-plugin-transform-object-rest-spread -D
babel-plugin-transform-object-rest-spread をインストールすることでJavaScriptの新しい機能を使用することができます。
ルートにある.babelrc
のplugins
に指定することで、この機能を使えるようにしましょう。
.babelrc
{ "presets": [ ["latest", { "es2015": { "modules": false } }] ], "plugins": ["transform-object-rest-spread"] }
IE11対応
VuexはPromiseというのを使用していますが、IE11は対応していません。
これはbabel-polyfill
をインストールすることで動くようになります。
npm install babel-polyfill -D
webpack.config.js
のentry
の部分を下記のようにします。
webpack.config.js
entry: ["babel-polyfill", './src/main.js'],
これでVue+Vuexを使用する準備が整いました。
下記コマンドを実行すると自動的にブラウザが起動しVueのロゴが表示されるのを確認してください。
$ npm run dev
Vuexデータの流れ
下図はVuex公式ドキュメントで説明されている図です。
細かい部分は置いといて、大きい流れは、
Vue Components
からActions
を実行する。
Actions
からMutations
を実行する。
Mutations
でState
を更新する。
Vue Components
に反映される。
という流れになります。
コンポーネントだけで作成するTODOアプリ
簡単なTODOアプリをサンプルを作成してみます。
最初はVuexを使わないでコンポーネントだけで作成してみます。
src/App.vue
<template> <div id="app"> <ul> <item v-for="item in items" :item="item"></item> </ul> <div> <input type="text" v-model="inputTitle"> <button @click="addItem">追加</button> </div> </div> </template> <script> import Item from './Item.vue'; export default { name: 'app', components: { Item }, data () { return { inputTitle: "", items: [ {is_do: false, title: 'タスク1'}, {is_do: true, title: 'タスク2'}, {is_do: false, title: 'タスク3'} ] } }, methods: { addItem() { this.items.push({ title: this.inputTitle, is_do: false }); } } } </script>
src/Item.vue
<template> <li v-bind:class="{ 'is-do': item.is_do }" @click="done(item)">{{ item.title }}</li> </template> <script> export default { props: ['item'], methods: { done(item) { item.is_do = !item.is_do; } } } </script> <style lang="scss" scoped> li { cursor: pointer; } li.is-do { text-decoration: line-through; } </style>
機能としては、li
をクリックするとis-do
というクラスが付与されチェックされあた状態となります。
追加ボタンをクリックすると新しいタスクが追加されます。
この部分は単純なVue.jsの作りなので説明は省略します。
このくらいならコンポーネント分ける必要ないのですが、一つだけだとVuexのサンプルとしてわかりにくいのかなと。
Storeファイルのstateのデータを表示する
まずは、Storeファイルにstate
を作成して、その内容をコンポーネントで表示するという部分をだけを作成してみます。
src/store
ディレクトリにindex.js
というファイル名で作成します。
src/store/index.js
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { items: [ {is_do: false, title: 'タスク1'}, {is_do: true, title: 'タスク2'}, {is_do: false, title: 'タスク3'} ] } });
Vue、Vuexをインポートしたら、Vue.use(Vuex)
でVuexを使用できる状態にします。
いくつかVuexを使用するための記述がありますが、基本的にはsrc/App.vue
にあったdata
をそのままstate
に書いているだけですね。
次にコンポーネントで設定したstate
を表示してみましょう。
Storeのインポートはmain.js
で行います。
src/main.js
import Vue from 'vue' import App from './App.vue' import store from './store'; new Vue({ store, el: '#app', render: h => h(App) })
src/App.vue
のitems
の中をthis.$store.state.items
に変更します。
src/App.vue
import Item from './Item.vue' export default { name: 'app', components: { Item }, data () { return { items: this.$store.state.items, inputTitle: "" } } }
これでストアからstate
のデータを読み込み表示することができました。
state
はmapState
を使用することで記述を簡略化することができます。
src/App.vue
import { mapState } from 'vuex' import Item from './Item.vue' export default { name: 'app', components: { Item }, data () { return { inputTitle: "" } }, computed: { ...mapState(['items']) } }
インポートでmapState
を読み込んだら。computed
に...mapState(['items'])
を追記します。これでdeta
にitems
を設定する必要がなくなります。
「…」が奇妙な感じですが「オブジェクトスプレッド演算子」という機能です。
main.js
にStore
をインポートしておけば、どのコンポーネントからもデータを取り出せるので便利ですね。
mutation-types.jsの作成
state
から取得する方法がわかったので次はActions
とMutations
を作成して、state
を更新できるようにしてみましょう。
「TODOをチェックする(DONE_TASK)」と「TODOを追加する(ADD_TASK)」というアクションを作成していきます。
早速アクションを書き始めてもいいのですが、その前にmutation-types.js
を作成します。
別ファイルにすることでストアとコンポーネントで使えるようにします。
src/store/mutation-types.js
export const ADD_TASK = 'ADD_TASK'; export const DONE_TASK = 'DONE_TASK';
定数に文字列を代入しているだけですが、こうすることでlintやコードの補間ができたりと便利らしいです。
ストアで使えるようにインポートしておきましょう。
src/store/index.js
import * as types from './mutation-types'
Actionsの作成
今度こそアクションを作成していきましょう。
src/store/index.js
のstate
の下に下記を追記します。
src/store/index.js
actions: { [types.ADD_TASK] ({ commit }, title) { let newItem = { title: title, is_do: false } commit( types.ADD_TASK, { data: newItem }) }, [types.DONE_TASK] ({ commit }, item) { commit( types.DONE_TASK, { data: item }) } }
関数名の部分が配列みたいになってまた奇妙な感じになってますが、こうすることで定数を関数名として使用できるようになります。
commit
を実行することでミューテーションが実行されます。
ここでは値を渡したかったので第2引数に設定しています。
今回のような値を渡すだけならアクションは必要ないと思いますが、今後サーバーとのやり取りをする場合はこのActions
に記述していくことになります。
commit
の部分は次のような書き方もできます。
commit({ type: types.DONE_TASK, data: item })
Mutationsの作成
先ほど作成したActions
の下に下記を追記します。
src/store/index.js
mutations: { [types.ADD_TASK] (state, payload) { state.items.push(payload.data); }, [types.DONE_TASK] (state, payload) { let index = state.items.indexOf(payload.data) state.items[index].is_do = !payload.data.is_do } },
アクションで設定した引数はpayload
に入っています。
data
プロパティに設定したのでpayload.data
で取り出せます。
あとは普通にstate
の配列を書き換えているだけですね。
Componentsで使う
最後にコンポーネントからアクションを実行してステートを更新してみましょう。
まずはTODOをチェックするアクションです。
これはItem.vue
で設定してましたね。
src/Item.vue
<template> <li v-bind:class="{ 'is-do': item.is_do }" @click="DONE_TASK(item)">{{ item.title }}</li> </template> <script> import { mapActions } from 'vuex' import * as types from './store/mutation-types'; export default { props: ['item'], methods: { ...mapActions([ types.DONE_TASK ]) } } </script>
ステートと同じでアクションにはmapActions
というヘルパーが用意されています。
methods
ではこれを使用して、使いたいアクションtypes.DONE_TASK
を指定します。
あとはタグの部分の@click
をDONE_TASK(item)
に変更しましょう。
同じ要領でApp.vue
も作成してみましょう。
src/App.vue
<template> <div id="app"> <ul> <item v-for="item in items" :item="item"></item> </ul> <div> <input type="text" v-model="inputTitle"> <button @click="ADD_TASK(inputTitle)">追加</button> </div> </div> </template> <script> import { mapState,mapActions } from 'vuex' import * as types from './store/mutation-types'; import Item from './Item.vue' export default { name: 'app', components: { Item }, data () { return { inputTitle: "" } }, computed: { ...mapState(['items']) }, methods: { ...mapActions([ types.ADD_TASK ]), } } </script>
いろいろ間違っているところがあると思いますが、以上になります。
複雑なとこもありますが、一つ一つ見ていくことでなんとなくわかってきたような気がしないでもないです。
参考サイト
Vuex Introduction
Vue.js + Vuexで開発をしてみよう!