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>