1单文件组件
前面在vue.js破冰系列2中提到HTML模板有4中方式定义,分别是:在挂载点内定义模板、在template选项中定义模板、以x-template方式类型模板,以及使用单文件组件(single-file components)的方式。这里我们在讲解单文件组件。
单文件组件顾名思义,一个文件就是一个组件,文件的扩展名为.vue
。它的好处有很多,比如支持语法高亮,css样式等。最重要的是,有效的支持前端工程化以及关注点分离。前端工程化一般使用webpack等构建工具,当我们项目越来越大时,使用单文件组件方便代码的组织和维护(关注点分离)。
一个单文件组件.vue
包含3个顶级语言块<template>
、<scirpt>
、<style>
,vue通过外部loader将这3个语言块解析为一个组件选项对象。
一个单文件中只能包含一个template和一个script语言块,但是可以包含多个style语言块,在template标签中只能存在一个根节点。格式如下:
//xxx.vue
<template></template>
<scirpt></scirpt>
<style></style>
2Props数据传递
2.1 props的定义
在注册组件时,使用props选项定义组件的特征,父组件通过这些特征向子组件传递数据,props可以接收数组和对象作为参数,数组中包含string类型的值,如果要对属性做更多的配置,则需要使用object类型。
<template>
<div>
<!--props的引用-->
<h4>{{message}}</h4>
</div>
</template>
<script>
export default{
//数组的方式定义props
//props:["message"],
//对象的方式定义props
props:{
message:String,
}
}
</script>
props的数组写法相对简单,只需要定义属性名即可。如果要对属性做一些约定则需要用到对象写法,对象的键为属性名,值可以是类型或一个属性参数对象。当属性值为类型时,表示对该属性做类型约束,如果是属性参数对象,则可以做更高级的约束:
<script>
export default{
props:{
属性名:{
type:'属性类型,String|Number|Boolean|Array|Object|Data|Function|Symbol|[可能的类型集合]',
default:'默认值,Object和Array类型的prop需要使用函数的方式(工厂函数)返回,原理同组件中的data使用函数返回一样',
required:'是否为必填项,Boolean',
validator:'验证函数,返回Boolean值,如果返回false,在控制台会发出警告'
}
}
}
</script>
可以看到props使用对象语法时,可以定义属性的类型,默认值,是否为必须已经验证信息等。
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
2.2 props的使用
html标签是不区分大小写的,如果porps使用Pascal命名,Vue会将其转换为kebab-case形式(我们在上一章提到过Pascal和kebab)。
//子组件welcome.vue
<tempalte>
<div>
<span>你好 {{name}},上次登录时间:{{lastLoginTime}}</span>
</div>
</tempalte>
<script>
export default{
props:['name','lastLoginTime']
}
</script>
父组件调用:
<tempalte>
<div>
<!-- 使用kebab形式调用子组件的属性 -->
<welcome :name="name" :last-login-time="time" />
</div>
</tempalte>
<script>
//导入
import Welcome from 'welcome.vue'
export default{
//局部注册
components:{
Welcome,
},
data(){
return {
name:'张三',
time:'2019-09-17',
}
}
}
</script>
2.3 单向数据流
子组件定义props,父组件向子组件传递数据,当父组件中的数据改变后,子组件会重新渲染,数据从父向子流动叫做数据的单向流动。Vue不允许在子组件中改变props的值,如果强行修改,Vue会在控制台中发出警告信息。
//子组件 child-component.vue
<template>
<div>
<h4>子组件中的title:{{title}}</h4>
<button @click="changePropsHandle">修改Props</button>
</div>
</template>
<script>
export default{
props:{
title:String,
},
methods:{
changePropsHandle(){
this.title="子组件修改的title"
}
}
}
</script>
<template>
<div>
<h4>父组件中的title:{{title}}</h4>
<child :title="title" />
</div>
</template>
<script>
import Child from 'child-component.vue'
export default{
components:{
Child
},
data(){
return {
title:"我是父组件中的title"
}
}
}
</script>
这个例子在点击子组件中的按钮是会在控制台中出现警告信息,原因是Vue中不允许直接修改props中的数据。如果要修改props,可以用data和computed替待。
//子组件 child-component.vue
<script>
export default{
props:{
title:String,
},
//title作为初始值保存在localTilte中
data(){
return{
loaclTitle:this.title,
}
},
computed:{
//将title转换为另一个字符串,
computeTilte:function(){
return "Hello"+this.title;
}
},
}
</script>
注意:当props的类型为Object或Array时,在子组件中修改props的项时,会影响到父组件,原因可以参考其他语言的引用传递和值传递。
3组件间通信
组件之间的关系可以简单的分为父子关系和非父子关系。父子关系的组件,父组件直接调用子组件,比如使用子组件的props传值给组件,或者使用子组件的slot插槽将内容分发到子组件。
3.1自定义事件
上面说了,子组件中不允许修改父组件传递的数据。有些情况下,子组件需要修改父组件传递来的值,这时,可以使用自定义事件通知父组件,由父组件修改其数据,间接的的达到子组件修改props。
我们在vue.js破冰系列2中提到v-on事件监听的内部指令,过程大致是这样的,父组件在子组件的标签中使用v-on监听事件,子组件中使用实例的内部方法$emit
触发事件,$emit
方法的定义如下:
vm.$emit('evnetName',[...args])
vm表示的是实例,在具体的组件中,可以使用this指代。$emit
的第1个参数为事件名称,他是一个字符串,vue不能自动转换其大小写,然而,使用v-on监听事件是在DOM标签中,我们知道DOM标签是不区分大小写的,DOM会将全部标签转换为小写,更进一步,要事件的触发,$emit
中事件名必须完全匹配v-on指令中的参数(事件名),所以vue建议在$emit
和v-on
定义的事件名使用kebab样式,或者也可以使用全小写的方式。
<div id="app">
<!--DOM 会将MyEvent转换为小写myevnet -->
<child @MyEvent="myEventHandler"></child>
<!-- 推荐使用kebab形式的事件名 -->
<child @my-event="myEventHandler" />
</div>
<!--子组件-->
<script>
export default{
methods:{
btnClicked(){
//该emit无效,因为v-on指令监听的事件名为myevent,而这里触发的是MyEvent事件
this.$emit('MyEvent');
//该emit有效
this.$emit('my-event');
}
}
}
</script>
$emit
的第2个参数为不定参数,表示的是该事件对应处理函数的实参。关于自定义事件可以参考vue.js破冰系列2中的v-on
和v-model
章节。
3.2中央事件总线
上面我们讲了父子组件之间的通信,他们是逐级传递的,也就是说父组件要和孙子组件通信,需要经过子组件,其他的还有兄弟组件之间的通信需要通过父组件。这种非父子组件之间的通信Vue推荐使用中央事件总线(bus),原理是这样的,创建一个vue的空实例,使用vue实例方法$emit
来触发事件,使用vue实例方法$on
来监听事件。
//创建一个vue的空实例,注意不能使用一个Object实例,因为我们要用到Vue实例的$emit和$on方法
var bus = new Vue();
//组件A注册监听bus上事件
export ComA={
created(){
//注册监听bus上的事件
bus.$on('my-evnet',myEvnetHandler)
},
methods:{
myEvnetHandler(arg1){
//事件处理逻辑
},
}
}
//组件B
export ComB = {
methods:{
clicked(){
//触发bus上的my-event事件
bus.$emit('my-event','Hello World!')
}
}
}
上面的例子演示了组件A和组件B之间的通信。