微信小程序 事件系統(tǒng)

2022-05-11 15:25 更新

事件

什么是事件

  • 事件是視圖層到邏輯層的通訊方式。
  • 事件可以將用戶(hù)的行為反饋到邏輯層進(jìn)行處理。
  • 事件可以綁定在組件上,當(dāng)達(dá)到觸發(fā)事件,就會(huì)執(zhí)行邏輯層中對(duì)應(yīng)的事件處理函數(shù)。
  • 事件對(duì)象可以攜帶額外信息,如 id, dataset, touches。

事件的使用方式

  • 在組件中綁定一個(gè)事件處理函數(shù)。

如bindtap,當(dāng)用戶(hù)點(diǎn)擊該組件的時(shí)候會(huì)在該頁(yè)面對(duì)應(yīng)的Page中找到相應(yīng)的事件處理函數(shù)。

<button id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </button>
  • 在相應(yīng)的Page定義中寫(xiě)上相應(yīng)的事件處理函數(shù),參數(shù)是event。
Page({
  tapName: function(event) {
    console.log(event)
  }
})
  • 可以看到log出來(lái)的信息大致如下:
{
  "type":"tap",
  "timeStamp":895,
  "target": {
    "id": "tapTest",
    "dataset":  {
      "hi":"WeChat"
    }
  },
  "currentTarget":  {
    "id": "tapTest",
    "dataset": {
      "hi":"WeChat"
    }
  },
  "detail": {
    "x":53,
    "y":14
  },
  "touches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
  }],
  "changedTouches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
  }]
}

使用WXS函數(shù)響應(yīng)事件

基礎(chǔ)庫(kù) 2.4.4 開(kāi)始支持,低版本需做兼容處理

從基礎(chǔ)庫(kù)版本2.4.4開(kāi)始,支持使用WXS函數(shù)綁定事件,WXS函數(shù)接受2個(gè)參數(shù),第一個(gè)是event,在原有的event的基礎(chǔ)上加了event.instance對(duì)象,第二個(gè)參數(shù)是ownerInstance,和event.instance一樣是一個(gè)ComponentDescriptor對(duì)象。具體使用如下:

  • 在組件中綁定和注冊(cè)事件處理的WXS函數(shù)。
<wxs module="wxs" src="./test.wxs"></wxs>
<view id="tapTest" data-hi="WeChat" bindtap="{{wxs.tapName}}"> Click me! </view>
**注:綁定的WXS函數(shù)必須用{{}}括起來(lái)**

  • test.wxs文件實(shí)現(xiàn)tapName函數(shù)
function tapName(event, ownerInstance) {
  console.log('tap wechat', JSON.stringify(event))
}
module.exports = {
  tapName: tapName
}

ownerInstance包含了一些方法,可以設(shè)置組件的樣式和class,具體包含的方法以及為什么要用WXS函數(shù)響應(yīng)事件。

事件詳解

事件分類(lèi)

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

  1. 冒泡事件:當(dāng)一個(gè)組件上的事件被觸發(fā)后,該事件會(huì)向父節(jié)點(diǎn)傳遞。
  2. 非冒泡事件:當(dāng)一個(gè)組件上的事件被觸發(fā)后,該事件不會(huì)向父節(jié)點(diǎn)傳遞。

WXML的冒泡事件列表:

類(lèi)型 觸發(fā)條件 最低版本
touchstart 手指觸摸動(dòng)作開(kāi)始
touchmove 手指觸摸后移動(dòng)
touchcancel 手指觸摸動(dòng)作被打斷,如來(lái)電提醒,彈窗
touchend 手指觸摸動(dòng)作結(jié)束
tap 手指觸摸后馬上離開(kāi)
longpress 手指觸摸后,超過(guò)350ms再離開(kāi),如果指定了事件回調(diào)函數(shù)并觸發(fā)了這個(gè)事件,tap事件將不被觸發(fā) 1.5.0
longtap 手指觸摸后,超過(guò)350ms再離開(kāi)(推薦使用longpress事件代替)
transitionend 會(huì)在 WXSS transition 或 wx.createAnimation 動(dòng)畫(huà)結(jié)束后觸發(fā)
animationstart 會(huì)在一個(gè) WXSS animation 動(dòng)畫(huà)開(kāi)始時(shí)觸發(fā)
animationiteration 會(huì)在一個(gè) WXSS animation 一次迭代結(jié)束時(shí)觸發(fā)
animationend 會(huì)在一個(gè) WXSS animation 動(dòng)畫(huà)完成時(shí)觸發(fā)
touchforcechange 在支持 3D Touch 的 iPhone 設(shè)備,重按時(shí)會(huì)觸發(fā) 1.9.90

注:除上表之外的其他組件自定義事件如無(wú)特殊聲明都是非冒泡事件,如 form 的submit事件,input 的input事件,scroll-view 的scroll事件,(詳見(jiàn)各個(gè)組件)

普通事件綁定

事件綁定的寫(xiě)法類(lèi)似于組件的屬性,如:

<view bindtap="handleTap">
    Click here!
</view>

如果用戶(hù)點(diǎn)擊這個(gè) view ,則頁(yè)面的 handleTap 會(huì)被調(diào)用。

事件綁定函數(shù)可以是一個(gè)數(shù)據(jù)綁定,如:

<view bindtap="{{ handlerName }}">
    Click here!
</view>

此時(shí),頁(yè)面的 this.data.handlerName 必須是一個(gè)字符串,指定事件處理函數(shù)名;如果它是個(gè)空字符串,則這個(gè)綁定會(huì)失效(可以利用這個(gè)特性來(lái)暫時(shí)禁用一些事件)。

自基礎(chǔ)庫(kù)版本 1.5.0 起,在大多數(shù)組件和自定義組件中, bind 后可以緊跟一個(gè)冒號(hào),其含義不變,如 bind:tap ?;A(chǔ)庫(kù)版本 2.8.1 起,在所有組件中開(kāi)始提供這個(gè)支持。

綁定并阻止事件冒泡

除 bind 外,也可以用 catch 來(lái)綁定事件。與 bind 不同, catch 會(huì)阻止事件向上冒泡。

例如在下邊這個(gè)例子中,點(diǎn)擊 inner view 會(huì)先后調(diào)用handleTap3和handleTap2(因?yàn)閠ap事件會(huì)冒泡到 middle view,而 middle view 阻止了 tap 事件冒泡,不再向父節(jié)點(diǎn)傳遞),點(diǎn)擊 middle view 會(huì)觸發(fā)handleTap2,點(diǎn)擊 outer view 會(huì)觸發(fā)handleTap1。

<view id="outer" bindtap="handleTap1">
  outer view
  <view id="middle" catchtap="handleTap2">
    middle view
    <view id="inner" bindtap="handleTap3">
      inner view
    </view>
  </view>
</view>

互斥事件綁定

自基礎(chǔ)庫(kù)版本 2.8.2 起,除 bind 和 catch 外,還可以使用 mut-bind 來(lái)綁定事件。一個(gè) mut-bind 觸發(fā)后,如果事件冒泡到其他節(jié)點(diǎn)上,其他節(jié)點(diǎn)上的 mut-bind 綁定函數(shù)不會(huì)被觸發(fā),但 bind 綁定函數(shù)和 catch 綁定函數(shù)依舊會(huì)被觸發(fā)。

換而言之,所有 mut-bind 是“互斥”的,只會(huì)有其中一個(gè)綁定函數(shù)被觸發(fā)。同時(shí),它完全不影響 bind 和 catch 的綁定效果。

例如在下邊這個(gè)例子中,點(diǎn)擊 inner view 會(huì)先后調(diào)用 handleTap3 和 handleTap2 ,點(diǎn)擊 middle view 會(huì)調(diào)用 handleTap2 和 handleTap1 。

<view id="outer" mut-bind:tap="handleTap1">
  outer view
  <view id="middle" bindtap="handleTap2">
    middle view
    <view id="inner" mut-bind:tap="handleTap3">
      inner view
    </view>
  </view>
</view>

事件的捕獲階段

自基礎(chǔ)庫(kù)版本 1.5.0 起,觸摸類(lèi)事件支持捕獲階段。捕獲階段位于冒泡階段之前,且在捕獲階段中,事件到達(dá)節(jié)點(diǎn)的順序與冒泡階段恰好相反。需要在捕獲階段監(jiān)聽(tīng)事件時(shí),可以采用capture-bind、capture-catch關(guān)鍵字,后者將中斷捕獲階段和取消冒泡階段。

在下面的代碼中,點(diǎn)擊 inner view 會(huì)先后調(diào)用handleTap2、handleTap4、handleTap3、handleTap1。

<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

如果將上面代碼中的第一個(gè)capture-bind改為capture-catch,將只觸發(fā)handleTap2。

<view id="outer" bind:touchstart="handleTap1" capture-catch:touchstart="handleTap2">
  outer view
  <view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
    inner view
  </view>
</view>

事件對(duì)象

如無(wú)特殊說(shuō)明,當(dāng)組件觸發(fā)事件時(shí),邏輯層綁定該事件的處理函數(shù)會(huì)收到一個(gè)事件對(duì)象。

BaseEvent 基礎(chǔ)事件對(duì)象屬性列表:

屬性 類(lèi)型 說(shuō)明 基礎(chǔ)庫(kù)版本
type String 事件類(lèi)型
timeStamp Integer 事件生成時(shí)的時(shí)間戳
target Object 觸發(fā)事件的組件的一些屬性值集合
currentTarget Object 當(dāng)前組件的一些屬性值集合
mark Object 事件標(biāo)記數(shù)據(jù) 2.7.1

CustomEvent 自定義事件對(duì)象屬性列表(繼承 BaseEvent):

屬性 類(lèi)型 說(shuō)明
detail Object 額外的信息

TouchEvent 觸摸事件對(duì)象屬性列表(繼承 BaseEvent):

屬性 類(lèi)型 說(shuō)明
touches Array 觸摸事件,當(dāng)前停留在屏幕中的觸摸點(diǎn)信息的數(shù)組
changedTouches Array 觸摸事件,當(dāng)前變化的觸摸點(diǎn)信息的數(shù)組

特殊事件: canvas 中的觸摸事件不可冒泡,所以沒(méi)有 currentTarget。

type

代表事件的類(lèi)型。

timeStamp

頁(yè)面打開(kāi)到觸發(fā)事件所經(jīng)過(guò)的毫秒數(shù)。

target

觸發(fā)事件的源組件。

屬性 類(lèi)型 說(shuō)明
id String 事件源組件的id
dataset Object 事件源組件上由data-開(kāi)頭的自定義屬性組成的集合

currentTarget

事件綁定的當(dāng)前組件。

屬性 類(lèi)型 說(shuō)明
id String 當(dāng)前組件的id
dataset Object 當(dāng)前組件上由data-開(kāi)頭的自定義屬性組成的集合

說(shuō)明: target 和 currentTarget 可以參考上例中,點(diǎn)擊 inner view 時(shí),handleTap3 收到的事件對(duì)象 target 和 currentTarget 都是 inner,而 handleTap2 收到的事件對(duì)象 target 就是 inner,currentTarget 就是 middle。

dataset

在組件節(jié)點(diǎn)中可以附加一些自定義數(shù)據(jù)。這樣,在事件中可以獲取這些自定義的節(jié)點(diǎn)數(shù)據(jù),用于事件的邏輯處理。

在 WXML 中,這些自定義數(shù)據(jù)以 data- 開(kāi)頭,多個(gè)單詞由連字符 - 連接。這種寫(xiě)法中,連字符寫(xiě)法會(huì)轉(zhuǎn)換成駝峰寫(xiě)法,而大寫(xiě)字符會(huì)自動(dòng)轉(zhuǎn)成小寫(xiě)字符。如:

  • data-element-type ,最終會(huì)呈現(xiàn)為 event.currentTarget.dataset.elementType ;
  • data-elementType ,最終會(huì)呈現(xiàn)為 event.currentTarget.dataset.elementtype 。

示例:

<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({
  bindViewTap:function(event){
    event.currentTarget.dataset.alphaBeta === 1 // - 會(huì)轉(zhuǎn)為駝峰寫(xiě)法
    event.currentTarget.dataset.alphabeta === 2 // 大寫(xiě)會(huì)轉(zhuǎn)為小寫(xiě)
  }
})

mark

在基礎(chǔ)庫(kù)版本 2.7.1 以上,可以使用 mark 來(lái)識(shí)別具體觸發(fā)事件的 target 節(jié)點(diǎn)。此外, mark 還可以用于承載一些自定義數(shù)據(jù)(類(lèi)似于 dataset )。

當(dāng)事件觸發(fā)時(shí),事件冒泡路徑上所有的 mark 會(huì)被合并,并返回給事件回調(diào)函數(shù)。(即使事件不是冒泡事件,也會(huì) mark 。)

代碼示例:

<view mark:myMark="last" bindtap="bindViewTap">
  <button mark:anotherMark="leaf" bindtap="bindButtonTap">按鈕</button>
</view>

在上述 WXML 中,如果按鈕被點(diǎn)擊,將觸發(fā) bindViewTap 和 bindButtonTap 兩個(gè)事件,事件攜帶的 event.mark 將包含 myMark 和 anotherMark 兩項(xiàng)。

Page({
  bindViewTap: function(e) {
    e.mark.myMark === "last" // true
    e.mark.anotherMark === "leaf" // true
  }
})

mark 和 dataset 很相似,主要區(qū)別在于: mark 會(huì)包含從觸發(fā)事件的節(jié)點(diǎn)到根節(jié)點(diǎn)上所有的 mark: 屬性值;而 dataset 僅包含一個(gè)節(jié)點(diǎn)的 data- 屬性值。

細(xì)節(jié)注意事項(xiàng):

  • 如果存在同名的 mark ,父節(jié)點(diǎn)的 mark 會(huì)被子節(jié)點(diǎn)覆蓋。
  • 在自定義組件中接收事件時(shí), mark 不包含自定義組件外的節(jié)點(diǎn)的 mark 。
  • 不同于 dataset ,節(jié)點(diǎn)的 mark 不會(huì)做連字符和大小寫(xiě)轉(zhuǎn)換。

touches

touches 是一個(gè)數(shù)組,每個(gè)元素為一個(gè) Touch 對(duì)象(canvas 觸摸事件中攜帶的 touches 是 CanvasTouch 數(shù)組)。 表示當(dāng)前停留在屏幕上的觸摸點(diǎn)。

Touch 對(duì)象

屬性 類(lèi)型 說(shuō)明
identifier Number 觸摸點(diǎn)的標(biāo)識(shí)符
pageX, pageY Number 距離文檔左上角的距離,文檔的左上角為原點(diǎn) ,橫向?yàn)閄軸,縱向?yàn)閅軸
clientX, clientY Number 距離頁(yè)面可顯示區(qū)域(屏幕除去導(dǎo)航條)左上角距離,橫向?yàn)閄軸,縱向?yàn)閅軸

CanvasTouch 對(duì)象

屬性 類(lèi)型 說(shuō)明 特殊說(shuō)明
identifier Number 觸摸點(diǎn)的標(biāo)識(shí)符
x, y Number 距離 Canvas 左上角的距離,Canvas 的左上角為原點(diǎn) ,橫向?yàn)閄軸,縱向?yàn)閅軸

changedTouches

changedTouches 數(shù)據(jù)格式同 touches。 表示有變化的觸摸點(diǎn),如從無(wú)變有(touchstart),位置變化(touchmove),從有變無(wú)(touchend、touchcancel)。

detail

自定義事件所攜帶的數(shù)據(jù),如表單組件的提交事件會(huì)攜帶用戶(hù)的輸入,媒體的錯(cuò)誤事件會(huì)攜帶錯(cuò)誤信息,詳見(jiàn)組件定義中各個(gè)事件的定義。

點(diǎn)擊事件的detail 帶有的 x, y 同 pageX, pageY 代表距離文檔左上角的距離。


WXS響應(yīng)事件

基礎(chǔ)庫(kù) 2.4.4 開(kāi)始支持,低版本需做兼容處理

背景

有頻繁用戶(hù)交互的效果在小程序上表現(xiàn)是比較卡頓的,例如頁(yè)面有 2 個(gè)元素 A 和 B,用戶(hù)在 A 上做 touchmove 手勢(shì),要求 B 也跟隨移動(dòng),movable-view 就是一個(gè)典型的例子。一次 touchmove 事件的響應(yīng)過(guò)程為:

a、touchmove 事件從視圖層(Webview)拋到邏輯層(App Service)

b、邏輯層(App Service)處理 touchmove 事件,再通過(guò) setData 來(lái)改變 B 的位置

一次 touchmove 的響應(yīng)需要經(jīng)過(guò) 2 次的邏輯層和渲染層的通信以及一次渲染,通信的耗時(shí)比較大。此外 setData 渲染也會(huì)阻塞其它腳本執(zhí)行,導(dǎo)致了整個(gè)用戶(hù)交互的動(dòng)畫(huà)過(guò)程會(huì)有延遲。

實(shí)現(xiàn)方案

本方案基本的思路是減少通信的次數(shù),讓事件在視圖層(Webview)響應(yīng)。小程序的框架分為視圖層(Webview)和邏輯層(App Service),這樣分層的目的是管控,開(kāi)發(fā)者的代碼只能運(yùn)行在邏輯層(App Service),而這個(gè)思路就必須要讓開(kāi)發(fā)者的代碼運(yùn)行在視圖層(Webview),如下圖所示的流程:

使用 WXS 函數(shù)用來(lái)響應(yīng)小程序事件,目前只能響應(yīng)內(nèi)置組件的事件,不支持自定義組件事件。WXS 函數(shù)的除了純邏輯的運(yùn)算,還可以通過(guò)封裝好的ComponentDescriptor 實(shí)例來(lái)訪問(wèn)以及設(shè)置組件的 class 和樣式,對(duì)于交互動(dòng)畫(huà),設(shè)置 style 和 class 足夠了。WXS 函數(shù)的例子如下:

var wxsFunction = function(event, ownerInstance) {
    var instance = ownerInstance.selectComponent('.classSelector') // 返回組件的實(shí)例
    instance.setStyle({
        "font-size": "14px" // 支持rpx
    })
    instance.getDataset()
    instance.setClass(className)
    // ...
    return false // 不往上冒泡,相當(dāng)于調(diào)用了同時(shí)調(diào)用了stopPropagation和preventDefault
}

其中入?yún)?nbsp;event 是小程序事件對(duì)象基礎(chǔ)上多了 event.instance 來(lái)表示觸發(fā)事件的組件的 ComponentDescriptor 實(shí)例。ownerInstance 表示的是觸發(fā)事件的組件所在的組件的 ComponentDescriptor 實(shí)例,如果觸發(fā)事件的組件是在頁(yè)面內(nèi)的,ownerInstance 表示的是頁(yè)面實(shí)例。

ComponentDescriptor的定義如下:

方法 參數(shù) 描述 最低版本
selectComponent selector對(duì)象 返回組件的 ComponentDescriptor 實(shí)例。
selectAllComponents selector對(duì)象數(shù)組 返回組件的 ComponentDescriptor 實(shí)例數(shù)組。
setStyle Object/string 設(shè)置組件樣式,支持rpx。設(shè)置的樣式優(yōu)先級(jí)比組件 wxml 里面定義的樣式高。不能設(shè)置最頂層頁(yè)面的樣式。
addClass/removeClass/ hasClass string 設(shè)置組件的 class。設(shè)置的 class 優(yōu)先級(jí)比組件 wxml 里面定義的 class 高。不能設(shè)置最頂層頁(yè)面的 class。
getDataset 無(wú) 返回當(dāng)前組件/頁(yè)面的 dataset 對(duì)象
callMethod (funcName:string, args:object) 調(diào)用當(dāng)前組件/頁(yè)面在邏輯層(App Service)定義的函數(shù)。funcName表示函數(shù)名稱(chēng),args表示函數(shù)的參數(shù)。
requestAnimationFrame Function 和原生 requestAnimationFrame 一樣。用于設(shè)置動(dòng)畫(huà)。
getState 無(wú) 返回一個(gè)object對(duì)象,當(dāng)有局部變量需要存儲(chǔ)起來(lái)后續(xù)使用的時(shí)候用這個(gè)方法。
triggerEvent (eventName, detail) 和組件的triggerEvent一致。
getComputedStyle Array.<string> 參數(shù)與 SelectorQuery 的 computedStyle 一致。 2.11.2

WXS 運(yùn)行在視圖層(Webview),里面的邏輯畢竟能做的事件比較少,需要有一個(gè)機(jī)制和邏輯層(App Service)開(kāi)發(fā)者的代碼通信,上面的 callMethod 是 WXS 里面調(diào)用邏輯層(App Service)開(kāi)發(fā)者的代碼的方法,而 WxsPropObserver 是邏輯層(App Service)開(kāi)發(fā)者的代碼調(diào)用 WXS 邏輯的機(jī)制。

使用方法

  • WXML定義事件:
<wxs module="test" src="./test.wxs"></wxs>
<view change:prop="{{test.propObserver}}" prop="{{propValue}}" bindtouchmove="{{test.touchmove}}" class="movable"></view>

上面的change:prop(屬性前面帶change:前綴)是在 prop 屬性被設(shè)置的時(shí)候觸發(fā) WXS 函數(shù),值必須用{{}}括起來(lái)。類(lèi)似 Component 定義的 properties 里面的 observer 屬性,在setData({propValue: newValue})調(diào)用之后會(huì)觸發(fā)。

注意:WXS函數(shù)必須用{{}}括起來(lái)。當(dāng) prop 的值被設(shè)置 WXS 函數(shù)就會(huì)觸發(fā),而不只是值發(fā)生改變,所以在頁(yè)面初始化的時(shí)候會(huì)調(diào)用一次WxsPropObserver的函數(shù)。

  • WXS文件test.wxs里面定義并導(dǎo)出事件處理函數(shù)和屬性改變觸發(fā)的函數(shù):
module.exports = {
    touchmove: function(event, instance) {
        console.log('log event', JSON.stringify(event))
    },
    propObserver: function(newValue, oldValue, ownerInstance, instance) {
        console.log('prop observer', newValue, oldValue)
    }
}

提示:

  1. 目前還不支持原生組件的事件、input和textarea組件的 bindinput 事件
  2. 1.02.1901170及以后版本的開(kāi)發(fā)者工具上支持交互動(dòng)畫(huà),最低版本基礎(chǔ)庫(kù)是2.4.4
  3. 目前在WXS函數(shù)里面僅支持console.log方式打日志定位問(wèn)題,注意連續(xù)的重復(fù)日志會(huì)被過(guò)濾掉。


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)