【问题标题】:Vue component as child of another componentVue 组件作为另一个组件的子组件
【发布时间】:2019-02-03 11:46:28
【问题描述】:

我正在努力将现有主题转换为可重复使用的组件。

我目前有一个这样的按钮组件:

<template>
    <a :href="link" class="button" :class="styling"><slot></slot></a>
</template>

<script>
export default {
    props: {
        link: {},
        styling: {
            default: ''
        }
    }
}
</script>

而且,在我的 HTML 中,我是这样使用它的:

<vue-button link="#" styling="tiny bg-aqua">Button 1</vue-button>

现在,我正在尝试使用现有的按钮组件创建一个“按钮组”。

我想做的是这样的:

<vue-button-group styling="radius tiny">
    <vue-button link="#" styling="tiny bg-aqua">Button 1</vue-button>
    <vue-button link="#" styling="tiny bg-aqua">Button 2</vue-button>
    <vue-button link="#" styling="tiny bg-aqua">Button 3</vue-button>
    <vue-button link="#" styling="tiny bg-aqua">Button 4</vue-button>
</vue-button-group>

我对 VueJS 很陌生,对处理这种事情的正确方法有点困惑。我希望能够根据需要将尽可能多的按钮组件传递到组中。

这是迄今为止我所拥有的按钮组:

<template>
    <ul class="button-group" :class="styling">
        <li><slot></slot></li>
    </ul>
</template>

<script>
    export default {
        props: {
            styling: {
                default: ''
            }
        }
    }
</script>

当然,这可以使用传入的单个按钮,但我似乎无法弄清楚如何允许更多,同时将每个按钮包含在自己的列表项中。

非常感谢对我的处理方式提出任何建议或更正。谢谢。

【问题讨论】:

  • 作为一个方面没有,你不需要通过styling="tiny bg-aqua"类是自动绑定的,所以使用class="tiny bg-aqua"具有相同的结果
  • 也可能有几种方法来处理这个问题(slots、named Slots、props),最好的解决方案取决于你如何构建数据,以及你想要的最终结果是什么。
  • @Daniel 你是 100% 正确的。我没有意识到你可以在 Vue 组件上使用“class”属性而不覆盖默认包含在组件中的基类。
  • 知道了,我误解了问题
  • 这在 Vue 中并不完全直截了当。我建议使用渲染函数,但它们并不完全适合初学者。我建议如果这是你可以用 css 解决的问题,那可能更容易实现和维护。

标签: javascript html vue.js vuejs2


【解决方案1】:

由于您想用组件的输出做一些高级的事情,现在可能是去render functions 的时候了,因为它们提供了更大的灵活性:

Vue.component('button-group', {
    props: {
        styling: {
            default: ''
        }
    },
    render(createElement) { // createElement is usually called `h`
        // You can also do this in 1 line, but that is more complex to explain...
        // const children = this.$slots.default.filter(slot => slot.tag).map(slot => createElement('li', {}, [slot]))
        const children = [];
        for(let i = 0; i < this.$slots.default.length; i++) {
            if(!this.$slots.default[i].tag) {
                // Filter out "text" nodes, so we don't make li elements
                // for the enters between the buttons
                continue;
            }
            children.push(createElement('li', {}, [
                this.$slots.default[i]
            ]));
        }

        return createElement('ul', {staticClass: "button-group",class: this.styling}, children);
    }
})

var app = new Vue({
  el: '#app',
})
.rainbow-background {
    /* todo: implement rainbow background */
    border: red 1px solid;
}
.button-group {
    border-color: blue;
}
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<div id="app">
    <button-group styling="rainbow-background">
        <button>hi</button>
        <button>How are you?</button>
        <button>I'm fine</button>
        <button>Have a nice day!</button>
    </button-group>
</div>

渲染函数通过返回一个虚拟 html 结构来工作,这个结构是通过重复调用 createElement 函数生成的。 createElement 接受 3 个参数、一个标签名称(如 ulli)、一个选项对象和一个子项列表。

我们首先生成一个包含传入槽的子列表,这些槽存储在this.$slots.default 中。

然后我们遍历这个列表,过滤掉基本上是文本的传入槽数据,这是因为 HTML 将标签之间的空白视为文本的方式。

我们现在几乎完成了我们的最终结构,我们现在将 slot 元素包装在一个新生成的 li 标签中,然后我们将所有内容包装在一个带有正确类名的新 ul 标签中,从而完成生成.

【讨论】:

  • 哈,打败我了...无论如何,这里是 renderFunction 解决方案的代码框的链接codesandbox.io/s/o1qkrvmq4z(减去文本节点的处理)
  • 这绝对比我预期的要先进,但它确实可以根据需要工作。谢谢你的回答+解释。将来我一定会更多地研究渲染函数。
  • @Daniel 你仍然向我展示/教我在使用 .vue 文件时不必担心那些文本节点,我只是碰巧发现当我开始使用 html 模板时它们很烦人对于这个答案。但是,当您突然切换编写 Vue 的环境时,为这些添加一个过滤器可以防止出现不好的情况
【解决方案2】:

Vue 做到这一点的方法是使用名称槽,并将子级使用的数据提供给父级。数据将通过slot-scope 传播给孩子。

这一切的关键是数据从父级流向子级

这是该过程的工作代码:https://codepen.io/Flamenco/pen/ZMOdYz

本示例使用默认槽,因此父子节点不需要name 属性。

家长

<template>
    <ul class="button-group" :class="styling">
        <li v-for='item in items'><slot :item='item'></slot></li>
    </ul>
</template>

<script>
   ...
   props:{
      items: Array
   }
</script>

孩子

<vue-button-group class="radius tiny" :items='items'>
    <template slot-scope='scope'>
       <vue-button link="#" styling="tiny bg-aqua">{{scope.item.text}}</vue-button>
   </template>
</vue-button-group>


<script>
   ...
   data:{
      items:[{text:'Button 1'},{text:'Button 2'},{text:'Button 3}]
   }
</script>

【讨论】:

  • 与在按钮组组件本身中进行循环相比,这种方法带来了什么?
【解决方案3】:

一个可能的解决方案是使用v-for

<button v-for="button in buttons" :key="button">
Button{{button}}
</button>

这里是fiddle

从那里您可以自己构建您的&lt;buttongroup&gt;-组件;将“元信息”作为props 传递到您的&lt;buttongroup&gt;(在我的小提琴中data-part 中的数组)。

Slots 是有意义的,如果你想渲染按钮之外的其他东西并且你想为此注入组件。否则,您将无法从插槽中获得任何收益。

【讨论】:

  • 抱歉,但我很难理解 v-for 在这种情况下如何工作;无需将按钮的数量硬编码到组件本身中(就像在你的小提琴中一样)。
  • 你可以,正如我所说,从父级传递一个数组作为道具,你的按钮组迭代道具。这个数组可以是动态的。
【解决方案4】:

您也可以尝试使用named slot

<template>
    <ul class="button-group">
        <li><slot name="buttons"></slot></li>
    </ul>
</template>

比:

<vue-button-group class="radius tiny">
    <template slot="buttons">
       <vue-button link="#" styling="tiny bg-aqua">Button 1</vue-button>
       <vue-button link="#" styling="tiny bg-aqua">Button 2</vue-button>
       <vue-button link="#" styling="tiny bg-aqua">Button 3</vue-button>
       <vue-button link="#" styling="tiny bg-aqua">Button 4</vue-button>
   </template>
</vue-button-group>

【讨论】:

  • 但这不会将所有 4 个按钮呈现到同一个列表项中吗?这就是我目前遇到的问题。
猜你喜欢
  • 2019-11-19
  • 2019-02-20
  • 2022-11-04
  • 2020-07-02
  • 2021-02-14
  • 2020-07-04
  • 2021-02-16
  • 2020-09-25
  • 2020-07-23
相关资源
最近更新 更多