1. 程式人生 > >在mpvue中使用map如何避坑

在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>

元素,在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。(完)