在mpvue中使用map如何避坑
最近在做一個需求,當用戶放大地圖到某個級別時,自動顯示marker
的callout標籤,當小於這個縮放級別時,則隱藏callout。然而在我實現的過程中,卻發現一個嚴重的問題:當我操作marker資料時,會導致地圖的縮放級別發生變化(使用者沒有縮放的操作)。這TM是什麼鬼??接下來就開始爬坑。
官方的避坑指南
在mpvue的文件中,官方是給出一些避坑指南的:
列表中沒有的原生事件也可以使用例如 bindregionchange 事件直接在 dom 上將bind改為@,同時這個事件也非常特殊,它的 event type 有 begin 和 end 兩個,導致我們無法在handleProxy 中區分到底是什麼事件,所以你在監聽此類事件的時候同時監聽事件名和事件型別既 <map @regionchange="functionName" @end="functionName" @begin="functionName"><map>
如果你發現@regionchange
沒有觸發,十有八九是掉到這個坑裡面了。
map元件類似一個特殊的表單元素
然而上面的指南跟我遇到的問題沒什麼關係,我們還是要繼續分析。map元件的操作很多,比如拖動,縮放,點選等。當用戶進行拖動和縮放操作時,都會觸發regionchange
事件,如果我們綁定了scale
,latitude
或者longitude
屬性,像下面這樣:
<map id="map" :markers="markers" :scale="scale" :latitude="latitude" :longitude="longitude" @callouttap="goToClass" @end="regionchange" @begin="regionchange" @regionchange="regionchange" show-location style="width: 100%; height: 100vh">
1.當我們綁定了這些屬性(scale,latitude,longitude)等,2.使用者進行了縮放操作,3.再去進行資料操作,就會出現上述bug。
將上訴三個條件合起來分析可以得出,這是由於mpvue和小程式的資料沒有保持一致引起的。當用戶進行了縮放和移動操作時,其實scale資料已經更新,然而vm中的資料並沒有相應的更新,在再次進行資料操作時,會導致舊的scale資料覆蓋小程式的scale,發生改動的是marker資料,縮放級別也被更新了的bug。
所以在使用者縮放操作時,需要監聽相應事件,手動更新vm中的相應的資料,來維持vm和小程式中的資料同步。不然會造成mpvue例項的資料和小程式的實際縮放資料不一致。換句話說,map元件可以類比<input>
:value
來設定資料,通過@input
來更新vm的資料,從而保證vm中的資料和DOM中元素資料的一致性。所以在map中也一樣,也要在regionchange中更新vm的資料來保證資料的一致性:
methods: {
regionchange: (e) => {
this.ctx.getScale({// this.ctx是MapContext物件的引用 https://developers.weixin.qq.com/miniprogram/dev/api/map/MapContext.html
success: (res) => {
this.scale = res.scale
}
})
this.ctx.getCenterLocation({
success: (res) => {
this.latitude = res.latitude
this.longitude = res.longitude
}
})
}
}
通過上面的程式碼,保證了vm資料和小程式的資料同步,避免了操作marker資料時,將舊的longitude,scale,latitude資料傳給小程式,造成在使用者沒有縮放操作的情況下地圖被縮放。問題是解決了,但是為什麼我改變的是markers的資料,mpvue會將longitude這個資料也一起傳給小程式呢,咱們繼續
mpvue的setData設計
Taro 1.0釋出時,提到了小程式 setState 效能優化:
在 setData 之前進行了一次資料 Diff,找到資料的最小更新路徑,然後再使用此路徑來進行更新
這個其實也是mpvue的一大痛點,我們來看一下mpvue的相關原始碼:
export function updateDataToMP () {
const page = getPage(this)
if (!page) {
return
}
const data = formatVmData(this)
throttleSetData(page.setData.bind(page), data)
}
上面的程式碼中,通過page.setData
,更新vm的data資料,並用throttleSetData
進行50ms一次的節流。然而,第二個引數data依然是將vm中的全部資料傳給setData,而沒有檢測究竟data中的哪些欄位真正發生了變化。
綜合起來講,vm和小程式資料沒有保持統一,和mpvue setData前沒有diff一次找出真正需要更新的資料,這兩個因素共同造成了上面的bug。(完)