HTML5テクニカルノート
Vue.js + Vuex入門 01: Storeを使う
- ID: FN1909002
- Technique: HTML5 / ECMAScript 2015
- Library: Vue.js 2.6.10
「VuexはVue.jsアプリケーションのための状態管理パターン+ライブラリです」。アプリケーションにひとつしかないStoreがデータなどの状態を集中してもち、状態は決まったやり方でしか変えられません。複数のコンポーネントから参照しても状態はつねに一意です。また、状態を変えるにも、いわば一本道をとおるしかないので、いつどのような変更が行われたのか、たやすくつきとめられます。
とはいえ、概念的な話だけでは、なかなかピンときません。Vuexが採り入れられた単一ファイルコンポーネントのVueアプリケーションをひとつ、チュートリアル形式でつくってみましょう。
01 題材はTodoMVC
お題は、Vue.js公式サイトの「TodoMVC の例」です。もっとも、このサンプルそのものは、単一ファイルコンポーネントのアプリケーションではありません。「Vue.js + CLI入門」シリーズで、単一ファイルコンポーネントにつくり直しました。その初回「Vue.js + CLI入門 01: リスト項目の表示と追加」でできたアプリケーションを出発点とします(サンプル001)。Vue.jsの構文やその解説については、この「Vue.js + CLI入門」シリーズをお読みください。本シリーズでは、Vuexの仕組みと構文を説明してゆきます。
サンプル001■vue-todo-mvc-01
ただし、前掲サンプル001には、「Vue.js + CLI入門 02」で採り上げた「日本語変換を確定する[enter]キーイベントの問題」があります。そこで、この項で改めたつぎの修正を加えてください。テキストフィールドに項目を入力したうえ、[return]/[Enter]キーでTodoリストに加わります(図001)。
src/components/TodoInput.vue: <template><input @keypress.enter="addTodo"> <!-- @keydown.enter="addTodo"> -->
図001■テキスト入力フィールドから項目がリストに加えられる
02 Vuexのインストール
まず、Vuexをインストールしなければなりません。詳しくは公式サイトの「インストール」のページをご覧ください。今回は単一ファイルコンポーネントのアプリケーションですので、「npm」の項にあるとおり、アプリケーションのディレクトリでコマンドラインツールから、つぎのように入力します。
npm install vuex --save
本稿執筆時にインストールされたのは、Vuex v3.1.1です。
package.json{ "dependencies": { "vue": "^2.6.10", "vuex": "^3.1.1" }, }
03 Storeをつくる
Vuexでまずつくるのは、状態を保管するStoreです。基本となる構文はつぎのとおりで、モジュールsrc/store.js
に定めることにします。Vue.use()
メソッドでVuex
プラグインをインストールしたら、Vuex.Store
コンストラクタでインスタンスを生成してください。コンストラクタに渡す引数のオブジェクトには、さまざまなオプションが与えられます。今回用いるのは、state
とgetters
およびmutations
の3つです。
src/store.jsimport Vue from 'vue'; import Vuex from 'vuex'; Vue.use(Vuex); export default new Vuex.Store({ state: { }, getters: { }, mutations: { } });
前掲サンプル001は、標準的なVueアプリケーションとして、ルートモジュールのsrc/App.vue
が状態を管理していました。それらを次項以降でそっくりStoreに移すことになります。VueとStoreのインスタンスオプションの対応は、つぎのとおりです。
データ
参照
変更
Vue
data
computed
methods
→
→
→
Vuex
state
getters
mutations
ルートコンポーネントsrc/App.vue
には、src/store.js
モジュールをimport
して、つぎのようにstore
オプションでインスタンスを注入してください。これで、すべての子コンポーネントがStoreを参照できるようになるのです。
import store from './store'; export default { store, };
04 Storeにstateとgettersのオプションを定める
ルートコンポーネントsrc/App.vue
から、src/store.js
モジュールにオプションの定めを移しましょう。オプションdata
は、そのままstate
に定められます。ただし、Storeはアプリケーションに唯一と決まっているため、return
で返しません。getters
のメソッドは、引数にstate
を受け取りますので、そこから参照を取り出してください。値を返すことがはっきりし、記述も短くなるため、アロー関数=>
が使われるようです。
src/App.vueexport default { /* data() { return { todos: [], uid: 0 } }, computed: { filteredTodos() { return this.todos; } }, */ }
src/store.jsexport default new Vuex.Store({ state: { todos: [], uid: 0 }, getters: { filteredTodos: (state) => state.todos }, });
ルートモジュールのsrc/App.vue
から、子コンポーネントへのデータバインディング(:
ディレクティブ)は外してください。
src/App.vue<template> <section id="app" class="todoapp"> <todo-list /> <!-- :todos="todos" :filtered-todos="filteredTodos" --> </section> </template>
子コンポーネントsrc/components/TodoList.vue
は、データを親コンポーネントからprops
に受け取るのでなく、computed
でStoreから直に参照できます。そのために用いるのが、$store
プロパティです。前述のとおりルートコンポーネントのインスタンスにstore
オプションを与えたことにより、このプロパティからStoreが参照できるようになりました。
src/components/TodoList.vueexport default { /* props: { todos: Array, filteredTodos: Array } */ computed: { todos() { return this.$store.state.todos; }, filteredTodos() { return this.$store.getters.filteredTodos; } } }
05 Storeにmutationsオプションを定める
ルートコンポーネントsrc/App.vue
のオプションmethods
に定めたメソッドは、src/store.js
モジュールのmutations
に移します。基本の処理は変わりません。ただし、第1引数にはstate
を受け取るので、データはこの参照から取り出してください。第2引数は、メソッドを呼び出すコンポーネントから渡される値です。
src/App.vueexport default { /* methods: { addTodo(todoTitle) { const newTodo = todoTitle && todoTitle.trim(); if (!newTodo) { return; } this.todos.push({ id: this.uid++, title: newTodo, completed: false }); } } */ }
src/store.jsexport default new Vuex.Store({ mutations: { // addTodo(todoTitle) { addTodo(state, todoTitle) { const newTodo = todoTitle && todoTitle.trim(); if (!newTodo) { return; } // this.todos.push({ state.todos.push({ // id: this.uid++, id: state.uid++, title: newTodo, completed: false }); } } });
ルートモジュールのsrc/App.vue
は、子コンポーネントからメソッドを呼び出すイベント(@
ディレクティブ)の受け取りがなくなります。
src/App.vue<template> <section id="app" class="todoapp"> <header class="header"> <todo-input /> <!-- @add-todo="addTodo" --> </header> </section> </template>
コンポーネントからStoreのmutations
を呼び出すのが、commit()
メソッドです。第1引数にはメソッド名を文字列で渡します。$emit()
と異なり、属性のかたちで定めるディレクティブ(@
)を介しませんので、ケバブケース(add-todo
)でなくメソッド名のままのキャメルケース('addTodo'
)です(「ファイル名やスタイル名などで複数語を連結する方法(キャメルケース、スネークケース、ケバブケース)」参照)。
src/components/TodoInput.vueexport default { methods: { addTodo() { // this.$emit('add-todo', this.newTodo); this.$store.commit('addTodo', this.newTodo); } } }
これで、前掲サンプル001のアプリケーションに、VuexのStoreが組み込めました。手を加えた4つのモジュールのの中身は、つぎのコード001に掲げます。とくに、ルートモジュールsrc/App.vue
のJavaScriptコードは、すっきりと軽くなったことが見てとれるでしょう。実際にコードを確かめて試したい方は、CodeSandboxに公開した以下のサンプル002をご覧ください。
コード001■VuexのStoreを使う
src/store.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
todos: [],
uid: 0
},
getters: {
filteredTodos: (state) => state.todos
},
mutations: {
addTodo(state, todoTitle) {
const newTodo = todoTitle && todoTitle.trim();
if (!newTodo) {
return;
}
state.todos.push({
id: state.uid++,
title: newTodo,
completed: false
});
}
}
});
<template>
<section id="app" class="todoapp">
<header class="header">
<h1>todos</h1>
<todo-input />
</header>
<todo-list />
</section>
</template>
<script>
import store from './store';
import TodoInput from './components/TodoInput.vue';
import TodoList from './components/TodoList.vue';
export default {
name: 'app',
store,
components: {
TodoInput,
TodoList
}
};
</script>
<style>
@import url("https://unpkg.com/todomvc-app-css@2.2.0/index.css");
</style>
<template>
<section class="main" v-show="todos.length" v-cloak>
<input class="toggle-all" type="checkbox">
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
>
<todo-item
:todo="todo" />
</li>
</ul>
</section>
</template>
<script>
import TodoItem from './TodoItem.vue';
export default {
name: 'TodoList',
components: {
TodoItem
},
computed: {
todos() {
return this.$store.state.todos;
},
filteredTodos() {
return this.$store.getters.filteredTodos;
}
}
};
</script>
<style scoped>
[v-cloak] {
display: none;
}
</style>
<template>
<input
v-model="newTodo"
class="new-todo" autofocus autocomplete="off"
placeholder="What needs to be done?"
@keypress.enter="addTodo"
>
</template>
<script>
export default {
name: 'TodoInput',
data() {
return {
newTodo: ''
};
},
methods: {
addTodo() {
this.$store.commit('addTodo', this.newTodo);
this.newTodo = '';
}
}
};
</script>
サンプル002■vue-vuex-todo-mvc-01
Vue.js + Vuex入門
- Vue.js + Vuex入門 01: Storeを使う
- Vue.js + Vuex入門 02: クラスバインディングと項目の削除
- Vue.js + Vuex入門 03: データの変化に応じた処理とローカルへの保存
- Vue.js + Vuex入門 04: リスト表示する項目を選び出して切り替える
- Vue.js + Vuex入門 05: チェックをまとめてオン/オフする
- Vue.js + Vuex入門 06: チェックした項目をまとめて削除する
- Vue.js + Vuex入門 07: 項目のテキストをダブルクリックで再編集する
- Vue.js + Vuex入門 08: フォーカスをコントロールする
作成者: 野中文雄
作成日: 2019年09月19日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.