vue 高阶组件必备知识$attrs,inheritAttrs,$listenters详解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue 高阶组件必备知识$attrs,inheritAttrs,$listenters详解相关的知识,希望对你有一定的参考价值。

参考技术A vue 默认情况下,父组件是可以直接给子组件的根元素添加 class 和 style 的,但是有时候我们可能需要在父组件上给子组件添加一些特性绑定( attribute bindings )(我的理解是自定义属性和一些原生属性)到子组件的根元素上, inheritAttrs 就是用来控制子组件根元素上是否允许添加父组件在子组件上定义的特性属性,因为 inheritAttrs 默认为 true ,所以我们在父组件中给子组件添加所有特性绑定,都能绑定到根元素,例如下面

渲染后的dom节点:

把 inheritAttrs 设为 false 后,
渲染后的dom节点:

可以得出上述的结论, inheritAttrs 是用来控制子组件根元素上是否允许添加父组件在子组件上定义的特性属性。
注意:

我们可以把父作用域中传递的所有属性看作一个大的对象 obj ,而 $attrs 会继承 obj 中的一部分属性,这一部分属性的 key 不能为 class ,和 style ,也不能是当前组件声明的 props 值,并且父组件为 v-model 的话,也是不能继承指令封装的 value 值的,若当前组件无props设置, $attrs 则继承除 class 和 style 的所有属性。
通常我们给已封装的组件进行中间处理的时候使用,例如 element-ui 的 el-input ,我们需要把父组件中的传递的 props 直接给子组件的子组件的时候,我们就可以用到 $attrs ,例如:
父组件为:

子组件为:

根据上面所说,去掉 class ,去掉指定的 value ,去掉子组件 props 声明的 limit ,那时我们此的 $attrs 是:

我们对ui框架中的组件进行再封装的时候,例如element-ui中的组件 el-input ,我们把他封装到我们自己的组件内部。
例如 self-input 组件如下,

我们需要把 el-input 组件上自定义的事件传递进去,那么就要用到 $listenters 。

官网的描述:

很容易理解, .native 是给子组件根元素添加事件,自然不是用来传给子组件上的子组件或元素。而我们在父组件上给子组件绑定的所有事件,都会放入 $listeners 中,我们可以在子组件中手动过滤修改后传给子组件中的子组件或者元素上,也可以用 v-on="$listeners" ,直接全部传给子组件或者元素上。
用法也相当简单,例如:
父组件:

子组件:

由于父组件传入的是一个change事件和两个input事件,那我们打印的 this.$listeners 是

下面是一个关于限制小数点的完整demo,感兴趣的可以了解一下

vue组件之间通信(provide/inject与$attrs/$listeners) 之四

一. provide/inject

     是Vue.js2.2.0版本后新增的API,虽然官方文档说,provide和inject主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中,但是在插件 / 组件库(比如 iView,事实上 iView 的很多组件都在用)。

这对选项需要一起使用,以允许一个祖先组件向其所有的子孙后代注入一个依赖,不论组件的层次有多深,并在起上下游关系成立的时间里始终生效。

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。

inject 选项应该是:一个字符串数组,或一个对象(属性值是一个对象时,包含from和default默认值)

 注意:provideinject绑定并不是可响应的。这显然不是设计的失误,而是刻意的。如果要详细了解,请参考:API — Vue.js

//祖先级组件(上级组件)
<template>
    <div>
        <Pro></Pro>
    </div>
</template>
<script>
import Pro from '../components/provide.vue';
export default 
    data()
        return
        
    ,
     provide:
        foo:'liuhua'
    ,
    components:
        Pro,
    

</script>
<style scoped>
</style>
//子孙级组件(下级组件)
<template>
    <div>
        <p>foo</p>
    </div>
</template>
<script>
export default 
    data()
        return 
        
    ,
    inject:['foo'],

</script>
<style scoped>
</style>

我们在上级组件中设置了一个provide:foo,值为liuhua,它的作用就是将foo这个变量提供给它的所有下级组件。而在下级组件中通过inject注入了从上级组件中提供的foo变量,那么在下级组件中,就可以直接通过this.foo来访问了

我们一般会在main.js中导入app.vue作为根组件,我们需要在app.vue上做文章,这就是我们实现功能的关键。我们可以这样理解:app.vue作为一个最外层的根组件,用来存储所有需要的全局数据和状态。因为项目中的所有组件(包含路由),它的父组件(或根组件)都是app.vue,所有我们可以把整个app.vue实例通过provide对外提供。那么,所有的组件都能共享其数据,方法等。

//app.vue,部分代码省略:
<script>
export default 
    provide () 
      return 
        app: this
      
    ,
    data () 
      return 
        userInfo: null
      
    ,
    methods: 
      getUserInfo () 
        // 这里通过 ajax 获取用户信息后,赋值给 this.userInfo,以下为伪代码
        $.ajax('/user/info', (data) => 
          this.userInfo = data;
        );
      
    ,
    mounted () 
      this.getUserInfo();
    
  
</script>

 上面,我们把整个app.vue的实例`this`对外提供,接下来,任何组件(或路由)只要通过`inject`注入app.vue的话,都可以通过this.app.xxx的形式来访问app.vue的data,computed,method等内容

<template>
  <div>
     app.userInfo 
  </div>
</template>
<script>
export default 
    inject: ['app']
 
</script>

只要一个组件使用了 `provide` 向下提供数据,那其下所有的子组件都可以通过 `inject` 来注入,不管中间隔了多少代,而且可以注入多个来自不同父级提供的数据。需要注意的是,一旦注入了某个数据,比如 `app`,那这个组件中就不能再声明 `app` 这个数据了,因为它已经被父级占有

我们介绍的这对 API,主要还是在独立组件中发挥作用

二.$attrs/$listeners

  • $attrs是在vue的2.40版本以上添加的。
  • 项目中有多层组件传参可以使用$attrs,可以使代码更加美观,更加简洁,维护代码的时候更方便。如果使用普通的父子组件传参prop和$emit$on会很繁琐;如果使用vuex会大材小用,只是在这几个组件中使用,没必要使用vuex;使用事件总线eventBus,使用不恰当的话,有可能会出现事件多次执行。
  • 非props属性:父组件传入子组件属性,但子组件没有接收称为非props属性,非props属性默认会加到子组件标签最外层(inheritAttrs:true,如果是false就不放到标签最外层显示)

  • 所有的非props属性都可以通过$attrs收到

    • 应用: v-bind="$attrs" 将所有的非props属性绑定到相应标签,也可以用于组件

  • 所有组件上的方法绑定子组件都可以通过$listeners接收

    • 应用: v-on="$listeners" 将所有的方法又绑定到组件相应标签,也可以用于组件

//最上层父级 传递数据的
<template>
  <div>
    <!-- 非props属性:组件标签上传入的属性如果子组件没有接收会跑到子组件标签最外层
    
     -->
    <!--通常: .native修饰符 才可以在组件标签上使用原生的事件 -->
    <Son
      src="https://img01.yzcdn.cn/vant/logo.png"
      @click="sonClick"
      @mouseleave="sonClick"
    ></Son>

  </div>
</template>
<script>
import Son from "./components/son.vue";
export default 
  components: 
    Son,
  ,
  methods: 
    sonClick() 
      console.log("sonClick");
    ,
  ,
;
</script>
子组件

<template>
  <div>
    <!-- 孙组件 通过该组件 接收祖先组件传递的src 与两个方法 -->
    <SonSon v-bind="$attrs" v-on="$listeners"></SonSon>
    <button @click="btnClick">打印$listeners</button>

    <!-- 子组件 接收父组件传递的src 与两个方法 -->
    <!-- <img v-bind="$attrs" v-on="$listeners" class="img" alt="" /> -->
     $attrs 

    <!-- <img :a='1' :b="2" class="img" alt="" /> -->
    <!-- 等价于如下方式 -->
    <!-- <img v-bind="obj" class="img" alt="" /> -->
  </div>

</template>
<script>
import SonSon from "./sonson.vue";
export default 
  components: 
    SonSon,
  ,

  data() 
    return 
      obj: 
        a: 1,
        b: 2,
      ,
    ;
  ,
  //   props: ["src"],//接收传递的值后,则$attrs失效
  methods: 
    btnClick() 
      console.log(this.$listeners);
    ,
  ,
;
</script>

情况一:

直接在孙组件中用$attras接收祖先传递的 非props属性,用$listeners接收祖先的方法

<template>
  <div>
    <img v-bind="$attrs" alt="" />
    <button v-on="$listeners">孙组件</button>
  </div>
</template>
<script>
export default 

;
</script>

 情况二:

儿子组件,中间层,作为父组件和孙子组件的传递中介,在儿子组件中给孙子组件添加v-bind="$attrs",这样孙子组件才能接收到数据,

在孙子组件中一定要使用props接收从父组件传递过来的数据,直接与prop传值一样使用在孙组件中

以上是关于vue 高阶组件必备知识$attrs,inheritAttrs,$listenters详解的主要内容,如果未能解决你的问题,请参考以下文章

Vue入门必备知识篇03--- 生命周期 & 数据共享

vue进阶知识点

[转] vue中父子组件通信

高阶前端开发人员必备工具-Node.JS知识讲解

vue基本知识点总结—面试必备

Vue 基础知识