Vue3 透传
什么是透传
透传(Attribute Inheritance)指未被子组件声明为 props 或 emits 的 attribute 或 v-on 事件监听器,会默认继承并应用到子组件的根元素上。
常见例子:class、style、id,以及 @click 等原生事件。
提示
这里说的"组件"特指单文件组件(.vue)中定义的非根组件。根组件(App.vue)不会继承父级透传,因为它的父级是应用容器。
一个简单的例子
父组件 Parent.vue:
<template>
<MyButton class="large" @click="onClick" />
</template>子组件 MyButton.vue:
<!-- 只有一个根元素 <button> -->
<template>
<button>点击</button>
</template>渲染结果——class 和 @click 都会被透传到 <button> 上:
<button class="large">点击</button>class 和 style 的合并
透传的 class / style 会与根元素已有的值自动合并,而不是覆盖。
<!-- 子组件 -->
<template>
<button class="btn">点击</button>
</template><!-- 父组件 -->
<MyButton class="large" />渲染结果:
<button class="btn large">点击</button>而普通 attribute(如 id、data-*)如果根元素上也有同名属性,则父级的透传值会覆盖子组件内部的值。
v-on 监听器的透传
事件监听器同样会被透传:
<!-- 父组件 -->
<MyButton @click="onClick" @mouseenter="onEnter" /><!-- 子组件 -->
<template>
<button>点击</button>
</template>点击按钮时,onClick 会被调用;根元素 <button> 上也会绑定 @mouseenter。
提示
如果子组件根元素上自己绑定了同名监听器,父级的监听器和子组件的监听器会一起触发,而不会互相覆盖。
多根节点的透传
当组件有多个根节点时,Vue 无法自动决定把透传属性放到哪个根元素上,因此会给出警告,且不会自动透传,必须显式绑定 $attrs。
<!-- 子组件:两个根元素 -->
<template>
<header>标题</header>
<main :class="$attrs.class">内容</main>
</template><!-- 父组件 -->
<MyPanel class="panel" />此时 class="panel" 只会透传到显式绑定了 $attrs 的 <main> 上。
警告
在没有显式绑定 $attrs 的多根组件上,透传的 attribute 不会生效,并会在控制台输出警告。
访问透传属性
模板中:$attrs
在模板中通过 $attrs 访问所有透传属性(不含已声明的 props / emits):
<template>
<div>
<span>透传属性:{{ $attrs }}</span>
</div>
</template>组合式 API 中:useAttrs()
<script setup lang="ts">
import { useAttrs } from 'vue'
const attrs = useAttrs()
console.log(attrs) // { class: 'large', onClick: fn }
</script>警告
$attrs / useAttrs() 返回的对象是响应式的,但不建议对它的属性做解构,会丢失响应性。需要响应式使用时直接访问 attrs.xxx 即可。
禁用透传:inheritAttrs: false
如果不想让透传属性自动应用到根元素,可以设置 inheritAttrs: false,然后手动决定如何使用 $attrs。
<script setup lang="ts">
defineOptions({
inheritAttrs: false,
})
</script>
<template>
<div class="wrapper">
<button v-bind="$attrs">点击</button>
</div>
</template>这样透传的 attribute 会绑定到 <button> 上,而不会出现在外层 <div class="wrapper"> 上。
提示
defineOptions 是 Vue 3.3+ 的编译宏,用于在 <script setup> 中定义组件选项。旧版本需要额外使用 <script> 块导出 inheritAttrs: false。
深层组件继承
透传属性默认会沿着组件链继续向下传递(除非中间的组件使用了 props / emits 显式声明,或者设置了 inheritAttrs: false 且未绑定 $attrs)。
Parent.vue ──► Mid.vue ──► Leaf.vue
class="a" (透传) <div class="a">只要中间的 Mid.vue 没有消费这些属性,class 就会一路透传到 Leaf.vue 的根元素。
常见应用场景
场景一:封装原生元素组件
封装一个带透传能力的按钮,保留原生 <button> 的全部能力:
<!-- MyButton.vue -->
<script setup lang="ts">
// 声明 props,剩下的(class、@click 等)自动透传到根元素
defineProps<{
variant?: 'primary' | 'default'
}>()
</script>
<template>
<button class="btn" :class="`btn--${variant}`">
<slot />
</button>
</template>场景二:属性透传到非根元素
根元素是容器,但属性要落到内部元素上,需要 inheritAttrs: false + v-bind="$attrs":
<!-- MyInput.vue -->
<script setup lang="ts">
defineOptions({ inheritAttrs: false })
</script>
<template>
<label class="field">
<span>输入:</span>
<input v-bind="$attrs" />
</label>
</template>总结
- 透传 = 未声明为 props / emits 的 attribute 和事件,自动应用到根元素
class/style会自动合并,普通 attribute 会覆盖子组件同名的值- 事件监听器透传后,父子监听器会同时触发
- 多根节点组件不会自动透传,必须显式绑定
$attrs inheritAttrs: false可禁用自动透传,配合v-bind="$attrs"手动控制落点- 透传会沿组件链层层向下,直到被消费
