一、前言
透传attribute指的是传递给一个组件,却没有被该组件声明为props或emits的attribute或者v-on事件监听器。最常见的例子就是 class、style 和 id。
二、代码解析
1、常见透传(class、style、id)
父组件
<template>
<div class="box">
<h1>父组件</h1>
<Son></Son>
</div>
</template>
<script setup>
import Son from "@/components/son.vue";
</script>
子组件
<template>
<div class="son-box">
<h2>这是子组件</h2>
<Grandson></Grandson>
</div>
</template>
<script setup>
import Grandson from "@/components/grandson.vue";
</script>
<style>
.son-box {color: blue;display: flex;border: 1px solid #cccccc;width: 600px;margin: 0 auto;}
</style>
后代组件
<template>
<h3 class="grandson-box">后代组件</h3>
</template>
<script setup>
</script>
<style>
.grandson-box {
border: 1px solid #cccccc;
height: 50px;
line-height: 50px;
width: 100px;
margin: auto;
}
</style>
父组件进行透传前效果
父组件进行透传代码及效果
代码
<template>
<div class="box">
<h1>父组件</h1>
<Son class="text-color"></Son>
</div>
</template>
<script setup>
import Son from "@/components/son.vue";
</script>
<style scoped lang="scss">
.text-color{color: red;}
</style>
效果
总结
前提条件:子组件并没有将声明为class一个它所接受的prop,所以class被视作透传attribute,且自动透传到了根元素上- 1.如果一个子或后代组件的根元素已经有了
class或styleattribute,它会和从父组件上继承的值合并; - 2.如果某个属性父与子与后代同时存在且属性权重相同,则父级属性覆盖后代级属性;
- 3.
id、style同理,不再进行演示。
2、事件透传(v-on || @)
- 在
父组件上定义事件,子或后代组件若为单一根元素则透传至组件内,组件内也会触发该事件; - 若
子或后代组件内已经存在该事件,则合并执行; - 示例在
子与后代组件上直接定义点击事件,透传父的事件逻辑,代码如下:
父组件
<template>
<div class="box">
<h1>父组件</h1>
<Son class="text-color" @click="grandsonBoxClick"></Son>
</div>
</template>
<script setup>
import Son from "@/components/son.vue";
function grandsonBoxClick() {
console.log('点击了后代组件');
}
</script>
<style scoped lang="scss">
.text-color{color: red;}
</style>
子组件
<template>
<div class="son-box" >
<h2>这是子组件</h2>
<Grandson @click="grandsonBoxClick"></Grandson>
</div>
</template>
<script setup>
import Grandson from "@/components/grandson.vue";
function grandsonBoxClick() {
console.log('点击了son-box');
}
</script>
<style>
.son-box {color: blue;display: flex;border: 1px solid #cccccc;width: 600px;margin: 0 auto;}
</style>
后代组件
<template>
<h3 class="grandson-box" @click="grandsonBoxClick">后代组件</h3>
</template>
<script setup>
function grandsonBoxClick() {
console.log('点击了grandson-box');
}
</script>
<style>
.grandson-box {
border: 1px solid #cccccc;
height: 50px;
line-height: 50px;
width: 100px;
margin: auto;
}
</style>
点击后代组件的控制台效果
- 分别触发父、子、后代事件内的逻辑
- 执行顺序为
后代组件内部逻辑——子组件事件逻辑——父组件事件逻辑
3、禁止透传
- 在选项式 API 中,你可以在组件选项中设置inheritAttrs: false来阻止;
- 在组合式 API 的
<script setup>中,你需要一个额外的<script>块来书写inheritAttrs: false选项声明来禁止
子组件代码示例
<template>
<div class="son-box" >
<h2>这是子组件</h2>
<Grandson @click="grandsonBoxClick"></Grandson>
</div>
</template>
<script setup>
import Grandson from "@/components/grandson.vue";
// 禁止透传
defineOptions({
inheritAttrs: false
});
function grandsonBoxClick() {
console.log('点击了son-box');
}
</script>
<style>
.son-box {color: blue;display: flex;border: 1px solid #cccccc;width: 600px;margin: 0 auto;}
</style>
子组件禁止透传后效果
子及后代class不再透传生效点击后代组件时,父组件事件内逻辑不再被触发
4、禁用透传的进阶用法
- 在子组件禁用透传,同时在后代组件设置
v-bind="$attrs",class重新定义,透传属性方法将继续在当前开启,继续向Grandson透传; 透传进来的attribute可以在模板的表达式中直接用$attrs访问。
代码
<template>
<div class="son-box">
<h2>这是子组件</h2>
<Grandson v-bind="$attrs" @click="grandsonBoxClick"></Grandson>
</div>
</template>
<script setup>
import Grandson from "@/components/grandson.vue";
// 禁止透传
defineOptions({
inheritAttrs: false
});
function grandsonBoxClick() {
console.log('点击了son-box');
}
</script>
<style>
.son-box {color: blue;display: flex;border: 1px solid #cccccc;width: 600px;margin: 0 auto;}
.text-color{color: orange;}
</style>
效果
后代class效果及点击后代组件控制台效果如下:
5、多根节点透传
后代组件示例代码
<template>
<h3 class="grandson-box" @click="clickLabel('h3')">后代组件</h3>
<button @click="clickLabel('button')">11232</button>
<s @click="clickLabel('s')">!!!删除线???</s>
</template>
<script setup>
function clickLabel(str) {
console.log('点击了' + str);
}
</script>
<style>
.grandson-box {
border: 1px solid #cccccc;
height: 50px;
line-height: 50px;
width: 100px;
margin: auto;
}
</style>
- 此时点击后代组件内事件,父组件透传至子组件因为只有一个根元素则不受影响,但因为后代组件多根节点,此时子组件无法透传至后代组件,所以子组件透传事件逻辑此时不会触发。
后代组件示例代码效果
- 官方文档解释:多根节点模板,由于
Vue不知道要将attribute透传到哪里,所以会抛出一个警告。
透传警告
- 上述代码为多个根节点,和单根节点组件有所不同,多个根节点的组件没有自动 attribute 透传行为。如果
$attrs没有被显式绑定,将会抛出一个运行时警告。
指定节点透传
<template>
<h3 class="grandson-box" @click="clickLabel('h3')">后代组件</h3>
<button @click="clickLabel('button')" v-bind="$attrs" >anniu</button>
<s @click="clickLabel('s')">!!!删除线???</s>
</template>
<script setup>
// 禁止透传
defineOptions({
inheritAttrs: false
});
function clickLabel(str) {
console.log('点击了' + str);
}
</script>
<style>
.grandson-box {
border: 1px solid #cccccc;
height: 50px;
line-height: 50px;
width: 100px;
margin: auto;
}
</style>
指定节点透传效果
- 只有被指定的按钮会触发子组件透传事件逻辑,其余节点不会触发,且指定节点后vue不会抛出警告。
JS访问中访问透传Attributes
setup组合式
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
setup选项式
export default {
setup(props, ctx) {
// 透传 attribute 被暴露为 ctx.attrs
console.log(ctx.attrs)
}
}
</script>
三、透传应用示例
通过父组件绑定v-model,透传共用属性,切换不同输入框类型,达到不同需求
父组件
<template>
<div class="box">
<h1>父组件</h1>
<Son v-model="inpValue" :maxlength="10" placeholder="请输入" :disabled="false" ></Son>
</div>
</template>
<script setup>
import { ref } from "vue";
import Son from "@/components/son.vue";
const inpValue = ref();
</script>
子组件
<template>
<div class="son-box">
<h2>这是子组件</h2>
<Grandson v-bind="$attrs"></Grandson>
</div>
</template>
<script setup>
import Grandson from "@/components/grandson.vue";
</script>
<style>
.son-box {color: blue;display: flex;border: 1px solid #cccccc;width: 600px;margin: 0 auto;}
</style>
后代组件
<template>
<el-input v-if="flag===1" v-bind="$attrs" type="date" @input="inpchange" />
<el-input v-if="flag===2" v-bind="$attrs" type="number" @input="inpchange" />
<el-input v-if="flag===3" v-bind="$attrs" type="color" @input="inpchange" />
<el-button @click="flag=1">11111</el-button>
<el-button @click="flag=2">22222</el-button>
<el-button @click="flag=3">33333</el-button>
{{ attrs.maxlength }}
{{ attrs.placeholder }}
</template>
<script setup>
import { ref,useAttrs } from 'vue';
const attrs = useAttrs();
console.log(attrs, 'attrs');
console.log(attrs.placeholder, 'attrs.placeholder');
console.log(attrs.maxlength, 'attrs.maxlength');
const flag = ref(1);
function inpchange(e) {
console.log(e, '输入变动');
}
</script>