# 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)?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 |
| setTimeout | (Function, Number) | 與原生 setTimeout 一致。用于創(chuàng)建定時(shí)器。 | 2.14.2 |
| clearTimeout | Number | 與原生 clearTimeout 一致。用于清除定時(shí)器。 | 2.14.2 |
| getBoundingClientRect | webview: 無(wú) skyline: (rect: BoundingClientRect) => void | webview 中,返回值與 SelectorQuery 的 boundingClientRect 一致; skyline 中此調(diào)用無(wú)法同步返回,會(huì)以回調(diào)形式異步返回 | 2.14.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)
}
}
更多示例請(qǐng)查看在開(kāi)發(fā)者工具中預(yù)覽效果