menu
書いてる野郎
orebike@gmail.com
file と folder で動的コンポーネントで切り替えられたのは両方共 props に item を取るというインターフェースだったからだ。そして中身自体も似ているわけだ。
現状は folder のほうが file よりもいろいろやることがあって、 ということは folder は file の発展版と考えられる。
プログラミングでこういう考え方をするのは、オブジェクト指向の「継承」である。
Vue.js にも継承の考え方が出来る仕組みがあるので、今回はそれを試してみる。
file のコンストラクタを作る
まずは file のコンストラクタを作る。Vueオブジェクトのコンストラクタがあるなら file のコンストラクタも欲しいということである。
このように作る。これにより Vue を継承した File コンストラクタが出来上がった。
var File = Vue.extend({ template: '<li class="file">{{ item.name }}</li>', store: store, props: ['item'] });
なのでこいつはこのようにオブジェクトを生成することができる
var fileVm = new File();
今回はコンポーネントとして使いたいので、このように記述するとこの定義をコンストラクタに登録することができる
Vue.component("file", File);
そして動作させると以前と変わらない動作をすることがわかるだろう。
ということでこいつをさらに継承して root-folder と folder も作ってみよう
このようになる。
var File = Vue.extend({ template: '<li class="file">{{ item.name }}</li>', store: store, props: ['item'] }); var RootFolder = File.extend({ template: ` <ul> <li v-for="(aitem, i) in items"> <component v-bind:is="aitem.type" v-bind:item="aitem"></component> </li> </ul>`, props: ['items'] }); var Folder = File.extend({ 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>`, methods: { toggleOpenClose: function(){ this.$store.dispatch("toggleOpenClose", this.item.id); } } }); Vue.component("file", File); Vue.component("root-folder", RootFolder); Vue.component("folder", 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}); } } }); // ■■■■■■■■ 今回はココ ■■■■■■■■ var File = Vue.extend({ template: '<li class="file">{{ item.name }}</li>', store: store, props: ['item'] }); var RootFolder = File.extend({ template: ` <ul> <li v-for="(aitem, i) in items"> <component v-bind:is="aitem.type" v-bind:item="aitem"></component> </li> </ul>`, props: ['items'] }); var Folder = File.extend({ 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>`, methods: { toggleOpenClose: function(){ this.$store.dispatch("toggleOpenClose", this.item.id); } } }); Vue.component("file", File); Vue.component("root-folder", RootFolder); Vue.component("folder", Folder); var vm = new Vue({ el: "#app", store: store, computed: { rootItems: function(){ return this.$store.state.items; } } }); </script> </body> </html>