Vue.js/ファイラーっぽいUIを作ってみる/100_動的コンポーネント

Vue.js/ファイラーっぽいUIを作ってみる/100_動的コンポーネント

前回コンポーネントに分割してそれで再構築した。 そこでこのような記述を使った。

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>
javascript/vuejs/create_filer_like_ui/100_dynamic_component.txt · 最終更新: 2018-11-24 01:33 by ore