在Vue 3 Composition API组件中,遇到了错误"Non-function value encountered for default slot."

回答 2 浏览 4981 2021-11-07

MCVE

https://github.com/hyperbotauthor/minvue3cliapp

MCVE代码展示

https://codesandbox.io/s/white-browser-fl7ji

我有一个Vue 3 cli-service应用程序,它使用了带有插槽的composition API组件。

HelloWorld组件在div中渲染它所收到的slots。

// src/components/Helloworld.js
import { defineComponent, h } from "vue";

export default defineComponent({
  setup(props, { slots }) {
    return () => h("div", {}, slots);
  }
});

Composite组件在其setup功能中使用HelloWorld,并填入其槽位中。

// src/components/Composite.js
import { defineComponent, h } from "vue";

import HelloWorld from "./HelloWorld";

export default defineComponent({
  setup(props, { slots }) {
    return () =>
      h(HelloWorld, {}, [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])]);
  }
});

该应用程序使用这两种方式来渲染相同的两个div。

<template>
  <!--<img alt="Vue logo" src="./assets/logo.png">-->
  Works with plain slots
  <HelloWorld>
    <div>Div 1</div>
    <div>Div 2</div>
  </HelloWorld>
  Triggers warning when slots are used from other component
  <Composite> </Composite>
</template>

<script>
import HelloWorld from "./components/HelloWorld";
import Composite from "./components/Composite";

export default {
  name: "App",
  components: {
    HelloWorld,
    Composite,
  },
};
</script>

<style>
</style>

Composite组件触发了这一警告。

Non-function value encountered for default slot. Prefer function slots for better performance. 

当我只从模板中使用HelloWorld时,同样的警告并没有被触发。

我不明白,如果我使用模板中的插槽或其他组件中的插槽有什么区别。

这个警告的意义是什么呢?

我有什么办法可以删除这个警告吗?

hyperbotauthor 提问于2021-11-07
2 个回答
#1楼 已采纳
得票数 26

该警告是关于setup()的渲染函数中Composite.js创建的VNode数组的。

// src/components/Composite.js
export default defineComponent({
  setup(props, { slots }) {
    return () =>
      h(HelloWorld, {}, [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])]);
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  }
});

这是低效的,因为子槽在HelloWorld组件使用它之前就被渲染了。子槽基本上是在父类中渲染的,然后再传递给子类。将子槽的生成包裹在一个函数中,将工作推迟到子槽被渲染。

我不明白,如果我使用模板中的插槽或其他组件中的插槽有什么区别。

@vue/compiler-sfc将来自SFC的<template>编译成一个渲染函数,其中槽是作为函数传递的,这就避免了你所观察到的警告。

解决方案

与其在父类中渲染子槽(即直接传递一个VNodes的数组作为slots的参数),不如将其包裹在一个函数中。

// src/components/Composite.js
export default defineComponent({
  setup(props, { slots }) {
    return () =>          👇
      h(HelloWorld, {}, () => [h("div", {}, ["Div 1"]), h("div", {}, ["Div 2"])]);
  }
});

注意内部的h()调用不需要这个函数包装器,因为它们都与子项的默认插槽一起呈现。

示范

tony19 提问于2021-11-08
tony19 修改于2022-01-13
#2楼
得票数 0

当你在构造嵌套组件时向children参数传递一个空的数组时,你也很容易得到这个错误。

例如:

const children = () => {
  const marker = h(ATreeDefaultMarker, {
    hasChildren: item.children.length > 0,
    isOpen: item.open ?? false,
  }, []); // <--------------------- This

  const indent = h(ATreeDefaultIndent, {
    item,
  }, []); // <--------------------- This

  const display = h(ATreeDefaultItem, { item, events });
  return [indent, marker, display];
};

return () => h(ATreeDefaultLine, children)];

尽管对h的顶层调用正在返回一个VNode的数组,以放入default槽中,但children却意外地被渲染成一个空数组的子列表。

h(..., props, []) <------像这样。

Vue无法区分一个空的子数组和一个真正非空的子数组,所以它引发了一个错误,比如说。

Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance. 
 at <ATreeDefaultIndent> 
 at <ATreeDefaultLine> 

警告只是说子数组不是一个函数,在这种情况下,它没有什么区别。

......然而,linter无法看出这一点。

tldr

如果你看到这个错误,也值得检查一下你是不是不小心把一个空的子列表(如[])传给了h,这是不小心的。

Doug 提问于2022-06-25