vue3使用props传vue组件,实现类似slot的效果

403 阅读1分钟

应用背景说明

有一个虚拟列表的需求,期望虚拟列表的列表项组件能由外部自行指定(但又不希望使用slot), 这样虚拟列表抽取为独立组件就成为了可能

h函数用法

渲染函数 & JSX | Vue.js (vuejs.org)

render函数用法

sfc写法实现

<!--

@file: RenderTest.vue
@author: pan
-->
<script lang="ts">
export default {
  name: 'RenderTest',
}
</script>
<script setup lang="ts">
import { h, Component, onMounted, ref, render } from 'vue'

export interface ItemData {
  id: number
  contentData: unknown
}
const props = defineProps<{
  /**
   * 内部需要被渲染的组件
   */
  itemComp: Component
  /**
   * 渲染组件时需要的数据
   */
  itemData: ItemData
}>()

const vueCompContainerRef = ref<HTMLDivElement>()
onMounted(() => {
  const dom = vueCompContainerRef.value
  if (!dom) return

  render(
    // @ts-ignore
    h(props.itemComp, {
      id: props.itemData.id,
      contentData: props.itemData.contentData,
    }),
    dom
  )
})
</script>

<template>
  <div ref="vueCompContainerRef"></div>
</template>

<style lang="scss" scoped></style>

使用

列表项组件:

<!--

@file: OneItem.vue
@author: pan
-->
<script lang="ts">
export default {
  name: 'OneItem',
}
</script>
<script setup lang="ts">
defineProps<{
  id: number
  contentData: string
}>()
</script>

<template>
  <div class="item" :class="{ blue: id % 2 === 0 }">
    {{ id }}-{{ contentData }}
  </div>
</template>

<style lang="scss" scoped>
.item {
  border-bottom: 1px solid #333;
}
.blue {
  background-color: lightblue;
}
</style>

整合使用

<!--

@file: FunTest.vue
@author: pan
-->
<script lang="ts">
export default {
  name: 'FunTest',
}
</script>
<script setup lang="ts">
import OneItem from './test/OneItem.vue'
import RenderTest from './test/RenderTest.vue'
import { shallowRef } from 'vue'

const OneItemShallow = shallowRef(OneItem)
const list = new Array(100)
  .fill(true)
  .map((_, idx) => ({ id: idx, contentData: '内容:' + idx }))
</script>

<template>
  <div class="outter">
    <RenderTest
      v-for="item in list"
      :key="item.id"
      :item-comp="OneItemShallow"
      :item-data="item"
    ></RenderTest>
  </div>
</template>

<style lang="scss" scoped>
.outter {
  height: 500px;
  overflow: auto;
}
.outter::-webkit-scrollbar {
  width: 0 !important;
}
</style>

参考资料

Vue3使用h函数和render函数动态插入组件和元素