创建组件
所有的 vue 实例里面都可以使用
注意: 2.0 之后 template 最外层只有一个结点
Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead
注意: data 必须是函数
组件写法 1
Vue.component('my-component', {
template: '<div @click="num++"></div>',
data() {
return {
num: 0
}
}
});
组件写法 2
var myComponent = Vue.extend({
template: '<div @click="num++"></div>',
data() {
return {
num: 0
}
}
});
Vue.component('my-component', myComponent);
vue.extend, vue.component 区别
Vue.extend 是构造一个组件的语法器
Vue.component 是注册组件的,注册标签便于在模板中使用,如果不需要标签可以不注册,如一些插件直接使用代码创建实例即可
var apple = Vue.extend({ ... })
// 注册组件,在模板中使用apple标签
Vue.component('apple',apple)
// 直接在代码中获得组件实例
let a = new aple()
a.$mount()
console.log(a.$el)
组件使用模版
在组件里面用template: '<div @click="num++"></div>'
是不利于书写的
方式 1
<script type="x-template" id="myComponent">
<div @click="num++"></div>
</script>
template: '#myComponent'
方式 2
<template id="myComponent">
<div @click="num++"></div>
</template>
template: '#myComponent'
动态组件
<div id="app">
<input type="button" @click="cpName='my-component-1'" value="使用my-component-1渲染">
<input type="button" @click="cpName='my-component-2'" value="使用my-component-2渲染">
<component :is="cpName"></component>
</div>
<script>
Vue.component('my-component-1', {
template: '<div>我是组件1</div>'
});
Vue.component('my-component-2', {
template: '<div>我是组件2</div>'
});
var vm = new Vue({
el: '#app',
data: {
cpName: 'my-component-1'
}
});
</script>
注意,组件切换是是重新渲染,数据不会保留
如果希望保留数据,可以使用 keep-alive
<keep-alive>
<component :is="currentView">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>
父子组件
注意子组件必须在父组件的模板中使用而不是嵌套,嵌套是 slot
错误写法
<father><son></son></father>
正确写法
父组件的模板
<div>
...
<son></son>
</div>
<div id="app">
<father></father>
</div>
<script>
Vue.component('father', {
template: '<div>我是父组件<son></son></div>',
components: {
'son': {
template: '<div>我是子组件</div>'
}
}
});
var vm = new Vue({
el: '#app'
});
</script>
插槽
引用组件时候,在组件标签内写的内容是会被覆盖的,即无效
<div id="app">
<comp>
<span>World</span>
</comp>
</div>
<script>
Vue.component('comp', {
template: '<div>Hello</div>'
});
var vm = new Vue({
el: '#app'
});
</script>
可以通过 slot 来将标签内的内容传递到组件模板中
<div id="app">
<comp>
<span>World</span>
</comp>
</div>
<script>
Vue.component('comp', {
template: '<div>Hello<slot></slot></div>'
});
var vm = new Vue({
el: '#app'
});
</script>
当然,把标签内的作为一个整体作为一个 slot 还是不太自由,可以为 slot 起名字,根据名字引用
直接 slot 代表的是所有没有名字的 slot
<div id="app">
<comp>
<span slot='s1'>World</span>
<span slot='s2'>!!!</span>
</comp>
</div>
<script>
Vue.component('comp', {
data() {
return {
name: " Ming"
}
},
template: '<div>Hello<slot name="s2"></slot><slot name="s1"></slot></div>'
});
var vm = new Vue({
el: '#app'
});
</script>
使用 prop 向组件传递数据
分为静态数据和动态数据,静态的就是一个字符串常量,动态的可以指定为父组件的 data 中数据
当让,动态的可以指定为一个数字,因为动态的实质就是一个表达式
当子组件需要一个对象传入且不想修改这个对象而影响到父组件,可以在自己的 data 返回 prop 中的一些属性
<son :sNum="fNum" sNum2="fNum"></son>
props: ['sNum',sNum2]
prop 的数据流向
父子组件之间通过动态 prop 传递数据是单向数据流,即父组件的数据改变会影响到子组件,子组件改变 prop 中的数据而不会影响到父组件
这句话是不对严谨的,对于一个数组或对象类型的 prop 来说,子组件改变 prop 也会影响到父组件,因为 JavaScript 中对象和数组是通过引用传入的
在父组件改变数据时候,子组件跟着改变,但是子组件改变 data1 会报错(单向数据流),改变 data2 父组件也会收到影响
Vue.component('father', {
data: function() {
return {
data1: -10,
data2: {
number: 0
}
}
},
template:`<div>
<span @click="data1++"></span>
<span @click="data2.number++"></span>
<son :data1="data1" :data2="data2"></son>
</div>`,
components: {
son: {
props: ['data1','data2'],
template: `<div>
<span @click="data1++"></span>
<span @click="data2.number++"></span>
</div>`
}
}
})
父子组件通讯的正确方式
综上,当 prop 为数组和对象的时候会影响到父组件破坏了单项数据流
同时,按照道理来讲,子组件不应该直接修改这些状态,因为会影响到父组件
所以正确的方式是子组件触发父组件的事件,然后在父组件中改变各个数据,然后会自动传递到子组件
- 通过 emit 来调用父组件的方法
Vue.component('father', {
data: function() {
return {
fNum: 1
}
},
methods: {
fFunc() {
this.fNum++
}
},
template:
'<div><div @click="fNum++">我是父组件</div><son @noticeF="fFunc" :fNum="fNum"></son></div>',
components: {
son: {
props: ['fNum'],
template: '<div @click="sClick">我是子组件</div>',
methods: {
sClick() {
this.$emit('noticeF')
}
}
}
}
})
- sync 语法糖
上面的 emit 可以通过使用 sync 语法糖来简化
Vue.component('father', {
data: function() {
return {
fNum: 1
}
},
template:
'<div><div @click="fNum++">我是父组件</div><son :fNum.sync="fNum"></son></div>',
components: {
son: {
props: ['fNum'],
template: '<div @click="sClick">我是子组件</div>',
methods: {
sClick() {
this.$emit('update:fNum', this.fNum + 1)
}
}
}
}
})
可以看出 Vue 替你在父组件内声明和了一个update:fNum
函数,该函数只有有一个参数用于更新父组件的值
不同组件之间通信
在一些大项目中会有大量的组件,不同组件都有自己的状态且之间能够互相通信
为了方便之间的数据通信,Vue 官方推荐的是使用 Vuex 对组件的状态数据进行统一管理
如果项目较小我们也可以自己实现不同组件之间的通信,首先为了便于管理,我们应该把不同组件的数据集中定义(Vuex 就是这样)
一般是于最外层的父组件中统一定义,然后层层向上 emit,为了简化这种操作,Vue 也提供了 hub 的方式,类似于中间人的角色一样
<div id="app">
<comp1></comp1>
<comp2></comp2>
</div>
<script>
//创建事件中心
var hub = new Vue()
hub.$on('event1', data => {
alert(data)
})
// 组件1
Vue.component('comp1', {
data() {
return {
num: 1
}
},
methods: {
click() {
// 触发hub中的的事件
hub.$emit('event1', 666)
hub.$emit('comp2Func', 666)
}
},
template: '<div @click="click"></div>'
})
// 组件2
Vue.component('comp2', {
data() {
return {
num: 1
}
},
mounted: function() {
let _this = this
// 在hub中定义事件,之所以在组件2里面定义就是为了this
hub.$on(
'comp2Func',
function(data) {
this.num = data
}.bind(_this)
)
},
template: '<div></div>'
})
var vm = new Vue({
el: '#app'
})
</script>