我是用 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 2024-04-29 20:17:05 +08:00 via Android warning 不是 error ,能跑就行 |
3 alvinbone88 2024-04-29 21:38:53 +08:00 报错的地方和 icon 无关,是 NText 有问题,传 slot 的时候用的不是函数 还有,都用 h 函数了,为什么不直接用 jsx 呢 |
4 RabbitDR 2024-04-29 21:46:41 +08:00 ![]() 有点怪,为啥要自己写 vnode ,完全可以弄个 jsx 。 关于你的问题: 首先,你定义的 icon 不是一个函数,h 函数返回一个 vnode ,所以你的 icon 是一个 vnode (一个对象)。 其次,h 函数可以接受一个 string ,或者一个 Children ,而 Slot 必须是一个返回 Children 的函数。 综上,你可以在 suffix slot 里这样写 suffix: () => h(NIcon, { default: () => item.icon }) |
![]() | 5 lisongeee 2024-04-29 21:57:33 +08:00 看起来用的 naiveui ,框架挺好用的,就是它那个 B 文档示例有 jsx 不用偏要用 h 函数 所以这个框架的初学者基本都会傻乎乎地去手写 h 函数 我刚学的时候搞得我以为只能传递手写 h 函数整的代码密密麻麻的看得累死我了 |
![]() | 6 leokun 2024-04-29 23:45:45 +08:00 |
![]() | 7 weixiaoD OP @lisongeee 哈哈哈, 我就是看他那个示例代码里, 都是用 h 函数实现的,刚好我又能理解这个逻辑,所以就也跟着他去写了,你看下我附加的代码内容, 浏览器 console 有警告, 但我找不出问题在哪里 |
![]() | 8 weixiaoD OP |
9 alvinbone88 2024-04-30 00:44:51 +08:00 |
![]() | 10 weixiaoD OP @alvinbone88 好像确实是这个问题,现在我改了函数返回,但是还有有一个 warning, 我找不出是哪部分代码发出的   |
11 alvinbone88 2024-04-30 10:31:38 +08:00 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 的值, 不过我还是想学一下计算属性的方法, 不知道可以实现不?  |
13 alvinbone88 2024-04-30 15:05:33 +08:00 ![]() |
![]() | 14 weixiaoD OP @alvinbone88 这么尴尬,忘记+value 了,哈哈,粗心了,已经搞定,感谢感谢 |