有没有一种方法来检测一个作用域插槽是否被传递给了一个组件?

4

我有一个组件,嵌套在几个层次的组件中。可以将作用域插槽传递到此组件中。我想检测它不存在时。

示例:

Vue.component("parent", {
    template: `
        <child>
            <template slot="expand" slot-scope="{data}" v-if="$scopedSlots.expand">
                <slot name="expand" :data="data"></slot>
            </template>
        </child>
`,
});

Vue.component("child", {
    template: `
        <div>
            <slot name="expand" :data="1"></slot>
            <div @click="$scopedSlots.expand && log()">Child</div>
        </div>
`,
    methods: {
        log() {
            console.log(this.$scopedSlots.expand);
            console.log("This shouldn't work");
        }
    }
});

new Vue({
    el: "#app",
    template: `<parent></parent>`,
})

CodePen

在这个例子中,作用域插槽从根组件没有传递到<parent>。因此,我原本认为$scopedSlots.expand<parent>中的v-if都是假值,所以不会向<child>传递插槽,点击也不会有任何效果。

然而,这种想法是错误的,有两个原因:

  • 由于$scopedSlots.expand在内部被存储为函数,它总是求值为真值,您可以在CodePen的控制台中看到。
  • 似乎<template>始终会渲染,即使使用v-if="false"。因此,即使在<parent>$scopedSlots.expand求值为假值,仍会向<child>传递一个空插槽,并且<child>内的$scopedSlots.expand仍将求值为真值。

有没有办法让它工作?

我知道我可以通过传递一个布尔属性来实现是否处理点击的功能。我正在尝试弄清楚是否可以从插槽本身确定。


插槽模板将被编译为“渲染函数”,即使使用了“v-if”,它也只是该“渲染函数”内的一行逻辑代码。如果仅存在<slot name="expand" :data="data"></slot>作为<template slot="expand">的子元素,您可以使用this.$parent.$slots.expand - Sphinx
@Sphinx 确实如此。但是如果有多个嵌套级别,那么这种方法就行不通了。 - Behind The Math
1
如果是这样,您可能需要使用默认插槽内容和引用。在这个CodePen中,存在作用域插槽,如果this.$refs.test为false,则为真。 - Sphinx
这是一个有趣的解决方法。 - Behind The Math
实际上,在我的实际用例中它不起作用,因为插槽是组件的一部分,当单击其余部分时会展开。因此,在单击处理程序完成之前,内容不会被呈现,而$refs.test将无法定义。 - Behind The Math
1个回答

5

我以前遇到过这个问题,非常烦人。

Vue模板编译器将编译此内容。

<template v-if="false"/>

转化为基本上

vm._e()

在这里,_e 是一个内部辅助方法,其实现是 createEmptyVNode。因此我们知道可以期望得到 空的虚拟节点 而不是假值。

如果我们进一步调查这个模板的编译代码:

<child>
  <template slot="expand" slot-scope="scope" v-if="false"/>
</child>

我们得到了这段代码(我稍微简化了一下,只显示了相关的部分)
_c('child', {
  scopedSlots: {
    expand(scope) {
      return undefined
    }
  }
})

总结一下:v-if="false"并不意味着作用域插槽函数将被省略,而是会提供该函数,但在调用时将返回undefined。因此,在子组件中,您可以期望$scopedSlots.expand存在,但在调用时将返回undefined
据我所知,没有简单的方法来处理这个问题。对于您的示例,一个快速的“修复”方法是:
<div @click="$scopedSlots.expand() && log()">Child</div>
                                ^^

但要注意

  • 你的父组件是否能够处理不存在的{data}时的slot-scope="{data}"?最好使用真实的scope数据调用$scopedSlots.expand(scope)
  • 在渲染周期中多次调用$scopedSlots.expand()并丢弃可能为真值的结果可能会导致性能问题。

顺便说一下,我曾经尝试将v-ifslot分开放在不同的<template>节点中,但效果参差不齐。

<template v-if="$scopedSlots.expand">
  <template slot="expand" slot-scope="{data}">
    <slot name="expand" :data="data"/>
  </template>
</template>

我可以确认,像@DecadeMoon建议的那样在v-if外部再包装另一个template对我有效。在测试子组件中的$scopedSlots之前,我还加入了$nextTick,因为在我的情况下似乎这是必要的。是否存在不适用的情况? - shaunc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接