一文搞懂 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 中的属性透传就介绍完了,希望对你有所帮助。

相关推荐
蓝天白云下遛狗4 分钟前
google-Chrome常用插件
前端·chrome
多多*42 分钟前
Spring之Bean的初始化 Bean的生命周期 全站式解析
java·开发语言·前端·数据库·后端·spring·servlet
linweidong1 小时前
在企业级应用中,你如何构建一个全面的前端测试策略,包括单元测试、集成测试、端到端测试
前端·selenium·单元测试·集成测试·前端面试·mocha·前端面经
满怀10151 小时前
【HTML 全栈进阶】从语义化到现代 Web 开发实战
前端·html
繁依Fanyi1 小时前
用 UniApp 构建习惯打卡 App —— HabitLoop 开发记
javascript·uni-app·codebuddy首席试玩官
东锋1.31 小时前
前端动画库 Anime.js 的V4 版本,兼容 Vue、React
前端·javascript·vue.js
满怀10152 小时前
【Flask全栈开发指南】从零构建企业级Web应用
前端·python·flask·后端开发·全栈开发
小杨升级打怪中2 小时前
前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制
前端·webpack·node.js
Yvonne爱编码2 小时前
CSS- 4.4 固定定位(fixed)& 咖啡售卖官网实例
前端·css·html·状态模式·hbuilder
SuperherRo3 小时前
Web开发-JavaEE应用&SpringBoot栈&SnakeYaml反序列化链&JAR&WAR&构建打包
前端·java-ee·jar·反序列化·war·snakeyaml