vue2和vue3中$attrs的对比

565 阅读1分钟

透传的attribute

介绍$arrts之前先讲一下“透传的attribute”,“透传的attribute”指的的是传递给一个组件,却没有被组件声明为prop和emit的attribute,常见的就是styleclass

以一个<MyButton>组件为例:

<!-- Mybutton -->
<template>
  <button>click me</button>
</template><script>
  export default {
    props:{
      foo:String
    },
  }
</script>

当你在父组件这样调用:

<my-button
  class="button" 
  style="margin-top:20px" 
  :foo="foo" 
  :bar="bar"
></my-button>

这时classstylebar会自动被添加到根元素上,渲染出来的dom就是这样:

<button bar="bar" class="button" style="margin-top: 20px;">
    click me
</button>

禁用Attribute继承

如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false

禁用Attribute继承

<!-- Mybutton -->
<template>
  <button>click me</button>
</template><script>
  export default {
    inheritAttrs: false,//新增
    props:{
      foo:String
    },
  }
</script>

设置禁用Attribute继承后,vue2和vue3有所区别。vue2渲染的dom依然会保留classstyle,只把没有在组件的prop中声明的attribute禁用了:

<button class="button" style="margin-top: 20px;">click me</button>

可以看到原来在dom中的bar="bar"消失了。而vue3渲染的dom则会禁用包括classstyle在内的attribute,渲染出来的dom为:

<button>click me</button>

$attrs

当父组件传递的attribute 需要应用在根节点以外的其他元素上时,可以通过$attrs访问到。

需要注意的是,vue2和vue3中的$attrs不一样,vue3中的$attrs是一个代理对象,包含了除组件所声明的 propsemits 之外的所有其他 attribute,例如 classstylev-on 监听器等等。

而vue2中的$attrs只包含了组件声明的prop之外的属性,这其中不包括classstyle。还是以<Mybutton>为例:

//Mybutton.vue
<template>
  <button>click me</button>
</template><script>
  export default {
    props:{
      foo:String
    },
    created(){
      console.log(this.$arrts)//重点看这里的打印结果
    }
  }
</script>
​
//parent.vue
<template>
  <div>
    <my-button
      class="button" 
      style="margin-top:20px" 
      :foo="foo" 
      :bar="bar"
      @changeMsgB="changeMsgB" 
      @changeMsgc="changeMsgc"
    ></my-button>
  </div>
</template><script>
  import myButton from "../components/myButton.vue"
  export default {
    components:{
      myButton
    },
    data(){
      return {
        foo:"foo",
        bar:"bar",
      }
    },
    methods:{
      changeMsgB(val){},
      changeMsgc(val){},
    }
  }
</script>

以上这段代码在vue2中运行的打印结果为:

微信截图_20221021205104.png

在vue3中运行的打印结果为:

微信截图_20221021205317.png

vue2中也可以通过$listeners在组件内访问到父组件传递的v-on,而在vue3中则把$listeners废弃掉了,把除组件所声明的 propsemits 之外的所有其他 attribute都整合到了$attrs

$attrs的组件通信

$attrs 常被用来进行祖先组件和子组件之间通信

vue2中通过$attrs$listeners配合进行进行祖先组件和子组件之间通信,假设有三个组件A、B、C,组件之间的关系A>B>C,现在A组件需要向C组件传递数据,方式如下:

A组件:

<template>
  <div>
    <component-b
      :foo="foo" 
      :bar="bar"
      @changeMsgc="changeMsgc"
    ></component-b>
  </div>
</template><script>
import componentB from "../components/B.vue"
  export default {
    components:{
      componentB
    },
    data(){
      return {
        foo:"foo",
        bar:"bar",
      }
    },
    methods:{
      changeMsgc(val){
        this.bar=val
      },
    }
  }
</script>

B组件:

<template>
  <div>
    <span>b组件 foo:{{foo}}</span>
    <component-c v-bind="$attrs" v-on="$listeners"></component-c>
  </div>
</template>

<script>
  import componentC from "../components/C.vue"
  export default {
    inheritAttrs: false,
    components:{componentC},
    props:{
      foo:String
    }
  }
</script>

C组件:

<template>
  <div>
    <span>c组件 bar:{{bar}}</span>
    <button @click="handleClick">update</button>
  </div>
</template><script>
  export default {
    props:{
      bar:String
    },
    methods:{
      handleClick(){
        this.$emit('changeMsgc','update')
      }
    }
  }
</script>

vue3的组件传值

A组件:

<template>
  <div>
    <component-b
      :foo="foo" 
      :bar="bar"
      @changeMsgc="changeMsgc"
    ></component-b>
  </div>
</template><script setup>
import componentB from "../../components/B.vue"
import {ref} from "vue"
const foo=ref('foo')
​
const bar=ref('bar')
const changeMsgc=(val)=>{bar.value=val}
</script>

B组件:

<template>
  <div>
    <span>b组件 foo:{{foo}}</span>
    <component-c v-bind="$attrs"></component-c>
  </div>
</template><script>
import { defineComponent } from 'vue'
import componentC from "../components/C.vue"
export default defineComponent({
  inheritAttrs: false,
  components:{componentC},
  props:{
    foo:String
  }
})
</script>

C组件:

<template>
  <div>
    <span>c组件 bar:{{bar}}</span>
    <button @click="handleClick">update</button>
  </div>
</template><script>
import {defineComponent} from "vue"
export default defineComponent({
  props:{
    bar:String
  },
  emits:['changeMsgc'],
  setup(props,{emit}){
    return {
      handleClick(){
        emit('changeMsgc','update')
      }
    }
  }
})
</script>

最终效果:

动画.gif