Appearance
透传 Attributes
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器。最常见的例子就是 class
、style
和 id
。
基本用法
单根元素的透传
如果一个 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 的合并
如果子组件的根元素上已经有了 class
或 style
属性,那么它自有的属性值会和父组件透传进来的属性值合并。假设子组件 <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 执行副作用。