透传的基本概念
Vue 的透传 Attributes(也称为“属性穿透”或“v-bind:$attrs”)是一个有用的功能,它允许父组件通过属性(props)将数据或配置传递给子组件。子组件可以接收这些属性,并根据需要进行处理或渲染。在更复杂的组件树中,属性还可以从祖先组件透传到后代组件。
透传 Attributes 的工作原理
在 Vue 中,$attrs
是一个包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外) 的对象。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs"
传入内部组件——在创建更高层次的组件时非常有用。
例如:
<!-- 父组件 -->
<template>
<child-component :foo="foo" :bar="bar" baz="baz"></child-component>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
foo: 'fooValue',
bar: 'barValue'
};
}
};
</script>
<!-- ChildComponent.vue -->
<template>
<div v-bind="$attrs">
<!-- 这里可以渲染一些内容 -->
</div>
</template>
<script>
export default {
props: ['foo'], // 只声明了 foo 作为 prop
};
</script>
在这个例子中,bar
和 baz
属性没有被 ChildComponent
作为 prop 声明,所以它们会作为 $attrs
的一部分传递给 ChildComponent
的根元素。因此,baz
属性会被应用到 div
元素上。
透传 Attributes 的优点
-
灵活性:允许父组件向子组件传递任意数量的属性,而不需要子组件显式声明它们。
-
重用性:提高组件的重用性,因为你可以创建通用的组件,并允许用户通过属性来定制它们。
-
简洁性:减少了在子组件中手动声明和绑定属性的需要,使得代码更加简洁。
透传 Attributes 的缺点
-
不明确性:过度使用透传属性可能会导致属性来源不明确,增加了代码的阅读和维护难度。
-
潜在的命名冲突:如果多个层级的组件都使用相同的prop名,但没有明确地进行命名区分,可能会导致命名冲突和不可预期的行为
- 增加调试难度:当数据在多个组件之间传递时,如果出现问题,可能需要跟踪多个组件来确定问题的根源。
应用场景
-
创建基础组件:当你创建一些基础组件(如按钮、输入框等)时,你可以使用透传属性来允许用户自定义这些组件的样式和行为。
-
封装第三方组件:当你封装第三方组件时,可以使用透传属性来传递那些你不打算处理的属性,从而保持对原始组件的最大兼容性。
-
构建布局组件:在构建复杂的布局组件时,透传属性可以帮助你传递那些与布局无关的属性到内部的子元素上。
- 插槽内容:当使用插槽(slots)时,父组件可以通过透传数据来控制插槽内容的行为或样式。
注意事项
- 明确接口:定义组件时,应明确哪些属性是必需的、可选的以及它们的类型和作用。
- 避免过度透传:尽量只透传必要的属性,避免不必要的复杂性。
- 使用v-bind或缩写: 在模板中使用
v-bind
或缩写:来透传属性,以确保属性的正确绑定。
总结
Vue的透传是一种有用的模式,可以在特定场景下简化组件之间的通信。然而,它并不是所有情况下的最佳选择,需要根据具体的应用场景和需求来决定是否使用。
其他---Attributes 继承
在透传 Attributes 的工作原理中为什么说class 和 style 除外呢?这是因为class
、style有
Attributes 继承功能。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。举例来说,假如我们有一个 <MyButton>
组件,它的模板长这样:
<!-- <MyButton> 的模板 -->
<button>click me</button>
一个父组件使用了这个组件,并且传入了 class
:
<MyButton class="large" />
最后渲染出的 DOM 结果是:
<button class="large">click me</button>
这里,<MyButton>
并没有将 class
声明为一个它所接受的 prop,所以 class
被视作透传 attribute,自动透传到了 <MyButton>
的根元素上。
如果一个子组件的根元素已经有了 class
或 style
attribute,它会和从父组件上继承的值合并。如果我们将之前的 <MyButton>
组件的模板改成这样:
<!-- <MyButton> 的模板 -->
<button class="btn">click me</button>
则最后渲染出的 DOM 结果会变成:
<button class="btn large">click me</button>
其他---禁用 Attributes 继承
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
。
最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs
选项为 false
,你可以完全控制透传进来的 attribute 被如何使用。
这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs
访问到。
<span>Fallthrough attribute: {{ $attrs }}</span>
这个 $attrs
对象包含了除组件所声明的 props
和 emits
之外的所有其他 attribute,例如 class
,style
,v-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>
我们想要所有像 class
和 v-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>