在平时的业务开发中,相信在座的各位没少用过组件通讯。然而,对于一些新手/业务熟手来讲,不懂技术原理每每知其然而不知其因此然,用得一脸懵逼。看完本文能够帮助你了解
Vue
组件的通讯方式及原理,从而进一步加深对Vue
的理解,远离CV
工程师的行列。javascript
Vue
经常使用的组件通讯方式经过$emit
在子组件传参给父组件,同时触发对应的父组件函数,以此达到父子组件通讯的目的前端
经过eventbus
的$emit
和$on
方法传递数据,以此实现父子组件/兄弟组件之间的通讯vue
经过Vuex
将页面数据划分模块,更好更方便的管理数据java
// 父组件
<template>
<div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App" @test="test"/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'app', methods: { test (param) { debugger console.log('param-->', param); } }, components: { HelloWorld } } </script> 复制代码
// 子组件
<template>
<div class="wrapper">
<button @click="test">按钮</button>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
methods: {
test () {
debugger
this.$emit('test', '666')
}
}
}
</script>
复制代码
咱们能够看到,父子组件的test
方法中各打了一个debugger
。node
Vue.prototype.$emit = function (event) {
var vm = this;
...
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
};
复制代码
看完上面的代码咱们知道,vm._events[event]
拿到了一个方法,而后调用invokeWithErrorHandling
。固然,vm._events[event]
的方法应该是从template
上拿到的,接下来咱们能够带着这几个疑问继续往下看:chrome
vm._events
是何时赋值的?invokeWithErrorHandling
方法是怎么执行的?vm._events
是何时赋值的?在子组件的test
方法中打下一个断点,选中调用堆栈中的最后一个之后能够看到add$1
函数,在这里再下一个断点,从新刷新页面之后断点停在了add$1
这个函数上,同时调用堆栈列表刷新,大概有这些:数组
add$1
updateListeners
updateDomListeners
invokeCreateHooks
createElm
试探性的点进updateListeners
之后,咱们看到:微信
function updateListeners ( on, oldOn, add, remove$$1, createOnceHandler, vm ) {
var name, def$$1, cur, old, event;
// 看到这里初步猜想会遍历全部的方法
// 在chrome的断点下能够看到一个click属性,这里不知道为何没有test方法
for (name in on) {
def$$1 = cur = on[name];
old = oldOn[name];
event = normalizeEvent(name);
// 判断当前的方法的调用器(invoker)是不是undefined,在开发环境下则会有报错提示
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
"Invalid handler for event \"" + (event.name) + "\": got " + String(cur),
vm
);
} else if (isUndef(old)) { // 判断以前是否已存在
if (isUndef(cur.fns)) { // 判断实际上调用的函数是不是undefined
cur = on[name] = createFnInvoker(cur, vm);
}
if (isTrue(event.once)) { // 多是挂载在一次性节点上,这里也作出判断
cur = on[name] = createOnceHandler(event.name, cur, event.capture);
}
// 断点没打在这里以前,event.name一直是“click”
add(event.name, cur, event.capture, event.passive, event.params);
} else if (cur !== old) {
old.fns = cur;
on[name] = old;
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove$$1(event.name, oldOn[name], event.capture);
}
}
}
复制代码
整理完上面这个函数的逻辑之后,将断点打在add
上,刷新页面后断点停在这里,步进这个函数:app
function add (event, fn) {
target.$on(event, fn);
}
复制代码
显然target
是全局变量,可是这里先不深究。再次步进以后能够看到断点停在这里:dom
Vue.prototype.$on = function (event, fn) {
var vm = this;
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn);
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm
};
复制代码
可见父子组件通讯过程当中,尽管$on
对开发者不可见,可是最终仍是要走$on
函数,这里感受跟使用eventbus
大同小异。
至此,刚才提出的第一个疑问已经解决:)
invokeWithErrorHandling
方法是怎么执行的?在一开始的基础上,直接步进invokeWithErrorHandling
方法:
function invokeWithErrorHandling ( handler, context, args, vm, info ) {
var res;
try {
// 判断是否有参数,而后分状况调用
res = args ? handler.apply(context, args) : handler.call(context);
// 处理异步函数的状况
if (res && !res._isVue && isPromise(res)) {
// issue #9511
// reassign to res to avoid catch triggering multiple times when nested calls
res = res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
}
} catch (e) {
handleError(e, vm, info);
}
return res
}
复制代码
最后从新梳理下父子组件通讯的实现逻辑:
vm._events[event]
Vue
调用updateListeners
函数(固然,在那以前会生成虚拟dom
,也就是vnode
, 这里暂不深究),在函数里面调用createFnInvoker
方法,给模板上的方法再套一层调用器(invoker)target.$on
方法event
为数组的状况vm._events[event]
赋值invokeWithErrorHandling
方法是怎么执行的?⚠️注意:因为Vue
会在方法上再封装一层调用器(invoker
),因此在在调用堆栈这里每每会出现两个invokeWithErrorHandling
方法
扫描下方的二维码或搜索「tony老师的前端补习班」关注个人微信公众号,那么就能够第一时间收到个人最新文章。