先簡單介紹一下項目,就是一個比較常規的點餐小程式。
介面如圖:
左邊是分類選單,右邊是長列表,有多個分類的商品,單一分類滾動完後可以繼續滾動切換到下一個分類,同時左邊的分類選單選取態會跟著切換到目前商品清單顯示的分類。
考慮到更好的用戶體驗,以及參考了美團等點餐小程序,這個商品列表的數據是一次性返回的。 目前遇到的問題是,當商品數量比較多時,首次渲染時間很長,而且頁面會卡頓。
小聲bb:其實就是原來程式碼(因為歷史原因)寫得太爛了…OTL
先放個圖
小聲bb:連小程式都看不下去了,要警告了
微信開發者工具都有警告了,而且提示裡面也有定位到具體程式碼的位置,所以關鍵就是這個setData
! ! !
我們可以先看看官方對於小程式效能以及 setData
最佳化的一些建議。 (developers.weixin.qq.com/miniprogram…)
具體實踐:
setData
不能一次性傳太多數據,如果清單太長,可以分開渲染【例如轉換為二維數組,每次循環渲染一個數組】。 v1:簡單粗暴版
// 每次渲染一个分类// 假设goodsList是一个二维数组goodsList.forEach((item, index) => { this.setData({ [`goodsList[${index}]`]: item }) })复制代码
像上面這樣寫會有一個問題,頁面首屏渲染是快了,但是點擊頁面操作(比如加購按鈕等),頁面會卡住,等一下才有反應,操作回饋延遲嚴重。
其實這是因為,這個循環是把單次setData
數量減少了,但是卻變成了循環多次setData
,我們看著首屏顯示好了,但其實其他分類(其他陣列)還在渲染,線程還是忙碌狀態,JS 線程一直在編譯執行渲染,點擊事件不能及時傳遞到邏輯層,邏輯層亦無法及時將操作處理結果及時傳遞到視圖層。
v2:定時器hack版
既然js線程忙著渲染,那我們可以強制讓它先停下來。於是有了v2的定時器hack版。
// 每次渲染一个分类let len = data.goodsList ? data.goodsList.length : 0;let idx = 0let timer = setInterval(() => { if (idx < len) { that.setData({ [`goodsList[${idx}]`]: data.goodsList[idx] }); idx++ } else { clearInterval(timer) } }, 15);复制代码
現在首屏渲染速度問題解決了,點擊按鈕延遲回應問題也解決了。就是程式碼有點hack,逼死強迫症
v3:大殺器——虛擬列表
虛擬列表簡單說原理就是只渲染當前顯示螢幕區域以及前n屏和後n屏的數據,用一個單獨的字段保存當前需要顯示的數組(就是當前一屏前n屏後n屏),每次列表滾動的時候重新計算需要顯示的數據,更新這個字段,頁面就會相應更新了。這樣就能確保頁面上的元素節點數量不會太多,就可以支援大量資料的長列表需求。
更詳細的原理和實現各位同學們可以自己搜一下,此處不展開。
小程式官方也有開源的虛擬清單元件:recycle-view
setData
可以支援顆粒更新,指定到特定的屬性。 例如加購等操作,需要更新商品右上角的小數字,可以這樣寫:
this.setData({ [`goodsList[${categoryIndex}][${goodsIndex}].num`]: goodsItem.num })复制代码
data
,不要用setData
更新,因為setData
會觸發頁面渲染。 eg:
Page({ data: { ... }, // 跟页面渲染无关的数据 state: { hasLogin: false, }, ... })// 更新的时候直接赋值就行this.state.hasLogin = true复制代码
PS:或甚至不需要掛載到 page
物件下,直接用普通變數儲存。
在長列表中圖片大小如果不加限制,大量的大圖會佔用很多內存,有可能導致iOS客戶端內存佔用上升,從而觸發系統回收小程式頁面。除了記憶體問題外,大圖片也會造成頁面切換的卡頓。
解決方法就是根據目前顯示的圖片區域大小,取尺寸剛好適合(2倍-3倍圖)的圖片。
建議圖片用CDN,一般CDN服務廠商提供圖片服務的都會提供裁剪圖片的接口,然後接口只返回原圖鏈接,前端根據需要傳參數裁剪圖片。前端具體做法可以寫公共的圖片處理方法,或是自己封裝圖片組件。
附常用圖片CDN服務商圖片裁切API文件:
比如在该点餐页面进入时需要获取定位,然后根据定位获取最近的门店,前面两个接口都需要请求(具体可以根据业务需求),而最后如果获取到的距离最近的门店跟上次一样,则不需要重新获取店铺详情和商品数据。
还是该点餐页面流程,像上文说过的,进入页面时需要获取定位接口,等定位接口返回结果了再拿定位取值去获取距离最近的店铺,最后才是请求店铺和商品数据。
这三个接口是串行的。此时如果我们每个接口都弹出一个loading提示,就会出现loading显示一会儿,消失,又显示一会儿,又消失……这样的现象,这样的体验是不太好的。
建议可以通过封装请求,并且在请求里统一处理loading,来合并短时间内多次发起请求的多个loading。
eg:
let showLoadingTimer = null;let showRequestLoading = false; // 标记是否正在显示loading/** * 封装request * @param {*} {showLoading:是否需要显示loading, options:request参数,如url,data等} */function request({showLoading = true, ...options}) { // 显示request loading handleShowLoading(showLoading) wx.request({ ... complete() { // 关闭request loading handleShowLoading(false) } }) }/** * 封装request loading * 短时间内如果调用多次showLoading,会合并在一起显示,而不是每个都闪现一下 * @param showLoading */function handleShowLoading(showLoading) { if (showLoading) { // 显示loading clearTimeout(showLoadingTimer); if (!showRequestLoading) { showRequestLoading = true; wx.showNavigationBarLoading(); wx.showLoading({ title: "加载中", mask: true }) } } else { // 200ms后关闭loading showLoadingTimer = setTimeout(() => { showRequestLoading = false; wx.hideNavigationBarLoading(); wx.hideLoading() }, 200) } }复制代码
比如这个点餐页每次 onShow
都会调用定位接口和获取最近门店接口,但是不显示loading,用户就没有感知,体验比较好。
需要关注接口的粒度控制。 因为有时候合并接口,前端可以减少一次请求,体验更好;但有时候如果接口的数据太多,响应太慢,就可以考虑是否某部分数据可以后置获取,让主要的页面内容先渲染出来,根据这个设计来拆分接口。
比如项目中的点餐页面,原来购物车数据和商品规格弹窗显示的详情数据都是在获取店铺商品接口一次性返回的,而这个接口本来由于设计需要一次返回所有商品,就会造成数据量太大,而且后端需要查询的表也更多。于是把获取购物车,和商品详情接口都拆分为单独的接口,获取店铺商品接口的响应时间就减少了,页面也能更快显示出来。
其实上面提到的逻辑优化和接口优化很多都是细节,并不是太高深的技术,我们平时迭代的时候就可以注意。而体验方面的优化则需要前端同学在前端技术以外更多关注用户体验和设计方面的知识啦,而且这也是一个有追求的前端应该具备的技能……←_←
所以嘛……技术路漫漫,大家共勉吧
相关免费学习推荐:微信小程序开发教程
以上是實作總結小程式效能優化的詳細內容。更多資訊請關注PHP中文網其他相關文章!