• 浅析mpvue的事件代理系统


    前言

    说来惭愧,用 mpvue 大半年,小程序快一年了,居然还试图用 event.stopPropagation 方法阻止事件冒泡,也是有点蠢。痛定思痛,写篇博文来认真捋一捋小程序的事件系统和 mpvue 的事件代理

    小程序事件系统,mpvue 事件系统和 DOM 事件系统的差别

    从文档得知,小程序的 event 对象和 DOM 的 event 对象是不同的,我们还可以通过把两个对象的属性打印出来进行比较:

    // 小程序 event 对象属性(8 个)
    
    ["type", "timeStamp", "target", "currentTarget", "detail", "touches", "changedTouches", "_requireActive"]
    
    
    // DOM event 对象属性 / 方法(54 个)
    
    ["isTrusted", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "shiftKey", "altKey", "metaKey", "button", "buttons", "relatedTarget", "pageX", "pageY", "x", "y", "offsetX", "offsetY", ..., "cancelable", "timeStamp", "srcElement", "returnValue", "cancelBubble", "path", "composedPath", "stopPropagation", "stopImmediatePropagation", "preventDefault", "initEvent"]
    
    // mpvue event 对象属性 / 方法(9 个)
    
    ["mp", "type", "timeStamp", "x", "y", "target", "currentTarget", "stopPropagation", "preventDefault"]
    
    
    

    我们关注一下 stopPropagationpreventDefault 这两个在网页中常用的方法。从打印得到的属性来看,在小程序中的 event 对象中没有这两个方法,取而代之的是借助 catchtap 这样的语法来阻止事件冒泡。另外,小程序中没有什么方式来阻止默认事件。

    但是在 mpvue 中,event 对象又被挂载了这两个方法。至于这两个方法是否可以真的有用,这就需要我们了解一下 mpvue 的事件代理机制。

    mpvue 的事件代理

    <cover-view class="_cover-view" id="zanIcon" catchtap="handleProxy" data-eventid="{{'4'}}" data-comkey="{{$k}}">
    
    

    上面是mpvue生成的wxml文件片段,如果你看过 mpvue 的生成文件,可能会发现,在 mpvue 生成的 wxml 文件中,所有的事件都被一个叫 handleProxy 的函数接管,在 handleProxy 进行处理后再去调用我们写的真正的事件处理函数。这个方法在initMp时,作为小程序Page构造函数的一个选项,从而可以在wxml中被正确调用。函数的定义如下:

    // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/lifecycle.js#L286-L288
    
    handleProxy (e) {
       return rootVueVM.$handleProxyWithVue(e) 
    }
    
    // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/index.js#L53-L54
    
    import { handleProxyWithVue } from './events'
    
    Vue.prototype.$handleProxyWithVue = handleProxyWithVue // handleProxyWithVue实现在 events.js 中
    

    从上面的代码可以看出,handleProxy只是将小程序的event对象传给handleProxyWithVue函数进行进一步处理。接下来我们看看handleProxyWithVue都做了什么工作:

    // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/events.js#L84-L108
    
    export function handleProxyWithVue (e) {
      const rootVueVM = this.$root
      const { type, target = {}, currentTarget } = e
      const { dataset = {}} = currentTarget || target
      const { comkey = '', eventid } = dataset
      const vm = getVM(rootVueVM, comkey.split(','))// 根据comkey找到页面对应的mpvue实例(vm)
    
    
      if (!vm) {
        return
      }
    
    
      const webEventTypes = eventTypeMap[type] || [type]
      const handles = getHandle(vm._vnode, eventid, webEventTypes) //找到真正的事件处理函数
    
      if (handles.length) {
        const event = getWebEventByMP(e) // 包装为mpvue的event对象
        if (handles.length === 1) {
          const result = handles[0](event)
          return result
        }
        handles.forEach(h => h(event))// 调用真实的事件处理函数
      }
    }
    

    handleProxyWithVue做了以下几件事情:

    1. 从根实例开始,根据comkey找出事件处理函数所在的mpvue实例(getVm)

    2. 通过遍历找到的vm的vnode,结合eventid找到小程序事件对应的真实的事件处理函数(getHandle)

    3. 将小程序的event对象包装为mpvue的event对象(getWebEventByMP),并添加了preventDefault和stopPropagation方法

    4. 将包装后的event对象传给真实的处理函数进行调用

    这就解释了生成的wxml中绑定事件的节点都有event-comkeyevent-eventid属性,在一个事件触发时,它们是被用来寻找事件对应的vm实例和对应的事件处理函数的。

    最后我们来看看getWebEventByMP函数:

    // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/events.js#L62-L82
    
    function getWebEventByMP (e) {
      const { type, timeStamp, touches, detail = {}, target = {}, currentTarget = {}} = e
      const { x, y } = detail
      const event = {
        mp: e,
        type,
        timeStamp,
        x,
        y,
        target: Object.assign({}, target, detail),
        currentTarget,
        stopPropagation: noop,
        preventDefault: noop
      }
    
    
      if (touches && touches.length) {
        Object.assign(event, touches[0])
        event.touches = touches
      }
      return event
    }
    

    从上面代码可以看出,stopPropagation和preventDefault都对应到一个叫noop的函数,而这个函数是一个空函数,也就是说在mpvue中,尽管可以调用event.stopPropagation(),但是并没有什么用,还是要借助.stop修饰符通过compiler编译成catchEvent来阻止冒泡。(完)

  • 相关阅读:
    IO流-----写到输出流
    MyBatis中collection (一对一,一对多)
    POI导出Excel并下载
    篇二:MySQL存储过程
    篇三:访问JSON静态文件
    ajax同步处理(使得JS按顺序执行)
    篇二:JSON解析
    篇一:MySQL中case when then
    乱码问题
    解决spring配置中的bean类型的问题:BeanNotOfRequiredTypeException
  • 原文地址:https://www.cnblogs.com/imgss/p/mpvue-handleProxy.html
Copyright © 2020-2023  润新知