Attributes继承

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

当一个组件以单个元素为根作渲染时,透传的attribute会自动被添加到根元素上。举例:假如有一个<MyButton>组件,它的模板为:

<!-- <MyButton>的模板 -->
<button>Click Me</button>

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

<MyButton class="large" />

最后渲染出来的DOM结果是:

<button class="large">Click Me</button>

这里,<MyButton>并没有将class声明为一个它所接受的prop,所以class被视作透传attribute,自动透传到了<MyButton>的根元素上。

classstyle的合并

如果一个子组件的根元素已经有了class或者style attribute,它会和从父组件上继承的值合并。如果将之前的模板改成这样:

<!-- <MyButton> 的模板 -->
<button class="btn">Click Me</button>

则最后渲染出的DOM结果会变为:

<button class="btn large">Click Me</button>

v-on监听器继承

同样的规则也适用于v-on事件监听器:

<MyButton @click="onClick" />

click监听器会被添加到<MyButton>的根元素,即那个原生的<button>元素之上。当原生的<button>被点击,会触发父组件的onClick方法。同样的,如果原生button元素自身也通过v-on绑定了一个事件监听器,则这个监听器和父组件继承的监听器都会被触发。

深层组件继承

有些情况下一个组件会在根节点上渲染另一个组件。比如重构一下<MyButton>,让它在根节点上渲染<BaseButton>

<!-- <MyButton/> 的模板,只是渲染另一个组件 -->
<BaseButton />

此时<MyButton>接收的透传attribute会直接继续传给<BaseButton>

注意:

  1. 透传的attribute不会包含<MyButton>上声明的props或是针对emits声明事件的v-on监听函数。
  2. 透传的attribute若符合声明,也可以作为props传入<BaseButton>

禁用 Attributes 继承

如果不想要一个组件自动地继承attribute,可以在组件选项中设置inheritAttrs: false

从3.3版本开始可以直接在<script setup>中使用defineOptions

<script setup>
defineOptions({
inheritAttrs: false
})
</script>

最常见的需要禁用attribute继承的场景就是attribute需要应用在根节点以外的其他元素上。通过设置inheritAttrs选项为false,可以完全控制透传进来的attribute被如何使用。

这些透传进来的attribute可以在模板的表达式中直接用$attrs访问到。

<span>Fallthrough attribute: {{ $attrs }}</span>

这个$attrs对象包含了除组件所声明的propsemits之外的所有其他attribute,如classstylev-on监听器等等。

注意点⚠️:

  • 和props有所不同,透传attributes在Javascript中保留了它们原始的大小写,所以像foo-bar这样的一个attribute需要通过$attrs['foo-bar']来访问。
  • @click这样的一个v-on事件监听器将在此对象下被暴露为一个函数$attrs.onClick

现在使用之前的<MyButton>组件,有时候可能为了样式,需要在<button>元素外包装一层<div>

<div class="btn-wrapper">
<button class="btn">Click Me</button>
</div>

想要所有像classv-on监听器这样的透传attribute都应用在内部的<button>上而不是外层的<div>上。可以通过设定inheritAttrs: false和使用v-bind="$attrs"来实现:

<div class="btn-wrapper">
<button class="btn" v-bind="$attrs">Click Me</button>
</div>

多根节点的Attributes继承

和单根节点组件有所不同,有着多个根节点的组件没有自动attribute透传行为。如果$attrs没有被显式绑定,将会抛出一个运行时警告。

<CustomLayout id="custom-layout" @click="changeValue" />

如果<CustomLayout>有下面这样的多根节点模板,由于Vue不知道要将attribute透传到哪里,所以会抛出一个警告。

<header>...</header>
<main>...</main>
<footer>...</footer>

如果$attrs被显式绑定,则不会有警告:

<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>

在Javascript中访问透传Attributes

如果需要,可以在<script setup>中使用useAttrs()API来访问一个组件的所有透传attribute:

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

const attrs = useAttrs()
</script>

如果没有使用<script setup>attrs会作为setup()上下文对象的一个属性暴露:

export default {
setup(props, ctx) {
//透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs)
}
}

需要注意的是,虽然这里的attrs对象总是反映为最新的透传attribute,但它并不是响应式的。不能通过监听器去监听它的变化。如果需要响应性,可以使用prop。或者也可以使用onUpdated()使得在每次更新时结合最新的attrs执行副作用。