组件化
组件注册
在注册一个组件我们,需要给它一个名字。比如在全局注册的时候
Vue.component('my-component-name',{})
该组件名就是Vue.component的第一个参数,推荐遵循W3C规范中的自定义组件名。这回帮助你避免个当前以及未来的HTML元素冲突。
局部注册的组件在其子组件中不可用。如果想要使用需要这样写
var ComponentA ={}
var COmponentB ={
components:{
'component-a':ComponentA
}
}
或者通过Babel和webpack使用ES2015模块
import ComponentA from './ComponentA.vue'
export default{
components:{
ComponentA
}
}
注意在ES2015+中,在对象中放一个类似ComponentA
的变量名其实是:ComponentA:ComPonentA
的缩写,即这个变量名同时是:
- 用在模板中的自定义元素的名称
- 包含了这个组件选项的变量名
Prop
HTML中attribue名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用DOM中的模板时,驼峰命名法的prop名需要使用其等价的短横线分割命名命名:
Vue.component('blog-post',{
props:['postTitle'],
template:'<h3>{{postTitle}}</h3>'
})
<blog-post post-title="hello!"></blog-post>
Prop类型
字符串数组形式
props:['title','likes','isPublished','commentIds','author']
通常每个prop都有自己制定的值类型
props: {
title: String,
likes: Number,
isPublished: Boolean,
commentIds: Array,
author: Object,
callback: Function,
contactsPromise: Promise // or any other constructor
}
单向数据流
所有的prop都是的其父子prop之间形成单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。这样会防止子组件意外改变父级组件的状态,从而导致你的引用的数据流向难以理解。
额外的,每次父组件发生更新是,子组件中的所有prop都将会刷新为最新的值。这意味着你应该在一个子组件内部改变prop。如果你这样做了,Vue会在浏览器的控制台发出警告
-
在prop用来传递一个初始值:这个子组件接下来希望将其作为一个本地的prop数据来使用
在这种情况下,最好定义一个本地的data属性并将这个prop用作其初始值:
props:['initialCounter'], data: function(){ return { counter:this.initialCounter } }
-
这个prop以一个原始的值传入且需要进行转换。在这种情况下,最好使用这个prop的值来定义一个计算属性:
props:['size'],
computed:{
normalizedSize:function(){
return this.size.trim().toLowerCase()
}
}
Prop验证
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
}
}
}
})
自定义事件
不同于组件和prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。
this.$emit('myEvent')
监听这个名字的短横线分割命名版本是不会有任何效果的
<my-component v-on:myEvent="doSometing"></my-component>
将原生事件绑定到组件
监听一个原生事件,你可以使用v-on
的.native
修饰符
<base-input v-on:focus.native="onFocus"></base-input>
有的时候这是很有用的,但是尝试监听类似<input>
的非常特定的元素时,会导致监听失败
<base-input>
组件可能作为以下重构
<label>
{{label}}
</label>
<input
v-bind:"$attrs"
v-bind:value:"value"
v-on:input="$emit('input',$event.target.value)"
>
此时,父级的.native
监听器将失败,不会产生任何报错,但是oFocus
处理函数不会如逾期地被调用
vue提供了$listeners
属性,可以配合v-on=$listener
将所有的事件监听器指向这个组件的某个特定的子元素。为这些监听器创建一个类似inputListeners
的计算属性是非常有用的
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
组件间的通信
父子组件
父组件通过props
的方式向子组件传递数据,而通过$emit
子组件可以向父组件通信。
- 父组件向子组件传值
在子组件article.vue
中如何获取父组件section.vue
中的数据articles:['红楼梦','西游记','三国演义']
//section 父组件
<template>
<div class="section">
<com-article :articles="articleList"></com-article>
</div>
</template>
<script>
import comArticle from './test/article.vue'
export default{
name:'HelloWorld',
components:{comArticle},
data(){
return {
articleList:['红楼梦', '西游记', '三国演义']
}
}
}
</script>
//子组件 article.vue
<template>
<div>
<span v-for="(item,index) in articles" :key="index">{{item}}</span>
</div>
</template>
<script>
export default{
props:['articles']
}
</script>
prop只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且prop只读,不可被修改,所有修改都会失效并警告
- 子组件向父组件传值
$emit
绑定一个自定义事件,当这个语句被执行时,就会将参数arg传递给父组件,父组件通过v-on监听并接收参数。在上个例子的基础上,点击页面渲染出来的article
的item
,父组件中显示在数组中的下标
//父组件
<template>
<div class="section">
<com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
</div>
</template>
<script>
import comArticle from './test/article.vue'
export default{
name:'HelloWorld',
components:{comArticle},
data(){
return{
currentIndex:-1,
articleList:['红楼梦', '西游记', '三国演义']
}
},
methods:{
onEmitIndex(idx){
this.currentIndex= idx
}
}
}
</script>
//子组件
<template>
<div v-for="(item,index) in articles" :key="index" @click="emitIndex(index)">
{{item}}
</div>
</template>
<script>
export default{
prop:['articles'],
methods:{
emitIndex(index){
this.$emit('onEmitIndex',index)
}
}
}
</script>
非父子组件通信
eventBus
又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念,就像是所有组件公用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以通知其他组件
- 初始化
首先需要创建一个事件总线并将其导出,以便其他模块可以使用或者监听它
//event-bus.js
import Vue from 'vue'
export const EventBus =new Vue();
- 发送事件
假设你有两个组件:additionNum
和showNum
,这两个组件可以是兄弟组件也可以是父子组件;这里我们以兄弟组件为例:
<tempalte>
<div>
<show-num-com></show-num-com>
<addition-num-com></addition-num-com>
</div>
</tempalte>
<script>
import showNumCom from ‘./showNum.vue’
import additionNumCom from './additionNum.vue'
export default{
components:{showNumCom,additionNumCom}
}
</script>
//additionNum 中发送事件
<template>
<div>
<button @click="additionHandle">
+加法器
</button>
</div>
</template>
<script>
import {EventBus} from './event-bus.js'
export default{
data(){
return{
num:1
}
},
methods:{
additionHandle(){
EventBus.$emit('addition',{
num:this.num++
})
}
}
}
</script>
- 接收事件
//showNum.vue中接收事件
<template>
<div>
计算和:{{count}}
</div>
</template>
<script>
import {EventBus} from './event-bus.js'
export default{
data(){
return {
count:0
}
},
mounted(){
EventBus.$on('addition',param=>{
this.count =this.count +param.num
})
}
}
</script>
- 移除事件监听者
import {eventBus} from 'event-bus.js'
EventBus.$off('addition',{})
vuex
组件生命周期
Vue实例从创建到销毁的过程,就是生命周期。也就是从开始创建,初始化数据,编译模板,挂载DOM--->渲染,
更新--->渲染,卸载等一系列过程,我们称这是Vue的生命周期
- beforCreate:在实例化初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用
- created:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据检视(data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el属性目前不可见
- beforeMount: 在挂载开始之前被调用相关的render函数首次被调用
- mounted:
el
被新创建的vm.el
替换,并挂载到实例上去之后调用此钩子函数 - beforeUpdate: 数据更新是调用,发生在虚拟DOM重新渲染和打补丁之前
- updated:由于数据更新导致的虚拟DOM重新渲染和打补丁之前,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。
- beforeDestroy: 实例销毁之前被调用。在这一步,实例仍然完全可用
- destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都被解绑定,所有的事件监视器会被移除,所有的子实例也会被销毁
生命周期适用场景
beforeCreate:可以在这加loading事件,在加载实例时触发
created:初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用
mounted:挂载元素,获取到DOM节点
updated: 如果对数据统一处理,在这里写上相应函数
beforeDestory:一般在这里通过removeEventListener接触手动绑定的事件
Vue的父组件和子组件生命周期钩子函数执行顺序
- 加载渲染过程
父beforeCreate ---> 父created ---->父beforeMount ---->子beforeCreate --->子created --->子beforeMount ---->
子mounted ------>父mounted
- 子组件更新过程
父beforeUpdate ---->子beforeUpdata --->子updated ---->父updated
- 销毁过程
父beforeDestroy --->子beforeDestroy ---->子destroyed ---->父 destoryed
先创建父组件的实例模型,然后创建子组件模型。渲染页面先渲染子组件页面,当子组件页面渲染完毕之后,渲染父组件页面。在beforeDestory中及时销毁自定义事件,否则可能造成内存泄露