依据调试东西看Vue源码之组件通讯(一)## 依据调试东西看Vue源码之组件通讯(一)
在日常平凡的营业开辟中,置信在坐的列位没少用过组件通讯。然则,关于一些新手/营业熟手来讲,不懂手艺道理每每知其然则不知其所以然,用得一脸懵逼。看完本文能够协助你相识
Vue
组件的通讯体式格局及道理,从而进一步加深对
Vue
的明白,阔别
CV
工程师的行列。
Vue
经常运用的组件通讯体式格局
- 经由过程
$emit
在子组件传参给父组件,同时触发对应的父组件函数,以此到达父子组件通讯的目标 - 经由过程
eventbus
的$emit
和$on
要领通报数据,以此完成父子组件/兄弟组件之间的通讯 - 经由过程
Vuex
将页面数据分别模块,更好更轻易的治理数据
父子组件通讯道理
🌰示例代码:
// 父组件
<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
。
运转顺序,进入第一个断点
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
上拿到的,接下来我们能够带着这几个疑问继承往下看:
-
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
上,革新页面后断点停在这里,步进这个函数:
function add (event, fn) {
target.$on(event, fn);
}
明显target
是全局变量,然则这里先不穷究。再次步进以后能够看到断点停在这里:
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先生的前端补习班」关注我的微信民众号,那末就能够第一时间收到我的最新文章。