HTML5テクニカルノート
Vue.js + ES6入門 12: ローカルコンポーネントを定める
- ID: FN1911003
- Technique: HTML5 / ECMAScript 2015
- Library: Vue.js 2.6.10
「Vue.js + ES6入門 09: アプリケーションをコンポーネントに分ける」では、Vue.component()
メソッドでコンポーネントをつくって、アプリケーションに組み込みましました。今回は、親コンポーネントを特定して加えるローカルコンポーネントの定め方についてご説明します。
01 グローバルコンポーネントとローカルコンポーネント
「Vue.js + ES6入門 11」ででき上がったコード001に手を加えることにしましょう。アプリケーションのコンポーネントは、Vue.component()
メソッドでつくられています。第1引数がコンポーネント名の文字列、第2引数はコンポーネントのオプションオブジェクトです。Vue()
コンストラクタでインスタンスをつくれば、アプリケーションにコンポーネントが組み込まれます。親からのデータはv-bind
(省略記法@
)で渡し、子が受け取るのはprops
オプションです。子が$emit()
メソッドで送るイベントは、親はv-on
(省略記法@
)によりリスナーメソッドを呼び出します。
<script>要素Vue.component('list-header', { props: { todos: { type: Array, required: true }, }, methods: { archive() { this.$emit('archive'); } }, }); const app = new Vue({ data: { todos: [ ] }, methods: { archive() { }, } });
<body>要素<div id="app" class="container"> <list-header :todos="todos" @archive="archive" > </list-header> </div>
アプリケーションのVue
インスタンスにコンポーネントをグローバルに組み込むのが、Vue.component()
メソッドです。アプリケーションからただちに使えるものの、コンポーネントが増えて、とくに階層化されてくると、問題が起こっても原因を探しにくくなります。それに対して、親を決めてスコープを絞れるのが、ローカルコンポーネントです。
ローカルコンポーネントをつくるには、Vue.extend()
メソッドを用います。引数はコンポーネントに定めるオプションオブジェクトで、戻り値がVue
を継承したインスタンス(listHeader
)です。まだ、誰の子でもありません。親にするコンポーネントのcomponents
オプションに加えてはじめて子コンポーネントになります。オプションには、オブジェクトでコンポーネント名('list-header'
)とインスタンスを与えてください。components
に定めのない他のコンポーネントからは、この子コンポーネントは参照できません。親子間のデータの受け渡しはVue.component()
メソッドでつくったコンポーネントと同じです(書き替えは要りません)。
<script>要素// Vue.component('list-header', { const listHeader = Vue.extend({ }); const app = new Vue({ components: { 'list-header': listHeader, }, }
02 コンポーネントを入れ子にする
リスト表示のコンポーネントtodo-list
は、リスト項目の子コンポーネントtodo-item
をもちます。まず、リスト表示のコンポーネントのアプリケーションへの加え方は、前項01と変わりません。
<script>要素// Vue.component('todo-list', { const todoList = Vue.extend({ }); const app = new Vue({ components: { 'todo-list': todoList, }, });
つぎに、リスト項目のコンポーネント(todoItem
)は、やはりVue.extend()
メソッドでつくります。ただし、組み込む先はリスト表示コンポーネントtodoList
です。したがって、親のcomponents
オプションに加えなければなりません。ローカルコンポーネントにするとどうしても増えてしまうのが、親を決めるというひと手間です。そのかわり、子コンポーネントの働く範囲が絞れます。問題が起きたとき調べる範囲が狭まり、修正や管理もしやすくなるのです。親子の組み合わせを細かく決めることは、単一ファイルコンポーネントによる開発の考え方にも通じます。単一ファイルコンポーネントを使ったアプリケーションについては、「Vue.js + CLI入門」01-08をお読みください。
<script>要素// Vue.component('todo-item', { const todoItem = Vue.extend({ }); const todoList = Vue.extend({ components: { 'todo-item': todoItem }, template: ` <ul class="list-unstyled"> <li v-for="todo in filteredTodos" :key="todo.id"> <todo-item :todo="todo" @remove-todo="removeTodo"> </todo-item> </li> </ul> ` });
03 すべてローカルコンポーネントに差し替える
コンポーネントはあとふたつ残っています。けれど、入れ子でもありませんし、つぎのコード001にすべてローカルコンポーネントに書き替えたJavaScriptの記述全体をまとめて示しましょう。
コード001■ローカルコンポーネントに書き替えたアプリケーション
<script>要素
const filters = {
all(todos) {
return todos;
},
active(todos) {
return todos.filter((todo) =>
!todo.done
);
},
completed(todos) {
return todos.filter((todo) =>
todo.done
);
}
};
const listHeader = Vue.extend({
props: {
todos: {
type: Array,
required: true
},
remaining: {
type: Number,
required: true
}
},
methods: {
archive() {
this.$emit('archive');
}
},
template: `
<p>
全{{todos.length}}件中残り{{remaining}}件
<button class="btn btn-danger btn-sm" @click="archive">断捨離</button>
</p>
`
});
const todoItem = Vue.extend({
props: {
todo: {
type: Object,
required: true,
validator(value) {
return (
value.hasOwnProperty('text') &&
value.hasOwnProperty('done') &&
value.hasOwnProperty('id')
);
}
}
},
methods: {
removeTodo(todo) {
this.$emit('remove-todo', todo);
}
},
template: `
<div>
<label>
<input type="checkbox" v-model="todo.done">
<span :class="{'done': todo.done}">{{todo.text}}</span>
</label>
<button class="btn btn-warning btn-sm" @click="removeTodo(todo)">削除</button>
</div>
`
});
const todoList = Vue.extend({
components: {
'todo-item': todoItem
},
props: {
filteredTodos: {
type: Array,
required: true
}
},
methods: {
removeTodo(todo) {
this.$emit('remove-todo', todo);
}
},
template: `
<ul class="list-unstyled">
<li v-for="todo in filteredTodos" :key="todo.id">
<todo-item :todo="todo" @remove-todo="removeTodo">
</todo-item>
</li>
</ul>
`
});
const addTodo = Vue.extend({
data() {
return {
todoText: ''
};
},
methods: {
addTodo() {
const newTodo = this.todoText.trim();
this.todoText = '';
if (!newTodo) {return;}
this.$emit('add-todo', newTodo);
}
},
template: `
<p>
<input type="text" v-model="todoText" placeholder="add new todo here">
<button class="btn btn-primary btn-sm" @click="addTodo">追加</button>
</p>
`
});
const listFooter = Vue.extend({
props: {
visibility: {
type: String,
required: true,
validator(value) {
return (
['all', 'active', 'completed'].includes(value)
);
}
},
filters: {
type: Object,
required: true,
validator(value) {
return (
value.hasOwnProperty('all') &&
value.hasOwnProperty('active') &&
value.hasOwnProperty('completed')
);
}
}
},
methods: {
changeFilter(key) {
this.$emit('change-filter', key);
}
},
template: `
<div>
<button type="button"
v-for="(value, key) in filters"
:key="key"
:class="['btn btn-outline-info btn-sm mr-1', {active: visibility === key}]"
@click="changeFilter(key)">
{{ key[0].toUpperCase() + key.substr(1) }}
</button>
</div>
`
});
const app = new Vue({
components: {
'list-header': listHeader,
'todo-list': todoList,
'add-todo': addTodo,
'list-footer': listFooter
},
data: {
todos: [
{text: 'Vue.jsを学ぶ', done: true},
{text: 'Vue.jsでアプリケーションをつくる', done: false},
]
.map((todo, index) => ({...todo, id: index})),
visibility: 'all',
filters: filters
},
computed: {
remaining() {
const count =
this.todos.reduce((count, todo) =>
count = (todo.done) ? count : ++count
, 0);
return count;
},
filteredTodos() {
return filters[this.visibility](this.todos);
},
nextId() {
if (!this.todos.length) {return 0;}
const ids = this.todos.map((todo) => todo.id);
return Math.max(...ids) + 1;
}
},
methods: {
addTodo(newTodo) {
this.todos = [
...this.todos,
{text: newTodo, done: false, id: this.nextId}
];
console.log(this.todos);
},
removeTodo(todo) {
this.todos = this.todos.filter((_todo) => _todo !== todo);
},
archive() {
this.todos = this.todos.filter((todo) => !todo.done);
},
changeFilter(visibility) {
this.visibility = visibility;
}
}
});
document.addEventListener('DOMContentLoaded', () =>
app.$mount('#app')
);
サンプル001■Vue.js + ES6: Using local components
See the Pen Vue.js + ES6: Using local components by Fumio Nonaka (@FumioNonaka) on CodePen.
Vue.js + ES6
- Vue.js + ES6入門 01: Vue.jsを始める
- Vue.js + ES6入門 02: 要素のclass属性を動的に変える
- Vue.js + ES6入門 03: データから動的にリストをつくる
- Vue.js + ES6入門 04: フィールドに入力したテキストを動的に項目として加える
- Vue.js + ES6入門 05: 項目を数えて表示する
- Vue.js + ES6入門 06: 項目を調べてデータから削除する
- Vue.js + ES6入門 07: 表示する項目をフィルタで切り替える
- Vue.js + ES6入門 08: フィルタをボタンで切り替える
- Vue.js + ES6入門 09: アプリケーションをコンポーネントに分ける
- Vue.js + ES6入門 10: コンポーネントの応用とデータのチェック
- Vue.js + ES6入門 11: データチェックの応用とkey属性
- Vue.js + ES6入門 12: ローカルコンポーネントを定める
作成者: 野中文雄
作成日: 2019年11月17日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.