在Vue 3 Composition API组件中,遇到了错误"Non-function value encountered for default slot."
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
时,同样的警告并没有被触发。
我不明白,如果我使用模板中的插槽或其他组件中的插槽有什么区别。
这个警告的意义是什么呢?
我有什么办法可以删除这个警告吗?
该警告是关于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()
调用不需要这个函数包装器,因为它们都与子项的默认插槽一起呈现。
当你在构造嵌套组件时向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
,这是不小心的。