【小程式探索】:深入理解小程式中的數據

  • 2019 年 12 月 16 日
  • 筆記

剛開始擼小程式的時候,覺得看看文檔就可以了,導致寫了很多垃圾程式碼坑人坑己,相信大部分初學者也不會去仔細研究文檔,更別說啰里啰嗦的指南了,在通讀小程式官方指南後,很有必要總結一番。清楚了生命周期和數據通訊,就能對整個程式有一定的把控能力,定位問題和解決問題的能力將大幅提高。

天生的延時

  • 為了解決管控與安全問題,小程式提供了一個沙箱環境來運行開發者的JavaScript 程式碼
  • 基於雙執行緒模型,意味著任何數據傳遞都是執行緒間的通訊
  • 在小程式架構里,這一切都會變成非同步
  • 非同步會使得各部分的運行時序變得複雜一些,因此邏輯層與渲染層需要有一定的機制保證時序正確
  • 這些工作在小程式框架里會處理好,開發者只需要理解生命周期,以及控制合適的時機更新UI即可
  • 本文主要理解如何控制合適的時機更新UI

如何控制合適的時機更新UI

小程式作為MVVM框架中的一員,數據驅動是核心,得數據者得天下

  • 要理解數據通訊,和生命周期、運行機制密不可分,像雙執行緒通訊模型、數據驅動、底層框架、介面渲染機制等等,本文不會展開敘述,也不可能講的比官方文檔更好、更實時
  • 本文主要理解以下幾點:(想了半天,才概括如下)
  • 1、小程式中數據的作用域
  • 2、合理操作數據,提升性能
  • 3、組件間的數據通訊
  • 4、快取數據
  • 5、擴展-狀態管理westore

在這之前,還是上幾張官方的圖,有個概念便於後續理解

明確幾點概念

  • 渲染層和數據相關
  • 邏輯層負責產生、處理數據,小程式的JS腳本運行在同一個JsCore執行緒里
  • 邏輯層和渲染層是一對多的關係,但頁面對象(page)和頁面層級(webview)一一對應

一、小程式中數據的作用域

1、全局數據

// app.js  App({    globalData: 'I am global data' // 全局共享數據  })  // 其他頁面腳本other.js  var appInstance = getApp()  console.log(appInstance.globalData) // 輸出: I am global data
  • App實例是單例的,因此不同頁面直接可以通過App實例下的屬性來共享數據

2、頁面共享數據

  • 簡單來說就是頁面所在的JS中Page構造器外定義的變數
  • 執行如下示例程式碼以驗證
console.log('載入 page.js')  var count = 0  Page({    onLoad: function() {      count += 1      console.log('第 ' + count + ' 次啟動這個頁面')    }  })
  • 你會發現小程式啟動時,列印了'載入 page.js',每次打開這個頁面,count變數會遞增,不會隨著頁面的銷毀而銷毀
  • 由於頁面所在的JS文件、app.js和所有其他被require的JS文件,在小程式啟動時自動執行並被基礎庫註冊,所以邏輯層(看作所有js的集合)只執行一次,之後都是通過Page構造器創建Page實例來渲染頁面
  • 一般require的依賴或者第三方庫JS以及getApp(),我們都會放在頁面共享的數據中

3、Page實例中的數據

  • 也就是每個Page構造器中的數據,沒錯!這就是我們每天搬磚的地方
Page({    data: { text: "我用來改變介面顯示" },    onLoad: function(options) { },    onReady: function() { },    onShow: function() { },    onHide: function() { },    onUnload: function() { },    text: "我不顯示在頁面上",    myData:{        a: '我也不顯示在頁面上',        b: true    }  })
  • 大家應該都知道data中的數據用來渲染頁面,和VUE一樣,不過VUE中只要寫this.text,而小程式中要寫this.data.text,每次寫到這個就鬱悶,其實與介面渲染無關的數據最好不要設置在data中,對性能也是大有好處

4、自定義組件中的數據

  • properties外部傳值
  • data內部數據
  • emmmmmm自定義組件有必要另開一篇總結

二、合理操作數據,提升性能

數據通訊

  • 頁面初始數據通訊:視圖層在接收到初始數據data時,進行初始渲染
  • 更新數據通訊:視圖層在接收到更新數據setData時,進行重渲染
  • 用戶事件通訊:一個用戶事件被觸發,視圖層會將資訊回饋給邏輯層
  • 一切都是2個執行緒通訊的結果,數據量小於64KB時總時長可以控制在30ms內。傳輸時間與數據量大體上呈現正相關關係,傳輸過大的數據將使這一時間顯著增加。因而減少傳輸數據量是降低數據傳輸時間的有效方式

提升性能須遵循的原則

調用setData執行重渲染時,視圖層將data和setData數據套用在WXML片段上,得到一個新節點樹,然後與當前節點樹進行比較,這樣可以得到哪些節點的哪些屬性需要更新、哪些節點需要添加或移除,最後,將setData數據合併到data中,並用新節點樹替換舊節點樹,用於下一次重渲染。

可以看出邏輯層setData發送數據給更新視圖時,需要兩個執行緒的一些通訊消耗,且不會diff數據,只會一股腦傳過去,生成新節點樹,每一次通訊都需要經過傳輸、生成、比較、合併

為了提升數據更新的性能,最好遵循以下原則:

  • 1、不要過於頻繁調用setData,應考慮將多次setData合併成一次setData調用
  • 2、數據通訊的性能與數據量正相關,每次只設置需要改變的最小單位數據
  • 3、與介面渲染無關的數據最好不要設置在data中,可以考慮設置在page對象的其他欄位下

其他優化策略:

  • 1、去掉不必要的事件綁定(WXML中的bind和catch),從而減少通訊的數據量和次數
  • 2、事件綁定時需要傳輸target和currentTarget的dataset,因而不要在節點的data前綴屬性中放置過大的數據
  • 3、精簡程式碼,降低WXML結構和JS程式碼的複雜性,必要時使用分包優化

注意:

  • 直接修改 Page實例的this.data 而不調用 this.setData 是無法改變頁面的狀態的,還會造成數據不一致
  • 不要把data中的任意一項的value設為undefined,否則可能會有引起一些不可預料的bug

三、組件間的數據通訊

組件區分業務組件和純組件

  • 業務組件與業務數據緊耦合,換一個項目可能該組件就用不上,除非非常類似的項目
  • 業務組件和頁面一樣通過 全局變數 獲得所需參數,通過更改 全局變數 與外界通訊
  • 業務組件也可以通過 props 獲得所需參數,通過 triggerEvent 與外界通訊
  • 純組件與業務數據無關,可移植和復用
  • 純組件只能通過 props 獲得所需參數,通過 triggerEvent 與外界通訊

四、快取數據

本地數據快取是小程式存儲在當前設備上硬碟上的數據,小程式宿主環境從不同小程式和不同用戶兩個維度來隔離快取空間,每個小程式的快取空間上限為10MB

快取充當全局數據

  • 通過wx.getStorage/wx.getStorageSync讀取本地快取
  • 通過wx.setStorage/wx.setStorageSync寫數據到快取

利用本地快取提前渲染介面

  • 我們在拉取商品列表後把列表存在本地快取里
  • 在onLoad發起請求前,先檢查是否有快取過列表
  • 如果有的話直接渲染介面
  • 等到wx.request的success回調之後再覆蓋本地快取重新渲染新的列表
Page({    onLoad: function() {      var that = this      var list =wx.getStorageSync("list")      if (list) { // 本地如果有快取列表,提前渲染        that.setData({          list: list        })      }      wx.request({        url: 'https://test.com/getproductlist',        success: function (res) {          if (res.statusCode === 200) {            list = res.data.list            that.setData({ // 再次渲染列表              list: list            })            wx.setStorageSync("list",list) // 覆蓋快取數據          }        }      })    }  })
  • 一般在對數據實時性/一致性要求不高的頁面採用這個方法來做提前渲染,用以優化小程式體驗

五、擴展-狀態管理westore

引用

眾所周知,小程式通過頁面或組件各自的 setData 再加上各種父子、祖孫、姐弟、姑姑與堂兄等等組件間的通訊會把程式搞成一團漿糊,如果再加上跨頁面之間的組件通訊,會讓程式非常難維護和調試。雖然市面上出現了許多技術棧編譯轉小程式的技術,但是我卻沒有戳中小程式的痛點。小程式不管從組件化、開發、調試、發布、灰度、回滾、上報、統計、監控和最近的雲能力都非常完善,小程式的工程化簡直就是前端的典範。

而開發者工具也在持續更新,可以想像的未來,組件布局的話未必需要寫程式碼了。而且據統計,開發小程式使用最多的技術棧是使用小程式本身的開發工具和語法,所以最大的痛點只剩下狀態管理和跨頁通訊

  • 現在主流的MVVM框架如vue/react/angluar都有狀態管理,小程式也可以有,由於小程式的即時特性,迭代更新非常快,所以對於小程式我是崇尚原生開發的,不過多端合一也是很nice的解決方案,自己玩的時候當然要試試dcloud公司的uniapp
  • 廢話不多說,直接貼圖和鏈接,有興趣的自行研究哈,Westore 的方案: