menu
書いてる野郎
orebike@gmail.com
前回コンポーネントに分割してそれで再構築した。 そこでこのような記述を使った。
Vue.component("folder", { template: ` <li v-bind:class="['folder', {close: !item.isOpen}]"> <h2 v-on:click="toggleOpenClose">{{ item.name }}</h2> <ul> <li v-for="(aitem, i) in item.items"> <template v-if="aitem.type === 'folder'"> <folder v-bind:item="aitem"></folder> </template> <template v-else> <file v-bind:item="aitem"></file> </template> </li> </ul> </li>`, // 中略 });
type によって分岐しているのだが、本当にそれだけで後はコンポーネントの名前が違うだけで 後は同じなのである。
このような外見は同じで機能が違うようなコンポーネントは動的コンポーネントの仕組みを使うとうまくまとめることができる。
Vue.component("folder", { template: ` <li v-bind:class="['folder', {close: !item.isOpen}]"> <h2 v-on:click="toggleOpenClose">{{ item.name }}</h2> <ul> <li v-for="(aitem, i) in item.items"> <component v-bind:is="aitem.type" v-bind:item="aitem"></component> </li> </ul> </li>`, // 中略 });
分岐がなくなって 1個のタグになった。
この部分は、aitem.type で示される名前のコンポーネントを使えという意味である。
<component v-bind:is="aitem.type" v-bind:item="aitem"></component>
登録した file コンポーネントも folder コンポーネントも同じパラメータをバインドするのでこのようなことができる。
root-folder の記述も合わせて直しておこう。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello</title>
<script src="https://unpkg.com/vue@latest"></script>
<script src="https://unpkg.com/vuex@latest"></script>
<style>
#app li.folder.close ul{
display: none;
}
</style>
</head>
<body>
<div id="app">
<root-folder v-bind:items="rootItems"></root-folder>
</div>
<script>
var data = {
id: 1, name: "", type: "folder",
isOpen: true,
items: [
{
id: 2, name: "aaa", type: "folder",
isOpen : false,
items: [
{ id: 3, name: "bbb", type: "file" },
{ id: 4, name: "ccc", type: "file" },
{ id: 5, name: "ddd", type: "file" }
]
},
{ id: 6, name: "eee", type: "file" },
{
id: 7, name: "fff", type: "folder",
isOpen: false,
items: [
{ id: 8, name: "ggg", type: "file" },
{
id: 9, name: "hhh", type: "folder",
isOpen: false,
items: [
{ id: 10, name: "iii", type: "file" },
{ id: 11, name: "jjj", type: "file" }
]
}
]
},
{ id: 12, name: "kkk", type: "file" }
]
};
var store = new Vuex.Store({
state: data,
mutations: {
toggleOpenClose: function(state, {getters, id}){
var item = getters.findById(id);
if(item == null){
return;
}
if(item.type !== 'folder'){
return;
}
item.isOpen = !item.isOpen;
},
},
getters: {
findById: (state, getters) => (_id, _item) => {
if(_item == null){
return getters.findById(_id, state);
}
if(_item.id === _id){
return _item;
}
if(_item.type === 'file'){
return null;
}
for(var i = 0; i < _item.items.length; i++){
var tmp = getters.findById(_id, _item.items[i]);
if(tmp != null){
return tmp;
}
}
return null;
}
},
actions: {
toggleOpenClose: function(context, id){
context.commit('toggleOpenClose', {getters: context.getters, id: id});
}
}
});
Vue.component("root-folder", {
template: `
<ul>
<li v-for="(aitem, i) in items">
<component v-bind:is="aitem.type" v-bind:item="aitem"></component>
</li>
</ul>`,
store: store,
props: ['items']
});
Vue.component("folder", {
template: `
<li v-bind:class="['folder', {close: !item.isOpen}]">
<h2 v-on:click="toggleOpenClose">{{ item.name }}</h2>
<ul>
<li v-for="(aitem, i) in item.items">
<component v-bind:is="aitem.type" v-bind:item="aitem"></component>
</li>
</ul>
</li>`,
store: store,
props: ['item'],
methods: {
toggleOpenClose: function(){
this.$store.dispatch("toggleOpenClose", this.item.id);
}
}
});
Vue.component("file", {
template: '<li class="file">{{ item.name }}</li>',
store: store,
props: ['item']
});
var vm = new Vue({
el: "#app",
store: store,
computed: {
rootItems: function(){
return this.$store.state.items;
}
}
});
</script>
</body>
</html>