我是用 naiveUI 的 datatable 时自己写一个过滤菜单样式,我目前可以按照预期运行,传入 title 还有 value 还有图标的 src 链接. 格式
[{title:'标签名', value:'test', imgSrc='1.jpg'}]
现在我要允许传递一个 h 函数进去,像这样
[{title:'标签名', value:'test', imgSrc:'1.jpg', icon:h('div',null,'我是图标')}]
但是我不知道这个子组件接收到之后怎么把这个 icon 函数放进去 li 标签里面,直接在 h 函数里把 NList 标签用 h 函数来写,像下面的方法,但是他会报错,浏览器全都是下面的报错,我想问下这个要怎么处理才比较好一点
chunk-6SSRW7KQ.js?v=5c5b974b:1543 [Vue warn]: Non-function value encountered for default slot. Prefer function slots for better performance.
at <Text>
at <ListItem key="peersGettingFromUs" onClick=fn<onClick> class="padding" >
at <List hoverable=true clickable=true showDivider=false >
at <List>
at <ResizeObserver onResize=fn<handleContentResize> >
at <ResizeObserver onResize=fn<handleContainerResize> >
at <Scrollbar ref="scrollbarInstRef" xScrollable=false theme=undefined ... >
at <Scrollbar style= Object >
at <Menu options= Array(20) selected= Array(4) onUpdate:modelValue=fn<onUpdate:modelValue> >
at <DropdownRenderOption tmNode= Object key="header" >
at <DropdownMenu ref=fn class="n-popover-shared n-dropdown" clsPrefix="n" ... >
at <BaseTransition onEnter=fn onAfterLeave=fn<onAfterLeave> appear=true ... >
at <Transition name="popover-transition" appear=true onEnter=fn<onEnter> ... >
at <LazyTeleport show=true to="body" disabled=false >
at <Follower ref="followerRef" zIndex=undefined show=true ... >
at <PopoverBody theme= Object themeOverrides=undefined builtinThemeOverrides=undefined ... >
at <Binder ref="binderInstRef" syncTarget=true syncTargetWithParent=false >
at <Popover show=true defaultShow=false showArrow=false ... >
at <Dropdown trigger="hover" options= Array(1) >
at <Flex>
at <HelloWorld>
at <App>
const mylist = () => {
return h(NList, {
hoverable: true,
clickable: true,
showDivider: false,
},
props.options.map((item) => {
return h(NListItem,
{
key: item.title,
onClick: () => itemClick(item),
class: 'padding',
}, {
prefix: () => {
return h(NAvatar, {
size: 22,
src: item.imgSrc,
color: 'white',
bordered: true
})
},
default: () => {
return h(NText, null, item.title)
},
suffix: () => {
return h(NIcon,
{
// color: isSelected(item) ? '#1abc9c' : '#bdc3c7'
size: 20
}, {
default: () => {
return h(CheckmarkDoneCircle, null)
}
}
)
}
})
})
)
}
<template>
<n-scrollbar style="max-height: 600px">
<!-- <mylist /> -->
<n-list hoverable clickable :show-divider="false">
<n-list-item v-for="item in options" @click="itemClick(item)" :class="padding">
<template #prefix v-if="item.imgSrc">
<n-avatar :size="22" :src="item.imgSrc" color="white" :bordered="true" />
</template>
{{ item.title ? item.title : empty }}
<template #suffix>
<n-icon :color="isSelected(item) ? '#1abc9c' : '#bdc3c7'" size="20">
<CheckmarkDoneCircle />
</n-icon>
</template>
</n-list-item>
</n-list>
</n-scrollbar>
<n-button-group style="width:100%" size="large" :class="btn_width">
<n-button secondary @click="contrary" :disabled="isAll || isEmpty" v-if="contrary_btn">
<template #icon>
<n-icon :color="isEmpty ? 'black' : '#00b894'" class="rotate"
:style="{ transform: 'rotate(' + target.contrary + 'deg)' }">
<RepeatSharp />
</n-icon>
</template>
反选
</n-button>
<n-button secondary @click="_submit" :style="'width:' + 1 / 3">
<template #icon>
<n-icon>
<SaveOutline />
</n-icon>
</template>
保存
</n-button>
<n-button secondary @click="clear" :disabled="isEmpty" :style="'width:' + 1 / 3">
<template #icon>
<n-icon class="rotate" :color="isEmpty ? 'black' : '#e74c3c'">
<TrashBinOutline />
</n-icon>
</template>
清空
</n-button>
</n-button-group>
</template>
<script lang="ts" setup>
import { computed, h, reactive, ref } from 'vue'
import { CheckmarkDoneCircle, TrashBinOutline, RepeatSharp, SaveOutline } from '@vicons/ionicons5'
import { NAvatar, NIcon, NList, NListItem, NText } from 'naive-ui';
const props = defineProps({
selected: { type: Array<string | boolean>, required: true },
options: { type: Array<menu>, required: true },
padding: { type: String, required: false, default: 'small' },
contrary_btn: { type: Boolean, required: false, default: true },
// 当 title 为空字符串时显示的文本
empty: { type: String, required: false, default: '(empty)' },
renderIcon: { type: Function, required: false },
})
// 临时数组,用于保存用户已选中但是还未点击提交按钮的勾选值
const tmp_selected = ref<Array<string | boolean>>([...props.selected])
let btn_width = 'two'
if (props.contrary_btn) {
btn_width = 'there'
}
// 从传入的选项菜单里提取所有 value
const all = props.options.map(item => item.value)
const emit = defineEmits(['update:modelValue']);
interface menu {
title: string
value: string | boolean
desc?: string
imgSrc?: string
}
const itemClick = (item: menu) => {
if (isSelected.value(item)) {
tmp_selected.value = tmp_selected.value.filter(o => o !== item.value)
} else {
tmp_selected.value.push(item.value)
}
}
const _submit = () => {
emit('update:modelValue', [...tmp_selected.value]);
}
const target = reactive({
contrary: 0
})
// 反选函数
const contrary = () => {
target.contrary += 180;
tmp_selected.value = all.filter(item => !tmp_selected.value.includes(item))
console.log(tmp_selected.value)
}
// 清空函数
const clear = () => {
target.contrary = 0;
tmp_selected.value = []
emit('update:modelValue', [])
}
const isSelected = computed(() => {
return (item: menu) => {
return tmp_selected.value.some(o => o === item.value);
};
});
const isAll = computed(() => {
return tmp_selected.value.length === props.options.length
})
const isEmpty = computed(() => {
return tmp_selected.value.length === 0
})
</script>
<style scoped>
.two button {
width: 50%;
}
.there button {
width: 33.33%;
}
.n-list.n-list--hoverable .n-list-item.small {
padding: 7px 20px;
}
.n-list.n-list--hoverable .n-list-item.medium {
padding: 10px 20px;
}
.n-list.n-list--hoverable .n-list-item.large {
padding: 15px 20px;
}
.n-avatar {
vertical-align: middle;
}
.select {
background-color: aquamarine;
}
.rotate {
transition: all 0.5s ease;
}
.rotated {
transform: rotate(180deg);
}
</style>
可能是我表达不够清晰,主要的想法就是想让父组件传递一个Vnode进去给子组件去渲染, 但是他会在console里出现警告,而且会非常卡顿, 下面是我简化后的代码,大伙帮忙看看是哪里的问题.
至于为啥不用不用jsx, 因为我也是刚接触vue,而且需要自己定制的不多,就像自己写一个菜单, 所以就没有去学jsx,直接h函数就好了.
父组件:
<template>
<test1 :options="data" />
</template>
<script lang="ts" setup>
import test1 from './test.vue'
import { NButton } from 'naive-ui';
import { h,ref } from 'vue'
const data = ref([
{ title: 'title1', value: 'value1', icon: () => h(NButton, null, '我是按钮1') },
{ title: 'title2', value: 'value2', icon: () => h(NButton, null, '我是按钮2')},
{ title: 'title3', value: 'value3', icon: () => h(NButton, null, '我是按钮3')},
])
</script>
子组件
<template>
<VNode />
</template>
<script setup lang="ts">
import { h, } from 'vue';
import { NAvatar, NList, NListItem, NText } from 'naive-ui';
const props = defineProps({
options: { type: Array<menu>, required: true },
})
interface menu {
title: string
value: string | boolean
icon: Function
imgSrc?: string
}
const VNode = () => {
return h(NList, {
hoverable: true,
clickable: true,
showDivider: false,
},
props.options.map((item) => {
return h(NListItem,
{
key: item.title,
class: 'padding',
}, {
prefix: () => {
return h(NAvatar, {
size: 22,
src: item.imgSrc,
color: 'white',
bordered: true
})
},
default: () => {
return h(NText, null, item.title)
},
suffix: () => {
return item.icon()
}
})
})
)
}
</script>
1
renmu 210 天前 via Android
warning 不是 error ,能跑就行🐶
|
3
alvinbone88 210 天前
报错的地方和 icon 无关,是 NText 有问题,传 slot 的时候用的不是函数
还有,都用 h 函数了,为什么不直接用 jsx 呢 |
4
RabbitDR 210 天前 1
有点怪,为啥要自己写 vnode ,完全可以弄个 jsx 。
关于你的问题: 首先,你定义的 icon 不是一个函数,h 函数返回一个 vnode ,所以你的 icon 是一个 vnode (一个对象)。 其次,h 函数可以接受一个 string ,或者一个 Children ,而 Slot 必须是一个返回 Children 的函数。 综上,你可以在 suffix slot 里这样写 suffix: () => h(NIcon, { default: () => item.icon }) |
5
lisongeee 210 天前
看起来用的 naiveui ,框架挺好用的,就是它那个 B 文档示例有 jsx 不用偏要用 h 函数
所以这个框架的初学者基本都会傻乎乎地去手写 h 函数 我刚学的时候搞得我以为只能传递手写 h 函数整的代码密密麻麻的看得累死我了 |
6
leokun 210 天前
|
7
weixiaoD OP @lisongeee 哈哈哈, 我就是看他那个示例代码里, 都是用 h 函数实现的,刚好我又能理解这个逻辑,所以就也跟着他去写了,你看下我附加的代码内容, 浏览器 console 有警告, 但我找不出问题在哪里
|
8
weixiaoD OP |
9
alvinbone88 210 天前
|
10
weixiaoD OP @alvinbone88 好像确实是这个问题,现在我改了函数返回,但是还有有一个 warning, 我找不出是哪部分代码发出的
![6cedc03957a7eedee9bc5.png]( https://i.9m.pw/file/6cedc03957a7eedee9bc5.png) ![80ca7e1bf21bc359e13e3.png]( https://i.9m.pw/file/80ca7e1bf21bc359e13e3.png) |
11
alvinbone88 210 天前
NList 的 slot 还是数组
|
12
weixiaoD OP @alvinbone88 喔,可以了,谢谢; 还有一个疑问,就是怎么在 h 函数里使用计算属性呢? 我的组件逻辑是这样的, 用 NLi 标签列出按钮,然后通过点击这个按钮把 value 加到数组里, 如果这个 value 已经在数组里的就把他移除掉, 说白了就是一个多选组件, 然后我想他的 icon 颜色 选中就为绿色, 没选就黑色, 这里我用了计算属性来表达
``` const isSelected = computed(() => { return (item: menu) => { return tmp_selected.value.some(o => o === item.value); }; }); color: isSelected(item) ? '#1abc9c' : '#bdc3c7', ``` 我在 template 里是可以这样表达的 ``` <n-icon :color="isEmpty ? 'black' : '#00b894'"></n-icon> 但是现在我写进去 h 函数里,他不给我这样表达了,有报错,这种有啥好的处理逻辑吗? 我的想法是先给每一个 item 一个默认 color 属性,然后 icon 的 color:item.color, 最后通过 button click 事件去控制这个 item.color 的值, 不过我还是想学一下计算属性的方法, 不知道可以实现不? ![62ed599d648f00bcc97c3.png]( https://i.9m.pw/file/62ed599d648f00bcc97c3.png) |
13
alvinbone88 210 天前 1
|
14
weixiaoD OP @alvinbone88 这么尴尬,忘记+value 了,哈哈,粗心了,已经搞定,感谢感谢
|