一文搞懂 vue3 中的属性透传

背景

vue3 中有一个很有意思的特性,就是属性透传,这个特性我们经常会使用到,但往往不会注意到它,直到在实际工作中出现了一个警告,才让我注意到它:

这个警告的大致意思就是,在子组件中,由于子组件有多个节点或者使用了文本根节点导致非 props 接收的属性无法自动继承,我看到这个警告时,也是一脸疑惑,并不知道哪里出了问题,(可能是我当初在学习 vue 时忽略了这个知识点),在经过查阅百度和文档,才彻底了解了它的作用,故写下这篇博客记录一下 vue3 中的属性透传。

基本概念

首先看官方文档的解释:

"透传 attribute"指的是传递给一个组件,却没有被该组件声明为propsemitsattribute 或者 v-on 事件监听器。最常见的例子就是 classstyleid。当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。

官网介绍的非常清楚:当我们给子组件传递一个属性或者事件时,子组件由于没有使用 prosemits 接收,于是该属性或属性就被自动传递到子组件的根元素节点上,具体可以看这个例子:

html 复制代码
// parents.vue
<template>
  我是父组件
  <Children class="children" style="color: red;" name="aaa" age="1"> </Children>
</template>

<script setup lang="ts">
  import Children from "./children.vue";
</script>

<style scoped></style>
html 复制代码
// children.vue
<template>
  <div>我是子组件</div>
</template>

<script setup lang="ts">
  defineProps({
    name: String,
  });
</script>

<style scoped></style>

我们在父组件中给子组件传递了 classstylenameage等属性和事件,而在子组件中,只在props中声明了name属性,于是我们打开浏览器看看效果:

可以看到,classagestyle均传递到了子组件的根节点上,而被props 接收的 name 属性则没有被传递到子组件的根节点上,这就是属性透传。仔细想想我们给组件定义的 styleclassid等属性是不是都用了这个特性。

属性的自动合并

子组件除了可以自动继承父组件传递过来的值以外,针对styleclass属性子组件还可以进行属性的合并,而对于其他自定义的属性则会被传递过来的属性值覆盖,具体可以看以下代码:

html 复制代码
// parents.vue
<template>
  我是父组件
  <Children class="children" style="color: red;" name="aaa"> </Children>
</template>

<script setup lang="ts">
  import Children from "./children.vue";
</script>

<style scoped></style>
html 复制代码
// children.vue
<template>
  <div class="children1" style="background-color: blue" name="bbb">
    我是子组件
  </div>
</template>
<script setup lang="ts"></script>

<style scoped></style>

在父组件中我们给子组件传递了 classstylename属性,而在子组件中,我们同样给根节点定义了这三个属性,看一下效果:

此时子组件的根节点的classstyle属性会自动合并,而被自定义name属性则被父组件传递过来的值覆盖。

如果我们给父组件和子组件同时定义一个click事件,那么会同时触发还是只触发一个呢?

html 复制代码
// parents.vue
<template>
  我是父组件
  <Children @click="handleClick"></Children>
</template>

<script setup lang="ts">
  import Children from "./children.vue";
  function handleClick() {
    console.log("p-click");
  }
</script>

<style scoped></style>
html 复制代码
// children.vue
<template>
  <div @click="handleClick">我是子组件</div>
</template>
<script setup lang="ts">
  function handleClick() {
    console.log("c-click");
  }
</script>

<style scoped></style>

看效果:

答案就是都会触发,并且触发顺序为先子组件后父组件。

以上就是属性和事件的合并。

深层组件透传

如果我们将子组件的根节点再替换为一个组件,类似这样:

html 复制代码
// children.vue
<template>
  <MybButton></MybButton>
</template>

<script setup lang="ts">
  import MybButton from "./MyButton.vue";
</script>

<style scoped></style>
html 复制代码
// MybButton.vue
<template>
  <div>我是孙组件</div>
</template>
<script setup lang="ts"></script>

<style scoped></style>

此时,子组件的透传属性,会原封不动的传递给孙组件:

这里要注意两点:

  • 如果父组件传递给子组件的属性或事件被 propsemits 接收了, 那么将不会被透传给孙组件,可以理解为 propsemits声明过的属性和事件被子组件消费了
  • 传递的属性若符合声明,也同样可以作为 propsemits 传递给孙组件

禁用属性透传

如果不想一个组件自动地继承传递过来的属性,可以在组件选项中设置 inheritAttrs: false。从 vue3.3 开始也可以直接在 defineOptions 中定义:

html 复制代码
<script setup>
  defineOptions({
    inheritAttrs: false,
  });
</script>

这样属性的就不会自动传递到子组件的根节点上了。

访问透传属性

首先,在模板中,我们可以使用 $attrs 来获取未被 propsemits 接收的属性,即透传的属性:

html 复制代码
// parents.vue
<template>
  我是父组件
  <Children class="children" name="aaa"> </Children>
</template>

<script setup lang="ts">
  import Children from "./children.vue";
</script>

<style scoped></style>
html 复制代码
// children.vue
<template>
  <div>{{$attrs}}</div>
</template>
<script setup lang="ts">
  defineOptions({
    inheritAttrs: false,
  });
</script>

<style scoped></style>

在父组件中,我们同样给子组件传递两个属性,在子组件中我们禁用自动继承,来看 $attrs 包含哪些属性:

可以看到 $attrs 就是一个包含了透传属性的对象,这里也有两点需要注意的:

  • props 有所不同,透传属性在保留了它们原始的大小写,所以像 foo-bar 这样的一个属性名需要通过 $attrs['foo-bar'] 来访问。
  • @click 这样的一个 v-on 事件监听器则需要使用 $attrs.onClick 来访问。

而在 javascript 中, 可以使用<script setup> 中的 useAttrs() API来访问一个组件的所有透传属性:

html 复制代码
<script setup>
import { useAttrs } from 'vue'
const attrs = useAttrs()
console.log(attrs)
</script>

打印一下, 可以看到 useAttrs() 得到的同样为一个包含了透传属性的对象:

如果没有使用 <script setup>attrs会作为 setup() 上下文对象的一个属性暴露:

js 复制代码
export default {
  setup(props, ctx) {
    // 透传 attribute 被暴露为 ctx.attrs
    console.log(ctx.attrs)
  }
}

这里需要注意的是,透传属性对象并不是响应式的 (考虑到性能因素),不能通过侦听器去监听它的变化。如果需要响应性,可以使用 prop。或者也可以使用 onUpdated() 使得在每次更新时获取最新的 attrs

$attrs的显示绑定

我们都知道 在vue3中, 组件可以同时有多个根节点,那么如果子组件有多个根节点,这时的透传属性则不知道该传递给哪个节点,就会出现文章一开始的警告,如要消除这个警告,则要显示的绑定透传属性,可以使用 v-bind='$attrs' 来指定绑定到哪个节点:

html 复制代码
// children.vue
<template>
  <header>...</header>
  <main v-bind="$attrs"></main>
  <footer>...</footer>
</template>

这样就被绑定到了 main 元素上,而不会出现警告了。

同样的,如果我们想绑定到非根节点上,那么我们需要将自动透传禁用,然后在想要绑定的节点上使用 v-bind 来绑定透传属性:

html 复制代码
// children.vue
<template>
  <div>
    <span v-bind="$attrs"></span>
  </div>
</template>

<script setup lang="ts">
  defineOptions({
    inheritAttrs: false,
  });
</script>

<style scoped></style>

到这里 vue3 中的属性透传就介绍完了,希望对你有所帮助。

相关推荐
web130933203986 分钟前
前端下载后端文件流,文件可以下载,但是打不开,显示“文件已损坏”的问题分析与解决方案
前端
王小王和他的小伙伴8 分钟前
解决 vue3 中 echarts图表在el-dialog中显示问题
javascript·vue.js·echarts
学前端的小朱12 分钟前
处理字体图标、js、html及其他资源
开发语言·javascript·webpack·html·打包工具
outstanding木槿17 分钟前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08211 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
摇光931 小时前
js高阶-async与事件循环
开发语言·javascript·事件循环·宏任务·微任务
隐形喷火龙1 小时前
element ui--下拉根据拼音首字母过滤
前端·vue.js·ui
m0_748241121 小时前
Selenium之Web元素定位
前端·selenium·测试工具
风无雨1 小时前
react杂乱笔记(一)
前端·笔记·react.js
胡西风_foxww1 小时前
【ES6复习笔记】Class类(15)
javascript·笔记·es6·继承··class·静态成员