Skip to content

监听插槽存在 useSlotsExist

赞助

监听给定名称或名称数组的插槽是否存在,支持监听单个插槽或一组插槽的存在

Show Source Code
ts
/**
 * 组合式函数
 * 监听给定名称或名称数组的插槽是否存在,支持监听单个插槽或一组插槽的存在
 *
 * 判定以「实际调用插槽并检查返回的 vnode」为准,可识别「已提供但渲染为空」的插槽;
 * 探测时以空对象作为作用域参数,插槽内抛错按「已提供」处理
 *
 * @param slotsName - 插槽的名称或名称数组,默认为 'default'
 * @returns 如果是单个插槽名称,则返回一个计算属性,表示该插槽是否存在
 *          如果是插槽名称数组,则返回一个 reactive 对象,其中的每个属性对应该插槽是否存在
 */
import { useSlots, reactive, computed, Comment, Text } from 'vue'
import type { ComputedRef, Reactive, VNode } from 'vue'
type SlotsExistResult<T extends string | string[]> = T extends string
  ? ComputedRef<boolean>
  : Reactive<Record<string, ComputedRef<boolean>>>
export function useSlotsExist<T extends string | string[] = 'default'>(slotsName: T): SlotsExistResult<T> {
  const slots = useSlots() // 获取当前组件的所有插槽
  // 检查特定名称的插槽是否存在且不为空
  const checkSlotsExist = (slotName: string): boolean => {
    const slot = slots[slotName]
    if (slot === undefined) {
      return false
    }
    // 必须实际调用一次插槽才能拿到 vnode,进而判断「是否真的渲染了内容」
    let slotsContent: VNode[] | undefined
    try {
      // 作用域参数在模板插槽的形参位置解构,空对象探测也可能因访问嵌套属性而抛错;
      // 能抛错即说明插槽已被提供,按「存在」处理(探测失败不等同于未提供)
      slotsContent = slot({})
    } catch {
      return true
    }
    const checkExist = (slotContent: VNode) => {
      if (slotContent.type === Comment) {
        return false
      }
      if (Array.isArray(slotContent.children) && !slotContent.children.length) {
        return false
      }
      if (slotContent.type !== Text) {
        return true
      }
      if (typeof slotContent.children === 'string') {
        return slotContent.children.trim() !== ''
      }
    }
    if (slotsContent && slotsContent?.length) {
      const result = slotsContent.some((slotContent: VNode) => {
        return checkExist(slotContent)
      })
      return result
    }
    return false
  }
  if (Array.isArray(slotsName)) {
    const slotsExist = reactive<Record<string, ComputedRef<boolean>>>({})
    slotsName.forEach((slotName: string) => {
      const exist = computed(() => checkSlotsExist(slotName))
      slotsExist[slotName] = exist // 将一个 ref 赋值给一个 reactive 属性时,该 ref 会自动解包
    })
    return slotsExist as SlotsExistResult<T>
  } else {
    return computed(() => checkSlotsExist(slotsName)) as SlotsExistResult<T>
  }
}

基本使用

通过开关控制 header 插槽是否渲染内容,实时查看检测结果


header 插槽内容

  • default 插槽:存在
  • header 插槽:存在
vue
<script setup lang="ts">
import { h, ref } from 'vue'
import { useSlotsExist } from 'vue-amazing-ui'
const showHeader = ref(true)
// 用一个内联组件模拟「外部提供插槽」的真实场景
const SlotChecker = {
  setup() {
    const slotsExist = useSlotsExist(['default', 'header'])
    return () =>
      h('ul', [
        h('li', `default 插槽:${slotsExist.default ? '存在' : '不存在'}`),
        h('li', `header 插槽:${slotsExist.header ? '存在' : '不存在'}`)
      ])
  }
}
</script>
<template>
  <Space :gap="8">
    <span>header 插槽内容</span>
    <Switch v-model="showHeader" />
  </Space>
  <SlotChecker>
    <template #header>
      <span v-if="showHeader">header 内容</span>
    </template>
    默认内容
  </SlotChecker>
</template>

Params

参数说明类型默认值
slotsName插槽的名称或名称数组string | string[]'default'

Return

类型说明
ComputedRef<boolean>传入单个插槽名称时,返回该插槽是否存在的计算属性
Reactive<Record<string, ComputedRef<boolean>>>传入插槽名称数组时,返回各插槽是否存在状态的响应式对象

Released under the MIT License.