根据调试工具看Vue源码之组件通信(一)

栏目: JavaScript · 发布时间: 5年前

内容简介:在平时的业务开发中,相信在座的各位没少用过组件通信。然而,对于一些新手/业务熟手来说,不懂技术原理往往知其然而不知其所以然,用得一脸懵逼。看完本文可以帮助你了解我们可以看到,父子组件的看完上面的代码我们知道,

根据调试 工具 看Vue源码之组件通信(一)## 根据调试工具看Vue源码之组件通信(一)

在平时的业务开发中,相信在座的各位没少用过组件通信。然而,对于一些新手/业务熟手来说,不懂技术原理往往知其然而不知其所以然,用得一脸懵逼。看完本文可以帮助你了解 Vue 组件的通信方式及原理,从而进一步加深对 Vue 的理解,远离 CV 工程师的行列。

Vue 常用的组件通信方式

  • 通过 $emit 在子组件传参给父组件,同时触发对应的父组件函数,以此达到父子组件通信的目的
  • 通过 eventbus$emit$on 方法传递数据,以此实现父子组件/兄弟组件之间的通信
  • 通过 Vuex 将页面数据划分模块,更好更方便的管理数据

父子组件通信原理

:chestnut:示例代码:

// 父组件
<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]
  1. 页面初始化时, Vue 调用 updateListeners 函数(当然,在那之前会生成虚拟 dom ,也就是 vnode

这里暂不深究),在函数里面调用 createFnInvoker 方法,给模板上的方法再套一层调用器(invoker)

target.$on
event
vm._events[event]
  • invokeWithErrorHandling 方法是怎么执行的?
  1. 判断是否有参数,然后分情况调用
  2. 处理异步函数的情况

:warning:注意:由于 Vue 会在方法上再封装一层调用器( invoker ),所以在在调用堆栈这里往往会出现两个 invokeWithErrorHandling 方法


以上所述就是小编给大家介绍的《根据调试工具看Vue源码之组件通信(一)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

iPhone开发实战

iPhone开发实战

2009-10 / 69.00元

《iPhone开发实战》全面探讨了iPhone平台的两种编程方式——Web开发和SDK编程。全书结合示例对这两种编程方式的基本流程、基本原理和基本原则给出了详细而通俗的讲解。在Web开发方面,分别介绍了三个iPhone Web库,即WebKit、iUI和Canvas,并讨论了Web开发环境Dashcode,最后阐述Web应用程序的调试。在SDK开发方面,详细描述其各种组件和功能,包括Xcode、I......一起来看看 《iPhone开发实战》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试