前言:在实际工作中,有很多这样的场景:在子组件中进行数据的处理,在父组件中需要同步获取数据变化。比如一个查询页面,有很多的查询条件,假设现在有一个子组件为地区选择组件,我们需要选择完地区之后,立即进行查询操作,这时候就需要将这个数据在父子组件之间同步变化。
一、子组件的定义和引入
子组件定义的方式有全局注册和局部注册两种,不过在我实际工作中,基本上都是用局部注册,把子组件定义成一个.vue
文件,父组件通过import
引入子组件,并且在components
中注册子组件:
父组件
import AreasSelector from '@/components/AreasSelector/index';
...
components: {
AreasSelector
},
在父组件中使用子组件,要设计好子组件可以接受什么参数、以及要返回什么参数,根据各自的业务场景决定。
这里我的子组件的可以接收父组件传过来的两个参数:一个是areaIds
,即父组件中已经选择的地区ids
;一个是isMulti
,即地区是否支持多选。父组件向子组件传递数据需要使用props
,如果传递的是静态数据(字符串、数字等)不需要加:
,传递动态数据(需要从当前组件实例上获取的数据)需要加:
。
子组件要返回的参数就是用户点击行为之后选中的areaIds
。接收子组件返回的参数,需要在使用子组件的地方,使用$on
定义一个回调函数,相当于一个观察者:
父组件中
<AreasSelector :props="{areaIds,isMulti:true}" @area-change="onAreaChange"></AreasSelector>
在子组件中需要发送数据的地方,使用$emit
发送数据,相当于一个发布者:
子组件中
this.$emit('area-change', newVal);
$on
和$emit
使用的是观察者模式,即在$emit
中需要找到所有的$on
定义的回调函数,然后执行所有的回调函数,可以简单看一下vue
的源码,我只保留了最关键的部分:
Vue.prototype.$emit = function (event) {
var vm = this;
// cbs中存储着$on定义的回调函数列表
var cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
var info = "event handler for \"" + event + "\"";
for (var i = 0, l = cbs.length; i < l; i++) {
// 到这个函数中执行回调函数
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm
};
二、watch
在子组件的内部,用户点击地区时需要进行一系列的处理,处理成地区id
数组传递给父组件。传递给父组件这一过程,我们可以手动调用$emit
,也可以交给watch
去监听数据变化,在watch
内部调用$emit
。
watch官方文档
watch
是vue
组件可以设置的一个属性对象,和data
平级,用来监听vue
组件中数据的变化,key
为需要监听的数据,value
有三种定义形式:
① 回调函数的方法名,字符串
② 对象,其中的handler
属性是一个回调函数方法,deep
属性为true
时,表示监听的目标对象的深层属性变化时也会触发回调函数;immediate
属性设置为true
时,会在侦听开始之后被立即调用
③ 回调函数数组,它们会依次执行
我们可以为需要传递给父组件的数据定义一个watcher
,实时监听其变化,并且自动调用$emit
:
watch: {
'areaData': {
handler(newVal, oldVal) {
if (!newVal) return;
this.$emit('area-change', newVal);
},
deep: true,
immediate: true
}
},