这篇文章上次修改于 428 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
在 Vue3 中,defineSlots、useSlots 和 useAttrs 是与插槽和属性处理相关的重要 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声明的属性(如class、style、自定义属性等)。 - 属性继承:将属性传递给子元素或第三方组件,避免手动逐一声明
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 插槽类型声明 | 仅类型提示,不影响运行时逻辑。 |
useSlots | setup 函数 | 动态检查/操作插槽 | 避免直接修改插槽内容。 |
useAttrs | setup 函数 | 透传未声明属性 | 不包含已声明的 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,可以提升组件的灵活性和可维护性,特别是在处理动态内容、类型安全和属性透传时。
没有评论