# 非 Prop 的 Attribute
没有进行 prop 定义的 attrbuite 可以通过 $attr 来获取。 常见的如 class
、style
和 id
等 attribute。
# vue2
# Attribute 继承
当只有单节点时,非 prop 的 attribute 将自动添加到根节点的 attribute 中。
<!-- 具有非 prop 的 attribute 的 date-picker 组件-->
<date-picker data-status="activated"></date-picker>
<!-- 渲染后的 date-picker 组件 -->
<div class="date-picker" data-status="activated">
<input type="datetime-local" />
</div>
2
3
4
5
6
7
同样的规则也适用于事件监听器:
<date-picker @change="submitChange"></date-picker>
app.component('date-picker', {
created() {
console.log(this.$attrs) // { onChange: () => {} }
}
})
2
3
4
5
# 指定元素继承
设置
inheritAttrs: false
,可以阻止根元素继承 attribute。使用
$attrs
,将除props
和emits
外的所有 attribute 应用到其它元素上。
app.component('date-picker', {
inheritAttrs: false,
template: `
<div class="date-picker">
<input type="datetime-local" v-bind="$attrs" />
</div>
`
})
2
3
4
5
6
7
8
这样做的结果是,data-status
attribute 将应用于 input
元素!
<!-- date-picker 组件使用非 prop 的 attribute -->
<date-picker data-status="activated"></date-picker>
<!-- 渲染后的 date-picker 组件 -->
<div class="date-picker">
<input type="datetime-local" data-status="activated" />
</div>
2
3
4
5
6
7
# 多根节点的继承
多根节点时就必须显式绑定 $attrs
,否则就会报错。
<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
// 指定绑定 attrs 的根节点
app.component('custom-layout', {
template: `
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
`
})
2
3
4
5
6
7
8
# 实例:封装三方组件
透传属性
$attrs
:获取一个组件没有声明的包含所有父作用域的绑定。
透传事件
$listeners
:包含了父作用域中的(不含.native
修饰器的)v-on
事件监听器。
暴露插槽
$slots:
普通插槽,使用$slots这个变量拿到非作用域的插槽,然后循环渲染对应的普通具名插槽,这样就可以使用第三方组件提供的原插槽;
$scopedSlots:
作用域插槽则绕了一圈,使用了一个插槽的语法糖(具名插槽的缩写)并且结合着动态插槽名的用法;循环$scopedSlots作用插槽位置和传递对应的参数,,这样就可以使用第三方组件提供的作用域插槽。
暴露内部方法
透过遍历目标组件实例,将实例内部方法挂载到当前实例
<template>
<el-autocomplete ref="el-autocomplete" v-bind="$attrs" v-on="$listeners">
<!-- 遍历子组件非作用域插槽,并对父组件暴露 -->
<slot v-for="(_, slotName) in $slots" :name="slotName" :slot="slotName" />
<!-- 遍历子组件作用域插槽,并对父组件暴露 -->
<template v-for="(index, name) in $scopedSlots" #[name]="data">
<slot :name="name" v-bind="data" />
</template>
</el-autocomplete>
</template>
<script>
export default {
// inheritAttrs: false, // 是否禁用样式透传
data() {
return {};
},
methods: {
extendMethod() {
const refMethod = Object.entries(this.$refs["el-autocomplete"]);
for (const [key, value] of refMethod) {
if (!(key.includes("$") || key.includes("_"))) {
this[key] = value;
}
}
},
},
created() {},
mounted() {
this.extendMethod();
},
};
</script>
<style lang="css" scoped></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# vue3
# Attributes 继承
未被 props 或 emits 声明的属性默认会被透传并被根元素继承。
<!-- <MyButton> 的模板 -->
<button>Click Me</button>
2
一个父组件使用了这个组件,并且传入了 class
:
<MyButton class="large" @click="onClick" />
最后渲染出的 DOM 结果是:
<button class="large" @click="onClick" >Click Me</button>
这里,<MyButton>
并没有将 class
声明为一个它所接受的 prop,所以 class
被视作透传 attribute,自动透传到了 <MyButton>
的根元素上。
此外,如果透传进的还是一个组件,那么它会进行深度透传,即继续向下透传
# class
和 style
的合并
如果透传的属性是 class 或 style ,她会将透传的属性和原有的属性进行合并。
<!-- <MyButton> 的模板 -->
<button class="btn">Click Me</button>
2
则最后渲染出的 DOM 结果会变成:
<button class="btn large">Click Me</button>
# 禁用 Attributes 继承
如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false
。
你也可以直接在 <script setup>
中使用 defineOptions
(opens new window):vue 3.3版本支持
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
2
3
4
5
6
# 多根节点的 Attributes 继承
和单根节点组件有所不同,有着多个根节点的组件没有自动 attribute 透传行为。如果 $attrs
没有被显式绑定,将会抛出一个运行时警告。
父组件:
<CustomLayout id="custom-layout" @click="changeValue" />
对应组件:
<header>...</header>
<main>...</main>
<footer>...</footer>
2
3
如果 $attrs
被显式绑定,则不会有警告:
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
2
3
# 访问透传属性
如果需要,你可以在 <script setup>
中使用 useAttrs()
API 来访问一个组件的所有透传 attribute:
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
</script>
2
3
4
5
如果没有使用 <script setup>
,attrs
会作为 setup()
上下文对象的一个属性暴露:
export default {
setup(props, ctx) {
// 透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs)
}
}
2
3
4
5
6
需要注意的是,虽然这里的 attrs
对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。你不能通过侦听器去监听它的变化。如果你需要响应性,可以使用 prop。或者你也可以使用 onUpdated()
使得在每次更新时结合最新的 attrs
执行副作用。
# Vue3 组件封装的变化
# 1、$attrs 与 $listeners 合并
在 Vue 3.x 当中,取消了$listeners这个组件实例的属性,将其事件的监听都整合到了$attrs这个属性上了,因此直接通过v-bind=$attrs属性就可以进行 props 属性和 event 事件的透传。
# 2、$slot 与 $scopedSlots 合并
在Vue 3.x当中取消了作用域插槽$scopedSlots的属性,将所有插槽都统一在$slots当中,因此在 Vue 3.x 当中不需要分默认插槽、具名插槽和作用域插槽,可以进行统一的处理。
# 3、如何继承第三方组件的Methods
element-plus el-table组件没有使用defineExpose,无法用这种方式继承
<template>
<a-table v-bind="attrs" ref="componentRef">
<!-- 遍历子组件作用域插槽,并对父组件暴露 -->
<template v-for="k in Object.keys(slots)" #[k]="slotProps" :key="k">
<slot :name="k" v-bind="slotProps || {}"></slot>
</template>
</a-table>
</template>
<script setup>
import { useAttrs, useSlots, useTemplateRef } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
const componentRef = useTemplateRef('componentRef')
defineExpose({
componentRef
})
</script>
<style></style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
提示
此外,我们绑定的属性(包括 class
和 style
)同时会在根元素(上面的例子是class="my-input"
的Dom节点)上起作用。要阻止这个默认行为,我们需要设置inheritAttrs
为false