目录
透传 Attributes
Attributes 继承
对 class 和 style 的合并
v-on 监听器继承
深层组件继承
禁用 Attributes 继承
多根节点的 Attributes 继承
vue2 $attrs 和 $listeners
$attrs 概念说明
$attrs 案例
$listeners 概念说明
$listeners案例
vue3 $attrs 和 $listeners
attrs在 template 中的用法
只有1个根元素的情况下
有2个根元素的情况下
$listeners 弃用
attrs在 js 中的用法
Options API
Composition API 3.0的语法
Composition API 3.2的语法
总结
移除 $listeners
概览
2.x 语法
3.x 语法
$attrs 包含 class & style
概览
2.x 行为
3.x 行为
(1)$props:当前组件接收到的 props 对象。Vue 实例代理了对其 props 对象属性的访问。
(2)$attrs:包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。
(3)$listeners:包含了父作用域中(不含 .native 修饰器的)v-on事件监听器。他可以通过 v-on="listeners"传入内部组件
透传 Attributes
透传 Attributes 是指由父组件传入,且没有被子组件声明为 props 或是组件自定义事件的 attributes 和事件处理函数。
Attributes 继承
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on
事件监听器。最常见的例子就是 class
、style
和 id
。
当一个组件以单个元素为根作渲染时,透传的 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
的合并
如果一个子组件的根元素已经有了 class
或 style
attribute,它会和从父组件上继承的值合并。如果我们将之前的 <MyButton>
组件的模板改成这样:
<!-- <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>
。
请注意:
透传的 attribute 不会包含
<MyButton>
上声明过的 props 或是针对emits
声明事件的v-on
侦听函数,换句话说,声明过的 props 和侦听函数被<MyButton>
“消费”了。透传的 attribute 若符合声明,也可以作为 props 传入
<BaseButton>
。
禁用 Attributes 继承
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
。
最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs
选项为 false
,你可以完全控制透传进来的 attribute 被如何使用。
vue2
Vue 2 的虚拟 DOM 实现对
class
和style
attribute 有一些特殊处理。因此,与其它所有 attribute 不一样,它们没有被包含在$attrs
中。上述行为在使用
inheritAttrs: false
时会产生副作用:
$attrs
中的 attribute 将不再被自动添加到根元素中,而是由开发者决定在哪添加。- 但是
class
和style
不属于$attrs
,它们仍然会被应用到组件的根元素中:
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
vue3
从 3.3 开始你也可以直接在 <script setup>
中使用 defineOptions:
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
这些透传进来的 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
。
多根节点的 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>
vue2 $attrs 和 $listeners
$attrs 概念说明
- 包含了父作用域中
不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)
- $attrs 包含了传递给一个组件的 attribute 名和 attribute 值;即一个JSON对象
- 可以通过
v-bind="$attrs"
传入内部组件
$attrs 案例
父组件
<template>
<SlotContainer
ref="slotContainer"
name="huangbiao"
:isOk="false"
:option="{ a: 1, b: true, c: 'ddd' }"
>
</SlotContainer>
</template>
<script>
import SlotContainer from "./SlotContainer"
export default {
data() {
return {};
},
components: {
SlotContainer,
}
};
</script>
<style lang="scss" scoped></style>
子组件
<script>
export default {
data() {
return {};
},
props: {
option: {
type: Object,
default: function() {
return {};
}
}
},
mounted() {
console.log(this.$attrs);
},
methods: {}
};
</script>
结果
不注释掉子组件的props, $attrs的值
注释掉子组件的props, $attrs的值
inheritAttrs: false 和 $attrs ;配合使用解决的问题?
- 可以手动决定这些 attribute 会被赋予哪个元素
- inheritAttrs: false 选项不会影响 style 和 class 的绑定
- 这个模式允许你在使用基础组件的时候更像是使用原始的 HTML 元素,而不会担心哪个元素是真正的根元素
$listeners 概念说明
- 包含了
父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
。 - 可以配合
v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素
- 它是一个对象,里面包含了作用在这个组件上的所有监听器。
$listeners案例
<template>
<div class>
<SlotContainer
ref="slotContainer"
v-on:m1="m1"
v-on:m2="m2"
@m3="m3"
@m4="m4"
@click.native="testJiami"
>
</SlotContainer>
</div>
</template>
<script>
import SlotContainer from "./SlotContainer";
import CryptoJS from "crypto-js";
export default {
data() {
return {};
},
components: {
SlotContainer,
},
methods: {
testJiami() {
this.m1();
this.m2();
this.m3();
this.m4();
},
m1() {
console.log("加密结果一 MD5:" + CryptoJS.MD5("你好"));
// 加盐 对应的API
console.log("加密结果一 MD5:" + CryptoJS.HmacMD5("你好", "salt"));
console.log(CryptoJS.SHA256("123456").toString());
// 加盐 对应的API
console.log(CryptoJS.HmacSHA256("123456", "salt").toString());
},
m2() {
var pwd = "passwor";
console.log("加密结果二 Hmac-MD5: " + CryptoJS.HmacMD5("你好", pwd));
},
m3() {
var salt = CryptoJS.enc.Utf8.parse("salt"); //盐
var iter = 1000; //迭代次数
var mi = CryptoJS.PBKDF2("你好", salt, {
keySize: parseInt(4),
iterations: parseInt(iter),
});
console.log("加密结果三:" + mi);
},
m4() {
var pswd = "我的密码";
var mi = CryptoJS.AES.encrypt("你好", pswd);
console.log("加密结果四" + mi);
//解密
var result = CryptoJS.AES.decrypt(mi, pswd).toString(CryptoJS.enc.Utf8);
console.log("解密结果:" + result);
},
},
};
</script>
<style lang="scss" scoped></style>
子组件(SlotContainer.vue)
<script>
export default {
data() {
return {};
},
mounted() {
console.log(this.$listeners);
},
methods: {}
};
</script>
<style lang="scss" scoped></style>
结果
@click.native="testJiami"的方法没有在 $listeners中
vue3 $attrs 和 $listeners
简单来说, attrs 主要接收没在 props 里定义,但父组件又传过来的属性。
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue -->
<template>
<div>
{{ msg }} - {{ $attrs }}
</div>
</template>
<script setup>
defineProps({
msg: {
type: String
}
})
</script>
可以看到,在子组件中,msg 使用了 props 接收,所以 {{ msg }} 可以直接输出 props 里接收的内容。
而没在 props 里接收的内容,全部都放到了 $attrs 里,并且存在一个对象里面。
接下来将展开讲解不同情况下 attrs 的使用方法。
attrs在 template 中的用法
在前面简单的例子里其实已经大概知道 attrs 在 template 的用法。但 Vue3 中 template 不再要求只有一个根元素了。所以 attrs 在 template 中分2种情况使用。
只有1个根元素的情况下
只有1个根元素的情况下,子组件中,没被 props 接收的属性,都会绑定在根元素上。
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
name="鲨鱼辣椒"
style="color: red;"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue -->
<template>
<div>
{{ msg }}
</div>
</template>
<script setup>
defineProps({
msg: {
type: String
}
})
</script>
可以看到,没被 props 接收的属性都被绑定到根元素上了。
连 style 里传入的样式也被执行,文字变成红色了。
有2个根元素的情况下
当子组件有2个根元素时,没被 props 接收的属性不会绑定到子组件的元素上。
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
name="鲨鱼辣椒"
style="color: red;"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue -->
<template>
<div>
{{ msg }}
</div>
<div>
{{ msg }}
</div>
</template>
<script setup>
defineProps({
msg: {
type: String
}
})
</script>
此时连父组件传入是 style 样式都不生效了。
如果我们此时希望第二个元素绑定所有没被 props 接收的属性,可以使用 v-bind="$attrs" 的方法实现
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
name="鲨鱼辣椒"
style="color: red;"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue -->
<template>
<div>
{{ msg }}
</div>
<div v-bind="$attrs">
{{ msg }}
</div>
</template>
<script setup>
defineProps({
msg: {
type: String
}
})
</script>
$listeners 弃用
$listeners
对象在 Vue 3 中已被移除。事件监听器现在是 $attrs
的一部分:
{
text: '这是一个 attribute',
onClose: () => console.log('close 事件被触发')
}
attrs在 js 中的用法
除了在 template 中可以访问到 $attrs ,在 JS 中也可以访问到。
vue 3 其实是兼容大部分 Vue 2 语法的,也就是 Options API 。而 attrs 在 Options APi 和 Composition Api 中的使用方法会稍微有一丢丢区别。而 Composition API 又分为 Vue 3.2 前的语法和 3.2 后的语法。
接下来将分开讨论这3种情况。
Options API
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
name="鲨鱼辣椒"
style="color: red;"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue 暂不关注 template 部分 -->
<script>
export default {
props: {
msg: {
type: String
}
},
mounted() {
console.log(this.$attrs)
}
}
</script>
此时控制台会输出没被 props 接收的属性。
Composition API 3.0的语法
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
name="鲨鱼辣椒"
style="color: red;"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue 暂不关注 template 部分 -->
<script>
export default {
props: {
msg: {
type: String
}
},
setup(props, context) {
console.log('props: ', props)
console.log('attrs: ', context.attrs)
}
}
</script>
Vue 3.2 前的写法,需要在 setup 方法里接收2个参数,而 attrs 就在 context 参数里。
Composition API 3.2的语法
Vue 3.2 后的语法,可以在 <script> 标签上添加 setup 属性。所以在代码里就不需要再调用 setup 方法了。
在这种情况下,props 成了默认的全局方法,而 attrs 则需要另外引入。
<!-- 父组件 ParentCom.vue -->
<template>
<ChildCom
msg="雷猴"
data="123"
name="鲨鱼辣椒"
style="color: red;"
/>
</template>
<script setup>
import ChildCom from './ChildCom.vue'
</script>
<!-- 子组件 ChildCom.vue 暂不关注 template 部分 -->
<script setup>
import { useAttrs } from 'vue'
const props = defineProps({
msg: {
type: String
}
})
const attrs = useAttrs()
console.log('props: ', props)
console.log('attrs: ', attrs)
</script>
需要引入 vue 中的 useAttrs ,在调用 useAttrs 后会返回当前未被 props 接收的属性。
重点是以下两句。
import { useAttrs } from 'vue'
const attrs = useAttrs()
总结
移除 $listeners
概览
$listeners
对象在 Vue 3 中已被移除。事件监听器现在是 $attrs
的一部分:
{
text: '这是一个 attribute',
onClose: () => console.log('close 事件被触发')
}
2.x 语法
在 Vue 2 中,你可以通过 this.$attrs
访问传递给组件的 attribute,以及通过 this.$listeners
访问传递给组件的事件监听器。结合 inheritAttrs: false
,开发者可以将这些 attribute 和监听器应用到根元素之外的其它元素:
<template>
<label>
<input type="text" v-bind="$attrs" v-on="$listeners" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
3.x 语法
在 Vue 3 的虚拟 DOM 中,事件监听器现在只是以 on
为前缀的 attribute,这样它就成为了 $attrs
对象的一部分,因此 $listeners
被移除了。
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
如果这个组件接收一个 id
attribute 和一个 v-on:close
监听器,那么 $attrs
对象现在将如下所示:
{
id: 'my-input',
onClose: () => console.log('close 事件被触发')
}
$attrs
包含 class
& style
概览
$attrs
现在包含了所有传递给组件的 attribute,包括 class
和 style
。
2.x 行为
Vue 2 的虚拟 DOM 实现对 class
和 style
attribute 有一些特殊处理。因此,与其它所有 attribute 不一样,它们没有被包含在 $attrs
中。
上述行为在使用 inheritAttrs: false
时会产生副作用:
$attrs
中的 attribute 将不再被自动添加到根元素中,而是由开发者决定在哪添加。- 但是
class
和style
不属于$attrs
,它们仍然会被应用到组件的根元素中:
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
像这样使用时:
<my-component id="my-id" class="my-class"></my-component>
……将生成以下 HTML:
<label class="my-class">
<input type="text" id="my-id" />
</label>
3.x 行为
$attrs
包含了所有的 attribute,这使得把它们全部应用到另一个元素上变得更加容易了。现在上面的示例将生成以下 HTML:
<label>
<input type="text" id="my-id" class="my-class" />
</label>
在使用了
inheritAttrs: false
的组件中,请确保样式仍然符合预期。如果你之前依赖了class
和style
的特殊行为,那么一些视觉效果可能会遭到破坏,因为这些 attribute 现在可能被应用到了另一个元素中。