Skip to content

透传 Attributes

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 propsemits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 classstyleid

基本用法

单根元素的透传

如果一个 SFC 的模板结构中,仅有一个根元素,则父组件透传进来的 attribute 会被自动添加到根元素上。假设有一个名为 <MyButton> 的子组件,其模板结构如下:

html
<template>
  <button>按钮</button>
</template>

如果父组件使用了这个组件,并传入了 class

html
<template>
  <MyButton class="btn-large" />
</template>

最后,<MyButton> 组件渲染出的 DOM 结果是:

html
<button class="btn-large">按钮</button>

在这个例子中,子组件 <MyButton> 并没有把 class 声明为 prop,所处 class 被当做透传进来的 attribute自动透传到了 <MyButton> 组件的根元素上。

class 和 style 的合并

如果子组件的根元素上已经有了 classstyle 属性,那么它自有的属性值会和父组件透传进来的属性值合并。假设子组件 <MyButton> 的模板结构如下:

html
<template>
  <button class="btn-default">按钮</button>
</template>

父组件透传的 attribute 如下:

html
<template>
  <MyButton class="btn-large" />
</template>

最后,<MyButton> 组件渲染出的 DOM 结果是:

html
<button class="btn-default btn-large">按钮</button>

其中,透传进来的 attribute 会拼接到原有 attribute 值的后面(实现了样式的覆盖)。

注意

透传 attribute 仅能修改到子组件根元素的样式,无法改动根元素下嵌套的子元素的样式。因为组件开启了 <style scoped> 之后,会防止组件样式冲突的问题。如果在父组件中要修改子组件根元素下子元素的样式,可以使用 :deep() 伪类实现。

v-on 监听继承

在父组件中通过 v-on 绑定的事件监听,也会被透传到子组件的根元素上。如果父组件透传 v-on 的同时,子组件中的根元素也绑定了自己的 v-on 监听,则这两个 v-on 都会被触发(触发顺序为先子后父)。

例如,子组件中的根元素绑定了 @click 事件监听器:

vue
<template>
  <button class="btn-default" @click="console.log('触发了子组件中的事件监听器')">
    <span class="btn-text">按钮</span>
  </button>
</template>

<style scoped>
.btn-default {
  font-size: 12px;
}

.btn-text {
  color: red;
}
</style>

父组件透传了 @click 事件监听器:

vue
<script setup>
import MyButton from './components/MyButton.vue'
</script>

<template>
  <MyButton class="btn-large" @click="console.log('触发了父组件透传的事件监听器')" />
</template>

<style scoped>
.btn-large {
  font-size: 30px;
}

:deep(.btn-text) {
  color: #000 !important;
}
</style>

实际点击按钮时,这两个 @click 事件监听器都会被触发,且触发的顺序为先子后父

深层组件继承

如果 <MyButton> 组件在根节点上包装渲染了另外一个组件 <BaseButton>,则透传给 <MyButton> 的 attribute 会往下直接传给 <BaseButton> 组件。

vue
<script setup>
import BaseButton from './BaseButton.vue'
</script>

<template>
  <!-- 在 MyButton 的模板中,只渲染 BaseButton 组件 -->
  <BaseButton />
</template>

此时,<MyButton> 接收到的透传 attribute 会继续向下传入 <BaseButton> 组件。

其它用法

禁用 Attribute 继承

SFC 组件的透传 Attribute 特性是默认开启的。如果您不想让某个组件自动的继承 attribute,可以在组件的配置项中显示声明 inheritAttrs: false 选项。语法格式如下:

vue
<script setup>
// defineOptions 是 Vue3.3 新增的内置宏函数,
// 用来在 <script setup> 中配置组件的选项式 API
defineOptions({
  inheritAttrs: false
})

// ...setup 逻辑
</script>

注意

当禁用了 Attribute 继承之后,只是不会自动把透传进来的 attribute 添加到根元素。但是组件依然可以接收到透传进来的 attribute。

手动绑定透传的 Attribute

当禁用了 Attribute 继承之后,我们可以手动把透传进来的 attribute 绑定到非根元素上。在 SFC 的 template 模板中,可以使用内置提供的 $attrs 对象,获取到透传进来的所有的 attribute。

vue
<script setup>
<!-- 禁用 Attribute 继承 -->
defineOptions({ inheritAttrs: false })
</script>

<template>
  <div>
    <!-- 利用不带参数的 v-bind 指令,手动把 $attrs 对象,绑定到非根元素的 button 按钮上 -->
    <!-- 请注意:如果透传进来的 $attrs 中包含 class 类样式,则父组件中需要使用 :deep() 伪类 -->
    <button class="btn-default" @click="console.log('触发了子组件中的事件监听器')" v-bind="$attrs">
      <span class="btn-text">按钮</span>
    </button>
  </div>
</template>

父组件中的代码如下:

vue
<script setup>
import MyButton from './components2/MyButton.vue'
</script>

<template>
  <!-- 这里的 class 和 @click 都是透传给子组件的 attribute -->
  <MyButton class="btn-large" @click="console.log('触发了父组件透传的事件监听器')" />
</template>

<style scoped>
/* 父组件中透传进去的 class,需要使用 :deep() 包裹,
  否则无法影响到开启了 <style scoped> 的子组件非根元素的样式 */
:deep(.btn-large) {
  font-size: 30px;
}

:deep(.btn-text) {
  color: #000 !important;
}
</style>

TIP

没有参数的 v-bind 会将一个对象的所有属性都作为 attribute 应用到目标元素上。

多根元素的 Attribute 透传

Vue3 的 SFC 既支持单个根元素,又支持多个根元素。在单根元素的 SFC 中,如果没有禁用 Attribute 继承,则透传进来的 attribute 会自动添加到根元素上。

在多根元素的 SFC 中,透传进来的 attribute 不会被自动应用到根元素,因为 Vue 不知道要把透传进来的 attribute 应用到哪个根元素上!因此需要程序员手动使用 v-bind="$attrs" 进行绑定。否则会抛出警告。

例如,父组件向下透传了 id@click 两个 attribute:

html
<CustomLayout id="custom-layout-main" @click="console.log('ok')" />

在拥有多个根元素的子组件 <CustomLayout> 中,需要手动把透传进来的 $attrs 绑定到指定的根元素上:

html
<template>
  <header>头部布局区域</header>
  <main v-bind="$attrs">内容主体布局区域</main>
  <footer>页脚布局区域</footer>
</template>

注意,可以同时把 $attrs 绑定到多个根元素上:

html
<template>
  <header>头部布局区域</header>
  <!-- 同时把透传进来的 attribute 绑定到了 main 和 footer 上 -->
  <main v-bind="$attrs">内容主体布局区域</main>
  <footer v-bind="$attrs">页脚布局区域</footer>
</template>

在 JS 中访问透传的 Attribute

在 SFC 的 <template> 模板中,可以使用内置的 $attrs 变量访问透传进来的 attribute。如果您想在 <script setup> 中访问透传进来的 attribute,需要使用 Vue 提供的 useAttrs API:

vue
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

注意

调用 useAttrs() 得到的 attrs 并不是响应式的数据(出于性能考虑),所以不能通过侦听器去监听它的变化。如果需要响应性,可以使用 Props。或者在 onUpdated() 生命周期函数中结合最新的 attrs 执行副作用。

天不生夫子,万古长如夜