请教一个 vue3 自定义组件传 h 函数问题

218 天前
 weixiaoD

我是用 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>
1691 次点击
所在节点    Vue.js
14 条回复
renmu
218 天前
warning 不是 error ,能跑就行🐶
weixiaoD
218 天前
@renmu 不可以,因为有几千条数据,有多少条他就报多少个,然后整个页面就卡死了
alvinbone88
218 天前
报错的地方和 icon 无关,是 NText 有问题,传 slot 的时候用的不是函数
还有,都用 h 函数了,为什么不直接用 jsx 呢
RabbitDR
218 天前
有点怪,为啥要自己写 vnode ,完全可以弄个 jsx 。
关于你的问题:
首先,你定义的 icon 不是一个函数,h 函数返回一个 vnode ,所以你的 icon 是一个 vnode (一个对象)。
其次,h 函数可以接受一个 string ,或者一个 Children ,而 Slot 必须是一个返回 Children 的函数。
综上,你可以在 suffix slot 里这样写
suffix: () => h(NIcon, { default: () => item.icon })
lisongeee
218 天前
看起来用的 naiveui ,框架挺好用的,就是它那个 B 文档示例有 jsx 不用偏要用 h 函数

所以这个框架的初学者基本都会傻乎乎地去手写 h 函数

我刚学的时候搞得我以为只能传递手写 h 函数整的代码密密麻麻的看得累死我了
leokun
218 天前
weixiaoD
218 天前
@lisongeee 哈哈哈, 我就是看他那个示例代码里, 都是用 h 函数实现的,刚好我又能理解这个逻辑,所以就也跟着他去写了,你看下我附加的代码内容, 浏览器 console 有警告, 但我找不出问题在哪里
weixiaoD
218 天前
@alvinbone88
@RabbitDR
@lisongeee 我附加里的代码内容可以帮忙看看是哪里的问题吗?
alvinbone88
218 天前
weixiaoD
217 天前
@alvinbone88 好像确实是这个问题,现在我改了函数返回,但是还有有一个 warning, 我找不出是哪部分代码发出的
![6cedc03957a7eedee9bc5.png]( https://i.9m.pw/file/6cedc03957a7eedee9bc5.png)
![80ca7e1bf21bc359e13e3.png]( https://i.9m.pw/file/80ca7e1bf21bc359e13e3.png)
alvinbone88
217 天前
NList 的 slot 还是数组
weixiaoD
217 天前
@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)
alvinbone88
217 天前
computed 的使用方式不对
https://vuejs.org/api/reactivity-core.html#computed
这里应该是 isSelected.value(item)
weixiaoD
217 天前
@alvinbone88 这么尴尬,忘记+value 了,哈哈,粗心了,已经搞定,感谢感谢

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1036865

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX