【问题标题】:How to toggle active class in recursive component in Vue?如何在 Vue 的递归组件中切换活动类?
【发布时间】:2020-03-12 12:50:23
【问题描述】:

我有树组件,比如默认的 Vue 树视图示例。 我想在单击树项时切换活动类。但是当我点击时,每个元素都有活动类:(我不明白我做错了什么。在非递归组件中工作正常。请帮忙。

这是我的代码:

new Vue({
        el: '#groups',
        data: {
            hasLoaded: true,
            treeData: [
                {
                    "uuid": 1,
                    "name": "Group1",
                    "children": [
                        {
                            "uuid": 2,
                            "name": "Group2"
                        },
                        {
                            "uuid": 3,
                            "name": "Group3"
                        }
                    ]
                },
                {
                    "uuid": 4,
                    "name": "Group4",
                    "children": [
                        {
                            "uuid": 5,
                            "name": "Group5",
                            "children": [
                                {
                                    "uuid": 7,
                                    "name": "Group7"
                                }
                            ]
                        }
                    ]
                },
                {
                    "uuid": 6,
                    "name": "Group6"
                }
            ]
        }
});

我的物品模板:

<script type="text/x-template" id="item-template">
    <li class="list-group-item group-item">
        <div
            :class="{bold: isFolder, active : isActiveItem}"
            @click="selectGroup"
        >
            {{ item.name }}
            <span v-if="isFolder" @click="toggle">[{{ isOpen ? '-' : '+' }}]</span>
        </div>
        <ul v-show="isOpen" v-if="isFolder" class="list-group">
            <tree
                class="item"
                v-for="(child, index) in item.children"
                :key="index"
                :item="child"
            ></tree>
        </ul>
    </li>
</script>

        <div id="groups">
            <ul id="demo" class="list-group">
                <tree
                    v-for="(item, index) in treeData"
                    class="item"
                    :item="item"
                    :key="index"
                ></tree>
            </ul>
        </div>

和组件

Vue.component('tree', {
    template: '#item-template',
    props: {
        item: Object
    },
    data: function () {
        return {
            isOpen: false,
            isItemActive: false,
            currentItem: null
        }
    },
    computed: {
        isFolder: function () {
            return this.item.children && this.item.children.length
        },
        isActiveItem: function () {
            return this.currentItem === this.item.uuid;
        }
    },
    methods: {
        toggle: function () {
            if (this.isFolder) {
                this.isOpen = !this.isOpen
            }
        },
        selectGroup: function () {
            this.currentItem = this.item.uuid;
        }
    }
});

【问题讨论】:

  • 我尝试运行您的代码,一切似乎都可以正常工作。当我单击一个节点时,该节点被选中。也许您可以明确说明您想要什么行为?您想单击一个节点两次以取消选择它吗?是否要单击一个节点以取消选择任何先前的选择(这样一次只能选择一个节点)?
  • "您是否要单击一个节点以取消选择任何先前的选择(这样一次只能选择一个节点)?" - 是的

标签: vue.js vuejs2 vue-component


【解决方案1】:

所以核心问题是每个组件实例都有自己的属性来保存currentItem。这不是共享的,因此一棵树中 currentItem 的值不会影响其他树的值。

要获得您想要的行为,需要某种形式的共享状态。有几种方法可以解决这个问题,包括:

  1. 共享全局状态,例如通过 Vuex 商店。这是在远距离相关组件之间共享数据的常用方法,尤其是在存在不关心该状态的中间组件的情况下。这与此处无关,因为树节点之间没有任何组件。
  2. 通过树节点向下传递反应性对象作为道具,当前选择保存在该对象的属性中。当所有节点都引用同一个对象时,对该对象的更改将自动在整个树中传播。包装对象的使用使这显得有些笨拙。它也被视为违反单向数据流,因为任何节点内都可能发生影响所有其他节点的突变。
  3. 通过道具向下传递当前选择并通过事件向上传播更改。涉及很多小部分,因此最终看起来有点混乱,但它确实遵守单向数据流的“规则”。

还有其他可行的选项,例如委托 DOM 事件,但对于下面的示例,我采用了上述第三种方法:props down, events up。

Vue.component('tree', {
    template: '#item-template',
    props: {
        item: Object,
        currentItem: Number
    },
    data: function () {
        return {
            isOpen: false
        }
    },
    computed: {
        isFolder: function () {
            return this.item.children && this.item.children.length
        },
        isActiveItem: function () {
            return this.currentItem === this.item.uuid;
        }
    },
    methods: {
        toggle: function () {
            if (this.isFolder) {
                this.isOpen = !this.isOpen
            }
        },
        selectGroup: function () {
            this.select(this.item.uuid);
        },
        select: function (uuid) {
            this.$emit('select', uuid);
        }
    }
});

new Vue({
    el: '#groups',
    data: {
        currentItem: null,
        treeData: [
            {
                "uuid": 1,
                "name": "Group1",
                "children": [
                    {
                        "uuid": 2,
                        "name": "Group2"
                    },
                    {
                        "uuid": 3,
                        "name": "Group3"
                    }
                ]
            },
            {
                "uuid": 4,
                "name": "Group4",
                "children": [
                    {
                        "uuid": 5,
                        "name": "Group5",
                        "children": [
                            {
                                "uuid": 7,
                                "name": "Group7"
                            }
                        ]
                    }
                ]
            },
            {
                "uuid": 6,
                "name": "Group6"
            }
        ]
    },
    methods: {
        onSelect: function (uuid) {
            if (this.currentItem === uuid) {
                this.currentItem = null;
            } else {
                this.currentItem = uuid;
            }
        }
    }
});
.bold {
  font-weight: bold;
}

.active {
  background: #000;
  color: #fff;
}
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>

<script type="text/x-template" id="item-template">
    <li class="list-group-item group-item">
        <div
            :class="{bold: isFolder, active : isActiveItem}"
            @click="selectGroup"
        >
            {{ item.name }}
            <span v-if="isFolder" @click="toggle">[{{ isOpen ? '-' : '+' }}]</span>
        </div>
        <ul v-show="isOpen" v-if="isFolder" class="list-group">
            <tree
                class="item"
                v-for="(child, index) in item.children"
                :current-item="currentItem"
                :key="index"
                :item="child"
                @select="select"
            ></tree>
        </ul>
    </li>
</script>

<div id="groups">
    <ul id="demo" class="list-group">
        <tree
            v-for="(item, index) in treeData"
            class="item"
            :current-item="currentItem"
            :item="item"
            :key="index"
            @select="onSelect"
        ></tree>
    </ul>
</div>

根组件获得了currentItem 属性,该属性通过currentItem 属性沿树向下传递。各个节点不再有本地数据来保存currentItem,它们只是使用道具代替。该道具被定义为Number 以匹配示例数据。我试图保留仅将uuid 存储为currentItem 的原始行为,尽管没有理由不能存储整个对象。

当一个树节点被点击时,它会发出一个select 事件和相应的uuid。这只会到达父组件,因此需要手动将事件一直传播到顶部,以便可以更新 currentItem 数据属性。然后,这个新值将通过 props 向下传递。

我还添加了一些代码以允许通过单击一个节点来取消选择它。如果您希望保留选择,则可以轻松省略。

【讨论】:

    猜你喜欢
    • 2018-03-25
    • 2018-02-24
    • 1970-01-01
    • 2020-08-10
    • 1970-01-01
    • 2016-08-20
    • 1970-01-01
    • 1970-01-01
    • 2018-09-01
    相关资源
    最近更新 更多