精通Vue(5):组件

这几天事情比较多,所以没有更新了。

这篇研究组件的问题,我们写慢点,争取将一个问题说清楚。

组件有什么用?

写下作者的理解,列如你要显示某个用户的信息,代码如下:

<div>姓名:bob</div>
<div>年龄:20</div>
<div>地址:广州天河区</div>

没有问题,过了一会,又要显示一个人的信息:

<div>姓名:bob</div>
<div>年龄:20</div>
<div>地址:广州天河区</div>
...
...
...
<!-- 又写一遍 -->
<div>姓名:peter</div>
<div>年龄:23</div>
<div>地址:广州黄浦区</div>

结果又把这个代码写了一遍,如果后面再有呢?原样又写一遍。重复多了,我们就会发现,这片代码外观不变,只是数据不同,那么我们就把这片代码抽象成组件,只要提供给它不同的数据即可,如下所示:

精通Vue(5):组件

我们抽象出来一个组件名为user,在vue2.x的版本中,要求必须有一个根标签,所以作者这里添加了一个红色div表明根标签。那么在使用的时候就变成下面这样:

<div id="app">
  <user></user>
  ...
  ...
  ...
  <user></user>
  ...
  <user></user>
</div>

视觉上清爽多了。

除了这些,它还有许多优点,随着学习会一步步加深了解。

创建组件方式一:全局注册

使用Vue.extend()创建,代码如下:

<!-- 组件的模板 -->
<template id="user">
  <div>
    <div>姓名:{{name}}</div>
    <div>年龄:{{age}}</div>
    <div>地址:{{address}}</div>
  </div>
</template>

<script>
  //创建一个组件的构造函数
  var user = Vue.extend({
    template: '#user',
    data() {
      return {}
    },
    props: {
      name: String,
      age: Number,
      address: String
    }
  })

  //全局注册
  Vue.component('user', user)
</script>

我们看这个组件,就像一个独立的模块,有自己专有的模板,数据,方法等,和Vue这个构造几乎一样,如果我们能将它们等价类比也是可以的。

使用组件

组件创建好之后,我们就可以拿来使用它,如下所示:

<div id="app">
  <!-- 使用组件 -->
  <user></user>
</div>

组件就像是一个独立模块,五脏俱全,在当前例子中,这个组件唯一欠缺的就是不知道数据是多少。举个通俗的例子,我们买了一台做馒头的机器,它可以自己执行各个流程不需要外界干扰,但唯一缺的就是加工原料,如果把原料输给它之后,后面我们就什么都不管了。那么如何把原料送给它呢?这就是组件通信的问题。

组件通信

我想把一个人的信息发给user组件让它显示出来,怎么做呢?这个问题我们需要好好的研究一下,但是我们不看组件,而是先看C的函数调用,列如C语言定义一个函数:

void foo(int a,char b,bool c){
	//操作
	printf("%d,%c,%d",a,b,c);
}

我们知道,函数声明就像是一个契约,它表明这个函数能接收多少个参数,而且每个参数是什么类型,图示如下:

精通Vue(5):组件

函数foo如果能说话的话,它就会说:我有三个参数(也就是三个入口),请传给我三个数据,类型分别是int,char,bool。我们看到这个声明,马上就清楚怎么发数据给它。

回到组件,它和这个原理几乎一样。站在组件这一方,它肯定会想,我怎么能让别人知道传什么数据给我呢?这是首要解决的问题。其次,站在调用方的角度,列如说我们,怎么知道传哪些数据给它呢?那么组件就想,干脆我也学C语言好了,做个声明。

组件的“函数声明”就是props。

props

<script>
  //创建一个组件的构造函数
  var user = Vue.extend({
    template: '#user',
    data() {
      return {}
    },
    props: {
      name: String,
      age: Number,
      address: String
    }
  })
</script>

在组件的props字段中,定义了三个参数,然后,这个组件可能会在文档上给我们暴露,让我们知道要传这三个数据给它。

那么数据怎么传呢?有许多种方式,但是在当前这个例子中只有一个地方可以传,就是在组件的属性上,如下:

<div id="app">
  <!-- 使用组件 -->
  <user name="bob" age=20 address="广州天河区"></user>
</div>

这种方式是最符合正常思维的,因此我们作为第一种方式特别拿出来举例。

这是其一。

其二,我们传的基本都是数据,不会出现style,class等相关的内容,由于<user>这个标签是被替换掉的,它不是真正的HTML标签。因此组件就认为我们传过来的都是数据。

数据来了下一步怎么办?

精通Vue(5):组件

第一,组件在内部已经声明好了三个“变量”,然后我们把数据送进来。但是它和C语言的函数有下面不同:

1、数据传送没有顺序的区分,列如我们写成这样<user age=20 address=’xx’ name=’bob’…>没有问题。

2、数据的个数不受限制(这个后面讨论)。

所以我们是类比C语言的函数,而不是说它俩是一样的。数据进来之后组件会查看一下,列如第一个我们传送的是name=”bob”,那么组件就看看自己肚子里面有没有名叫name的变量(或者说prop),如果正好有,然后再看看类型是否匹配?就像C语言的int,char一样,这个我们也在后面再展开说。如果一切都通过检查,那么内部的name就得到了值bob。其余以此类推。

所以prop是以名字做为唯一标识的,最终结果如下:

精通Vue(5):组件

问题一:数据类型检查

和C语言函数不同的是,组件prop的数据类型检查似乎没有那么严格,由于我们都知道JS的数据是乱套的,串转值、值转串之类的自动转换常常发生,所以我们就不能苛求它像C那样做严格的检查,列如:

<div id="app">
  <!-- 使用组件 -->
  <user name="bob" age="20" address="广州天河区"></user>
</div>

明明组件定义的age类型为Number,但是我们传给它的却是一个字符串,结果它只能警告一下。所以prop的数据类型定义只是要求我们尽量遵守,我们需要知道这个事情。

再列如,还有一种情况:

<script>
  //创建一个组件的构造函数
  var user = Vue.extend({
    template: '#user',
    data() {
      return {}
    },
    props: ['name','age','address']
  })
</script>

这个props的定义只有一个名字,更省略了,言外之意就是不管啥数据只要名字一样就可以了,也就是说连数据类型也不检查了。

当然,除了这个,还有其它各种用法,大家可以自行看文档,我们在这里主要是讨论原理,只要理解原理,其它的用法就能懂,所以不再多写。

问题二:传的数据多了怎么办?

列如我们除了传prop,还多传了一个phone:

<div id="app">
  <!-- 使用组件 -->
  <user name="bob" age="20" address="广州天河区" phone='137000'></user>
</div>

组件并不会由于它不是prop而不接收,而是照单接收。但是做为attrs来存储了,如下所示:

精通Vue(5):组件

因此,我们需要理解什么是attrs。

什么是attrs?

attrs就是标签的属性,也就是说凡是标签上面挂着的都算是属性,列如:

<div id=”app” xx=xx xx=xx….>

在这个标签上挂着的所有xx=yy都算是属性,还包括有名无值的xx。

因此,组件在接收数据的时候,会把传过来的数据全部看作是attrs(我的理解),只是这些属性中恰好有几个和它内部定义的props的名字是一样的,于是组件拿到值之后顺带过滤掉了,而没有过滤的自然就存储到组件的attrs中,这样逻辑上能通顺。

下面我们先看最终的结果:

精通Vue(5):组件

可以看到它把多余的属性设置到组件的根标签上了。如果不想让它挂上去可以吗?也是可以的,组件提供一个下面的属性:

<script>
  //创建一个组件的构造函数
  var user = Vue.extend({
    template: '#user',
    data() {
      return {}
    },
    inheritAttrs:false,
    props: {
      name: String,
      age: Number,
      address: String
    }
  })
</script>

将inheritAttrs设置为false,那么就不再设置HTML标签的attributes。

数据在组件中透传

根据上面的分析,我们知道如果数据没有被prop处理,就会转到组件的$attrs中,如果我们把这个$attrs绑定着继续往下传,就可以将祖先的数据一直往下传,就像穿透了组件一样。

精通Vue(5):组件

分析:数据传下来时,先被props过滤,剩下的存储到this.$attrs,接着我们把$attrs继续往下传,下方组件依然先props过滤,接着再存储….以此类推。这样就实现了一个简单的组件数据通信。

综合代码

下面是截止到最后一个例子的完整调试代码

<div id="app">
    <!-- 使用user组件 -->
    <user name="bob" age=20 address="广州天河区" phone="137000"></user>
</div>
<template id="user">
    <div>
        <div>{{name}}</div>
        <div>{{age}}</div>
        <div>{{address}}</div>
        <div>
            <button @click="handleClick">点我</button>
        </div>
        <!-- 使用手机组件 -->
        <phone v-bind="$attrs"></phone>
    </div>
</template>
<script>
    //1.定义手机组件
    var phone = Vue.extend({
        template: '<h2>{{phone}}</h2>',
        props: {
            phone: String
        }
    })
    Vue.component('phone', phone)

    //2.定义user组件
    var user = Vue.extend({
        template: '#user',
        data() {
            return {}
        },
        inheritAttrs: false,
        props: {
            name: String,
            age: Number,
            address: String
        },
        methods: {
            handleClick() {
                console.log(this.$attrs)
            }
        }
    })
    Vue.component('user', user)

    //3.定义根实例
    var vm = new Vue({
        el: '#app',
        data() {
            return {}
        }
    })
</script>

分析:在<user>组件中使用<phone>组件,并且使用v-bind=”$attrs”,直接将整个$attrs绑进去了。在vue中如果v-bind绑定的是对象,就表明把该对象内部的字段挨个绑定,这样我们就将$attrs整个的传给phone组件。

总结

不知不觉又写了一大篇,实则1/10都没写到。太长读者会很累,这篇我们就截止到这里。有兴趣的同学可以跟着动手做一遍。

在本篇中,重点说明了props是怎么来的,以及如何传数据的,顺带实现了一个简单的通信。

© 版权声明

相关文章

1 条评论

  • 头像
    尖尖角创始人张老师 读者

    组件抽象成模块很实用

    无记录
    回复