小程序事件系统

133 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

什么是事件

  • 事件是视图层到逻辑层的通讯方式
  • 事件可以将用户的行为反馈到逻辑层进行处理
  • 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数
  • 事件对象可以携带额外信息,如id,dataset,touches.

事件的使用方式

  • 在组件中绑定一个事件处理函数。
    如:bindtap,当用户点击该组件的时候会在该页面对应的page中找到相应的事件处理函数。

image.png

  • 在相应的page定义中写上相应的事件处理函数,参数是event

image.png

  • 可以看出log出来的信息大致如下:

image.png

使用wxs函数响应事件

wxs函数接收2个参数,第一个是event,在原有的event的基础上加了event.instanse(实例)对象,第二个参数是ownerInstance,和event.instance一样是一个ComponentDescript(批量组件定义)对象。具体使用如下:

  • 在组件中绑定和注册事件处理的wxs函数

image.png
注意:绑定的wxs函数必须使用{{}}括起来

  • test.wxs文件实现tapName函数

image.png
ownweInstanse包含了一些方法,可以设置组件的样式和class。

事件分类

事件分为冒泡事件和非冒泡事件:

  1. 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
  2. 非冒泡事件:当一个组件上的事件触发后,该事件不会向父节点传递。
wxml的冒泡事件列表

image.png

image.png
注意:除了上图以外的其他组件自定义事件如无特殊声明都是非冒泡事件,如form的submit事件,input的input事件,scroll-view的scroll事件等。

普通事件绑定

事件绑定的写法类似于组件的属性

image.png
事件绑定函数也可以是一个数据绑定:

image.png
此时,也没按的this.data.handlerName必须是一个字符串,指定事件处理函数名,如果她是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)

绑定并阻止事件冒泡

除bind外,也可以用catch来绑定事件,与bind不同,catch会阻止事件向上冒泡。
如下图所示,点击inner view会先后调用handleTap3和handleTap2(因为tap事件会冒泡到middle view,而middle view阻止了tap事件冒泡,不再向父节点传递),点击middle view会触发handleTap2,点击outer view会触发handleTap1。

互斥事件绑定

基础库版本2.8.2起。除了bind和catch外,还可以使用mut-bind来绑定事件。一个mut-bind触发后,如果事件冒泡到其他节点上,其他节点上的mut-bind绑定函数不会被触发,但bind绑定函数和catch绑定函数依旧会被触发。(因为mut-bind是“互斥”的,只会有其中一个绑定函数被触发)

image.png

事件的捕获阶段

触摸类事件支持捕获阶段,捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用capture-bind、capture-catch关键字。后者将中断捕获阶段和取消冒泡阶段。
如下图所示,点击inner view会先后调用handleTap2、handleTap4、handleTap3、handleTap1

image.png
如果将上面代码中的第一个capture改为capture-catch,将只触发handleTap2

image.png

事件对象

触发事件的处理函数接收event的参数,也就是事件对象。 image.png image.png
事件对象的属性
BaseEvent 基础事件对象属性列表: image.png

  • type:事件类型
  • timeStamp:事件生成时的时间戳
  • target:触发事件的组件的一些属性值集合
    • id:事件源组件上的id属性
    • dataset:事件源组件上由data-开头的自定义属性组成的集合
  • currentTarget:事件绑定的当前组件(由于事件冒泡的存在,类似于this和target的区别。)
    • id:当前组件的id
    • dataset:当前组件上由data-开头的自定义属性组成的集合。
      下面的两个图片完美的说明了target和currentTarget的区别:

1668752829882.png

image.png

  • mark:可以使用mark来识别具体触发事件的target节点。mark还可以用于承载一些自定义数据,类似于dataset当事件触发时,事件冒泡路径上所有的 mark 会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark 。)(如果是事件冒泡,绑定了好多处理函数,都会合并到mark里面,标记的属性名必须是mark:mark名

image.png
mark 和 dataset 很相似,主要区别在于: mark 会包含从触发事件的节点到根节点上所有的 mark: 属性值;而 dataset 仅包含一个节点的 data- 属性值。

细节注意事项:

  如果存在同名的 `mark` ,父节点的 `mark` 会被子节点覆盖。
 在自定义组件中接收事件时, `mark` 不包含自定义组件外的节点的 `mark` 。
 不同于 `dataset` ,节点的 `mark` 不会做连字符和大小写转换。

CustomEvent 自定义事件对象属性列表(继承 BaseEvent):

  • datail:自定义事件,额外的信息

TouchEvent 触摸事件对象属性列表(继承 BaseEvent):

  • touches:触摸事件,当前停留在屏幕中的触摸点信息的数组

image.png

  • changedTouches:触摸事件,当前变化的触摸点信息的数组 changedTouches 数据格式同 touches。 表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)

wxs响应事件

有频繁用户交互的效果在小程序上表现是比较卡顿的,例如页面有 2 个元素 A 和 B,用户在 A 上做 touchmove 手势,要求 B 也跟随移动,[movable-view]就是一个典型的例子。一次 touchmove 事件的响应过程为:

a、touchmove 事件从视图层(Webview)抛到逻辑层(App Service)

b、逻辑层(App Service)处理 touchmove 事件,再通过 setData 来改变 B 的位置

一次 touchmove 的响应需要经过 2 次的逻辑层和渲染层的通信以及一次渲染,通信的耗时比较大。此外 setData 渲染也会阻塞其它脚本执行,导致了整个用户交互的动画过程会有延迟。

如何解决

基本思路是减少通信的次数,让时间在视图层响应。小程序的框架分为视图层(wxs是视图层的)和逻辑层(js是逻辑层),这样分层的目的是管控,开发者的代码只能运行在逻辑层(App Service),而这个思路就必须要让开发者的代码运行在视图层(Webview),如下图所示的流程:
在webview上注册touchmove事件,APPservice上setData触发wxsPropsO不server,然后在webview上触发wxs函数进行初始化,用户触发touchmove事件

image.png
使用 WXS 函数用来响应小程序事件,目前只能响应内置组件的事件,不支持自定义组件事件。WXS 函数的除了纯逻辑的运算,还可以通过封装好的ComponentDescriptor 实例来访问以及设置组件的 class 和样式,对于交互动画,设置 style 和 class 足够了。
通过ownerInstance.selectComponent获取当前组件实例(页面的某个组件)
setStyle设置组件样式 image.png image.png 只有wxs有第二个参数ownerInstance image.png

image.png
其中入参 event 是小程序事件对象基础上多了 event.instance 来表示触发事件的组件的 ComponentDescriptor 实例。ownerInstance 表示的是触发事件的组件所在的组件的 ComponentDescriptor 实例,如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。
ComponentDescriptor的定义如下: image.png

wxs与js互相调用的方法

WXS 运行在视图层(Webview),里面的逻辑毕竟能做的事件比较少,需要有一个机制和逻辑层(App Service)开发者的代码通信,上面的 callMethod 是 WXS 里面调用逻辑层(App Service)开发者的代码的方法,而 WxsPropObserver 是逻辑层(App Service)开发者的代码调用 WXS 逻辑的机制。

wxs调用js的方法: image.png

image.png image.png
js调用wxs的方法:
change:prop(属性前面带change前缀)它的值是wxs的函数,(wxs的函数要用{{}}来括起来)绑定的就是props这个属性,这个属性的值是data中的状态,当通过setData调用修改之后就会触发。

image.png image.png

image.png
也可以直接使用instance image.png