您的位置:首页 > Web前端 > Vue.js

浅析mpvue的事件代理系统

2018-10-28 12:56 483 查看

前言

说来惭愧,用 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"]

我们关注一下

stopPropagation
preventDefault
这两个在网页中常用的方法。从打印得到的属性来看,在小程序中的 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-comkey
event-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
来阻止冒泡。(完)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: