这篇文章上次修改于 428 天前,可能其部分内容已经发生变化,如有疑问可询问作者。

在 Vue3 中,defineSlotsuseSlotsuseAttrs 是与插槽和属性处理相关的重要 API。以下是它们的用法和典型场景:


1. defineSlots

用途

  • 类型声明插槽:在 <script setup> 中为组件的插槽提供 TypeScript 类型定义,增强代码提示和类型检查。
  • 明确作用域参数:声明插槽接收的作用域参数,确保父组件使用插槽时符合预期类型。

用法

<!-- 子组件 Child.vue -->
<script setup lang="ts">
// 声明插槽类型
defineSlots<{
  // default 插槽,无作用域参数
  default: () => any;
  // header 插槽,接收 title 参数
  header?: (props: { title: string }) => any;
}>();
</script>

<template>
  <div>
    <!-- 使用作用域插槽 -->
    <slot name="header" title="子组件标题" />
    <slot>默认内容</slot>
  </div>
</template>

使用场景

  • 需要为插槽提供明确的类型提示(TypeScript 项目)。
  • 需要规范插槽的作用域参数,避免父组件传递错误类型。

2. useSlots

用途

  • 动态访问插槽:在 setup 函数中获取插槽对象,判断插槽是否存在或操作插槽内容。
  • 条件渲染:根据插槽是否存在动态调整 UI 结构。

用法

<!-- 子组件 Child.vue -->
<script setup>
import { useSlots } from 'vue';

const slots = useSlots();

// 检查是否存在 header 插槽
const hasHeader = !!slots.header;
</script>

<template>
  <div>
    <header v-if="hasHeader">
      <slot name="header" />
    </header>
    <main>
      <slot />
    </main>
  </div>
</template>

使用场景

  • 需要根据插槽是否存在动态渲染内容(如可选头部、尾部)。
  • 在逻辑中处理插槽内容(如计算插槽数量)。

3. useAttrs

用途

  • 透传未声明的属性:获取父组件传递的、未被 props 声明的属性(如 classstyle、自定义属性等)。
  • 属性继承:将属性传递给子元素或第三方组件,避免手动逐一声明 props

用法

<!-- 子组件 CustomInput.vue -->
<script setup>
import { useAttrs } from 'vue';

const attrs = useAttrs();
</script>

<template>
  <!-- 将 attrs 透传给原生 input -->
  <input v-bind="attrs" />
</template>

使用场景

  • 封装高阶组件时透传属性(如封装第三方 UI 库组件)。
  • 处理未在 props 中声明的动态属性(如 aria-*data-*)。

关键区别与注意事项

API作用域典型场景注意事项
defineSlots<script setup>TypeScript 插槽类型声明仅类型提示,不影响运行时逻辑。
useSlotssetup 函数动态检查/操作插槽避免直接修改插槽内容。
useAttrssetup 函数透传未声明属性不包含已声明的 props

完整示例

子组件 (Child.vue)

<script setup lang="ts">
import { useSlots, useAttrs } from 'vue';

// 定义插槽类型
defineSlots<{
  default: (props: { msg: string }) => any;
  footer?: () => any;
}>();

const slots = useSlots();
const attrs = useAttrs();

const hasFooter = !!slots.footer;
</script>

<template>
  <div v-bind="attrs">
    <slot :msg="Hello from child" />
    <footer v-if="hasFooter">
      <slot name="footer" />
    </footer>
  </div>
</template>

父组件

<template>
  <Child class="custom-class" data-test="123">
    <!-- 默认插槽(接收作用域参数) -->
    <template #default="{ msg }">
      <p>{{ msg }}</p>
    </template>

    <!-- footer 插槽 -->
    <template #footer>
      <p>Footer Content</p>
    </template>
  </Child>
</template>

通过合理使用这些 API,可以提升组件的灵活性和可维护性,特别是在处理动态内容、类型安全和属性透传时。