这几天事情比较多,所以没有更新了。
这篇研究组件的问题,我们写慢点,争取将一个问题说清楚。
组件有什么用?
写下作者的理解,列如你要显示某个用户的信息,代码如下:
<div>姓名:bob</div>
<div>年龄:20</div>
<div>地址:广州天河区</div>
没有问题,过了一会,又要显示一个人的信息:
<div>姓名:bob</div>
<div>年龄:20</div>
<div>地址:广州天河区</div>
...
...
...
<!-- 又写一遍 -->
<div>姓名:peter</div>
<div>年龄:23</div>
<div>地址:广州黄浦区</div>
结果又把这个代码写了一遍,如果后面再有呢?原样又写一遍。重复多了,我们就会发现,这片代码外观不变,只是数据不同,那么我们就把这片代码抽象成组件,只要提供给它不同的数据即可,如下所示:

我们抽象出来一个组件名为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);
}
我们知道,函数声明就像是一个契约,它表明这个函数能接收多少个参数,而且每个参数是什么类型,图示如下:

函数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标签。因此组件就认为我们传过来的都是数据。
数据来了下一步怎么办?

第一,组件在内部已经声明好了三个“变量”,然后我们把数据送进来。但是它和C语言的函数有下面不同:
1、数据传送没有顺序的区分,列如我们写成这样<user age=20 address=’xx’ name=’bob’…>没有问题。
2、数据的个数不受限制(这个后面讨论)。
所以我们是类比C语言的函数,而不是说它俩是一样的。数据进来之后组件会查看一下,列如第一个我们传送的是name=”bob”,那么组件就看看自己肚子里面有没有名叫name的变量(或者说prop),如果正好有,然后再看看类型是否匹配?就像C语言的int,char一样,这个我们也在后面再展开说。如果一切都通过检查,那么内部的name就得到了值bob。其余以此类推。
所以prop是以名字做为唯一标识的,最终结果如下:

问题一:数据类型检查
和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来存储了,如下所示:

因此,我们需要理解什么是attrs。
什么是attrs?
attrs就是标签的属性,也就是说凡是标签上面挂着的都算是属性,列如:
<div id=”app” xx=xx xx=xx….>
在这个标签上挂着的所有xx=yy都算是属性,还包括有名无值的xx。
因此,组件在接收数据的时候,会把传过来的数据全部看作是attrs(我的理解),只是这些属性中恰好有几个和它内部定义的props的名字是一样的,于是组件拿到值之后顺带过滤掉了,而没有过滤的自然就存储到组件的attrs中,这样逻辑上能通顺。
下面我们先看最终的结果:

可以看到它把多余的属性设置到组件的根标签上了。如果不想让它挂上去可以吗?也是可以的,组件提供一个下面的属性:
<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绑定着继续往下传,就可以将祖先的数据一直往下传,就像穿透了组件一样。

分析:数据传下来时,先被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是怎么来的,以及如何传数据的,顺带实现了一个简单的通信。



组件抽象成模块很实用