vue3透传Attributes

1,010 阅读3分钟

一、前言

透传attribute指的是传递给一个组件,却没有被该组件声明为props或emitsattribute或者v-on事件监听器。最常见的例子就是 classstyle 和 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>

父组件进行透传前效果

image.png

父组件进行透传代码及效果

代码

<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>

效果

image.png

总结

  • 前提条件:子组件并没有将声明为class一个它所接受的prop,所以class被视作透传attribute,且自动透传到了根元素上
  • 1.如果一个子或后代组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并;
  • 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>

点击后代组件的控制台效果

  • 分别触发父、子、后代事件内的逻辑
  • 执行顺序为后代组件内部逻辑——子组件事件逻辑——父组件事件逻辑 image.png

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不再透传生效
  • 点击后代组件时,父组件事件内逻辑不再被触发 image.png

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效果点击后代组件控制台效果如下:

image.png

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>
  • 此时点击后代组件内事件,父组件透传至子组件因为只有一个根元素则不受影响,但因为后代组件多根节点,此时子组件无法透传至后代组件,所以子组件透传事件逻辑此时不会触发。

后代组件示例代码效果

image.png

  • 官方文档解释:多根节点模板,由于Vue不知道要将attribute透传到哪里,所以会抛出一个警告。

透传警告

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

指定节点透传

<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不会抛出警告。

image.png

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>

效果

image.png