组件声明
- 分类:全局组件 局部组件
- 组件化开发有点:抽离组件实现复用;代码便于维护;便于组件级更新,每一个组件都有一个watcher(能抽离尽量抽离,减少更新)
Vue.component('my-component',{
data() { // data必须是一个函数,防止组件之间数据相互引用
return {
msg: 'hello',
}
},
template: "<div>{{msg}}</div>"
})
组件的实例化过程:根据传入对象,创建一个实例
- Vue.component默认调用vue.extend,返回传递对象的构造函数,通过这个构造函数可以手动的挂载组件
- vue.extend的原理:将传递的组件转为对象;创建一个子类,继承Vue的原型,并将参数传递给子类;在子类中会调用_init方法实现渲染,最终返回子类;多次使用同一个组件extend时,返回同一个类
let Ctor = Vue.extend({
data() {
return {
msg: 'hello',
}
},
template: "<div>{{msg}}</div>"
})
document.body.appendChild(new Ctor().$mount().$el) // 手动挂载组件
父子组件间通信
安装@vue/cli、@vue/cli-service-global快速原型工具,执行vue serve启动服务;实现零配置
组件使用规则:定义 引入 注册 使用,注册和使用时需要名字一致
通信方式:属性传递方式 事件绑定方式 语法糖方式v-model .sync
属性传递-props
父子组件间,父组件将属性和改变属性的方法fn传递给子组件,子组件进行接收;子组件通过调用传递的fn方法改变父组件的属性
// 父组件
<template>
<div>Parent
<Son1 :money='mny' :change-money='changeMoney'></Son1>
</div>
</template>
components: { Son1 },
data() { return { mny: 100 } },
methods: { // methods中的函数已经被bind过了,不管谁调用它的方法,函数中的this都是当前组件实例
changeMoney(value) { this.mny += value },
}
-----------------------------
// 子组件
<div> son1 {{money}}
<button @click='changeMoney(500)'>更改父亲</button>
</div>
props: {
money: { type: Number, default: 100 },
changeMoney: { type: Function, default: ()=> {} }
}
事件绑定方式$emit
在父组件中,给子组件增加事件cl;在子组件中,通过$emit方式触发cl事件
// 父组件
<template>
<div>Parent
<Son1 :money='mny' @cl='changeMoney'></Son1>
</div>
</template>
components: { Son1 },
data() { return { mny: 100 } },
methods: {
changeMoney(value) { this.mny += value },
}
-----------------------------
// 子组件
<div>son1 {{money}}
<button @cl='$emit("cl", 500)'>更改父亲</button>
</div>
props: {
money: { type: Number, default: 100 },
}
v-model方式同步数据
在父组件中,直接使用v-model,相当于v-bind:value+v-on:input;在子组件中,触发input事件,传递参数
// 父组件
<template>
<div>Parent
<!-- <Son1 :value='mny' @input='value=>mny=value'></Son1> -->
<Son1 v-model='mny'></Son1>-->
</div>
</template>
components: { Son1 },
data() { return { mny: 100 } },
-----------------------------
// 子组件
<div>son1 {{value}}
<button @cl='$emit("input", 500)'>更改父亲</button>
</div>
props: {
value: { type: Number, default: 100 },
}
自定义v-model方式同步数据
在父组件中,直接使用v-model;在子组件的model中自定义v-model的value和input方法---如何自定义v-model?
// 父组件
<template>
<div>Parent
<Son1 v-model='mny'></Son1>-->
</div>
</template>
components: { Son1 },
data() { return { mny: 100 } },
-----------------------------
// 子组件
<div>son1 {{mny}}
<button @click='$emit("change", 500)'>更改父亲</button>
</div>
model: { // 自定义v-model
prop: "mny", // 默认为value
event: 'change' // 默认为input
},
props: {
value: { type: Number, default: 100 },
}
.sync同步数据
在父组件中,使用.sync;在子组件中,触发update:money
.sync是:money和@update:money的语法糖,在子组件中触发的事件和绑定的事件一致--为什么可以使用.sync
// 父组件
<template>
<div>Parent
<!-- .sync等同于 <Son1 :money='mny' @update:money='val=>mny=val'></Son1> -->
<Son1 :money.sync='mny' ></Son1>
</div>
</template>
components: { Son1 },
data() { return { mny: 100 } },
-----------------------------
// 子组件
<div>son1 {{money}}
{{mny}}<button @click='$emit("update:money", 500)'>更改父亲</button>
</div>
props: {
mny: { type: Number, default: 100 },
}
跨级组件间通信
通过属性逐级传递
provide+inject
在父组件中,指定父组件的name,通过provide将父组件暴露出去;子孙组件通过inject注入后可以直接使用父组件name.属性和方法
缺点:容易造成数据流的混乱;此时,子组件可以通过parent.money = 200直接改变父组件的数据,不推荐使用这种方法,建议调用父组件的方法改变数据
// 父组件
<div>Parent <Son2></Son2> </div>
name: 'parent',
components: { Son2 },
data() { return { mny: 100 } },
provide() {
return {
parent: this, //将当前实例暴露出去
}
}
-----------------------------
// 子组件
<div> <grandson></grandson> </div>
-----------------------------
// 孙组件
<div> grandson {{parent.mny}} </div>
inject: ['parent'], // 将父组件注入
$parent|children
直接触发儿子或者父亲的方法
缺点:容易造成数据流的混乱,不好维护,尽量不使用;此时,子组件可以通过parent.money = 200直接改变父组件的数据,不推荐使用这种方法,建议调用父组件的方法改变数据
// 父组件
<div>Parent <Son2 @eat='eat'></Son2> </div>
name: 'parent',
components: { Son2 },
methods: {
eat() { console.log('这是parent中的eat') }
}
-----------------------------
// 子组件
<div> <grandson></grandson> </div>
-----------------------------
// 孙组件
<div>
<button @click='$parent.$emit("eat")'>通过$parent触发son2事件</button>
</div>
$disatch:实现指定的派发功能
// 向上派发,触发指定组件的事件
Vue.prototype.$dispatch = function (eventName, componentName, value) {
let parent = this.$parent;
while(parent){
if (parent.$options.name === componentName) {
parent.$emit(eventName, value); // 没有绑定的触发没有意义
return; // 一旦触发,不再继续向上查找
}
parent = parent.$parent;
}
}
// 父组件
<div>Parent <Son2 @eat='eat'></Son2> </div>
name: 'parent'
-----------------------------
// 子组件
<div> <grandson></grandson> </div>
name: 'son2'
-----------------------------
// 孙组件
<div> <button @click='$dispatch("eat","parent", "饭")'>通过dispatch触发son2事件</button> </div>
name: 'grandson'
$broadcast:指定的广播功能
// 向下扩展
Vue.prototype.$broadcast = function (eventName, componentName, value) {
let children = this.$children; // 是一个数组
function broadcast(children) {
for (let i = 0; i < children.length; i++) {
let child = children[i];
if (child.$options.name === componentName) {
child.$emit(eventName, value)
return;
} else {
if(child.$children) {
broadcast(child.$children)
}
}
}
}
broadcast(children)
}
$attrs|listeners:是所有方法和属性的合集
1. 父级的属性如果在子组件中通过props使用,那么在$attrs中会减少;
2. $attrs是响应式的,父级改变,数据也会更新
3. 可以通过v-bind|v-on将所有属性或方法都传递给儿子组件;
4. 通过inheritAttrs: false将没有使用的属性不添加在dom元素上
5. 不能隔代传递
<Parent a='1' b='2' c='3' d='4'></Parent>
-------------------------------------------------------------
// Parent组件
<div>
Parent {{$attrs}}
<Son v-bind="$attrs" @click='click' @mousedown='mousedown'></Son>
</div>
inheritAttrs: false,
props: ['a', 'b'],
methods: {
click() {
console.log('click')
},
mousedown() {
console.log('mousedown')
}
}
-------------------------------------------------------------
// Son组件
<div>
Son {{$attrs}}
<button @click="$listeners.click">点</button>
<Grandson v-on="$listeners"></Grandson>
</div>
mounted() {
console.log(this.$listeners)
}
-------------------------------------------------------------
// Grandson组件
<div>Grandson <button @click="$listeners.mousedown">ts点</button> </div>
$refs:通过它获取目标组件,调用它的方法
在普通元素上获取dom元素;在v-for,获取的是一组dom|实例;在组件上,获取的是当前组件的实例
<myDialog ref='dialog'></myDialog>
<button @click='change'>点我</button>
methods: {
change() {
this.$refs.dialog.change();
/*
ref:在普通元素上获取dom元素
在v-for,获取的是一组dom、实例
在组件上,获取的是当前组件的实例
*/
}
}
}
eventbus:发布订阅
1. 每个组件实例上都有\$on、\$emit、\$off;通过Vue.prototype.$bus = new Vue()可以让所有组件都能拿到\$bus,并创造公共实例,保证绑定事件和触发都是在同一个实例上
2. 注意在beforeDestroy上移除订阅
3. 子组件如何监听父组件的mounted?组件挂载:先挂载父组件,渲染子组件,--子mounted--父mounted;要实现子组件监听父组件的mounted,需要在子组件mounted时订阅一个监听事件,当父组件mounted时触发
Vue.prototype.$bus = new Vue()
// 父组件
mounted(){
this.$bus.$emit('监听事件', 1)
},
// 子组件
mounted(){
this.$bus.$on('监听事件', (arg) => {
console.log('父组件ok了', arg)
})
}
beforeDestroy() {
this.$bus.$off('监听事件')
}
缺点:可以实现任意组件间通信,但不好维护