商城系統(tǒng) 注冊(cè)

小程序redux性能優(yōu)化,提升三倍渲染速度

2020-09-27|HiShop
導(dǎo)讀:很多使用小程序的用戶會(huì)反饋,一些小程序會(huì)出現(xiàn)卡頓的情況,比如一些電商小程序在打開(kāi)商品列表需要幾秒的緩沖時(shí)間,那么微信小程序開(kāi)發(fā)需要如何優(yōu)化,下面是這篇小程序redux性能優(yōu)化。...

很多使用小程序的用戶會(huì)反饋,一些小程序會(huì)出現(xiàn)卡頓的情況,比如一些電商小程序在打開(kāi)商品列表需要幾秒的緩沖時(shí)間,那么微信小程序開(kāi)發(fā)需要如何優(yōu)化,下面是這篇小程序redux性能優(yōu)化。

首先了解小程序的工作原理和性能關(guān)鍵點(diǎn)。

小程序redux性能優(yōu)化,提升三倍渲染速度

1工作原理 (官方說(shuō)明)

小程序的視圖層目前使用 WebView 作為渲染載體,而邏輯層是由獨(dú)立的 JavascriptCore 作為運(yùn)行環(huán)境。在架構(gòu)上,WebView 和 JavascriptCore 都是獨(dú)立的模塊,并不具備數(shù)據(jù)直接共享的通道。當(dāng)前,視圖層和邏輯層的數(shù)據(jù)傳輸,實(shí)際上通過(guò)兩邊提供的 evaluateJavascript 所實(shí)現(xiàn)。即用戶傳輸?shù)臄?shù)據(jù),需要將其轉(zhuǎn)換為字符串形式傳遞,同時(shí)把轉(zhuǎn)換后的數(shù)據(jù)內(nèi)容拼接成一份 JS 腳本,再通過(guò)執(zhí)行 JS 腳本的形式傳遞到兩邊獨(dú)立環(huán)境。 
而 evaluateJavascript 的執(zhí)行會(huì)受很多方面的影響,數(shù)據(jù)到達(dá)視圖層并不是實(shí)時(shí)的。

2性能關(guān)鍵點(diǎn)(官方說(shuō)明)

  1. 頻繁的去 setData 
    在我們分析過(guò)的一些案例里,部分小程序會(huì)非常頻繁(毫秒級(jí))的去setData,其導(dǎo)致了兩個(gè)后果:

Android 下用戶在滑動(dòng)時(shí)會(huì)感覺(jué)到卡頓,操作反饋延遲嚴(yán)重,因?yàn)?JS 線程一直在編譯執(zhí)行渲染,未能及時(shí)將用戶操作事件傳遞到邏輯層,邏輯層亦無(wú)法及時(shí)將操作處理結(jié)果及時(shí)傳遞到視圖層; 
渲染有出現(xiàn)延時(shí),由于 WebView 的 JS 線程一直處于忙碌狀態(tài),邏輯層到頁(yè)面層的通信耗時(shí)上升,視圖層收到的數(shù)據(jù)消息時(shí)距離發(fā)出時(shí)間已經(jīng)過(guò)去了幾百毫秒,渲染的結(jié)果并不實(shí)時(shí);

  1. 每次 setData 都傳遞大量新數(shù)據(jù) 
    由setData的底層實(shí)現(xiàn)可知,我們的數(shù)據(jù)傳輸實(shí)際是一次 evaluateJavascript 腳本過(guò)程,當(dāng)數(shù)據(jù)量過(guò)大時(shí)會(huì)增加腳本的編譯執(zhí)行時(shí)間,占用 WebView JS 線程,
  2. 后臺(tái)態(tài)頁(yè)面進(jìn)行 setData 
    當(dāng)頁(yè)面進(jìn)入后臺(tái)態(tài)(用戶不可見(jiàn)),不應(yīng)該繼續(xù)去進(jìn)行setData,后臺(tái)態(tài)頁(yè)面的渲染用戶是無(wú)法感受的,另外后臺(tái)態(tài)頁(yè)面去setData也會(huì)搶占前臺(tái)頁(yè)面的執(zhí)行。

3度量性能指標(biāo)

我們?cè)趦?yōu)化性能時(shí),指標(biāo)是非常重要的,沒(méi)有指標(biāo),你沒(méi)法知道優(yōu)化的點(diǎn)是否有效。不能單憑感覺(jué)去優(yōu)化,要根據(jù)指標(biāo)反饋,明確優(yōu)化的成果。同時(shí),優(yōu)化就像個(gè)無(wú)底洞,要注意投入產(chǎn)出比。 
用戶反饋的卡頓,要么就是js執(zhí)行消耗資源過(guò)多導(dǎo)致處理器沒(méi)響應(yīng),要么是UI渲染消耗資源過(guò)多,導(dǎo)致UI沒(méi)法響應(yīng)用戶操作。 
通過(guò)查看代碼,我們并沒(méi)有消耗大量計(jì)算資源的業(yè)務(wù)邏輯,但是出現(xiàn)了UI反復(fù)操作和搶占資源的現(xiàn)象。

4如何度量

可以利用setData的第二個(gè)參數(shù),傳入callback函數(shù),統(tǒng)計(jì)渲染時(shí)長(zhǎng)。代碼如下


  1. let startTime = Date.now()
  2. this.setData(data, () => {
  3. let endTime = Data.now()
  4. console.log(endTime - startTime, '渲染時(shí)長(zhǎng)')
  5. })

案例分析  1、檢查點(diǎn):是否頻繁去setData  檢查結(jié)果:存在  產(chǎn)生原因:redux中監(jiān)聽(tīng)的是整個(gè)store,只要store變化,就會(huì)執(zhí)行setData操作,這就意味著頁(yè)面無(wú)關(guān)的數(shù)據(jù)改變,也會(huì)觸發(fā)該頁(yè)面執(zhí)行setData操作,但是這個(gè)操作是無(wú)意義的。  問(wèn)題代碼:


  1. // libs/redux-wechat/connect.js
  2.  
  3. // 對(duì)整個(gè)store進(jìn)行subscribe。變化就執(zhí)行handleChange
  4. this.unsubscribe = this.store.subscribe(handleChange.bind(this, options));
  5.  
  6. function handleChange(options) {
  7. ...省略代碼
  8. const state = this.store.getState()
  9. const mappedState = mapState(state, options);
  10. this.setData(mappedState)
  11. }

解決方案:

只監(jiān)聽(tīng)當(dāng)前頁(yè)面用到的store中的部分?jǐn)?shù)據(jù),只有該部分?jǐn)?shù)據(jù)變化,才setData。(store沒(méi)提供單個(gè)數(shù)據(jù)的監(jiān)聽(tīng),如果自己修改redux實(shí)現(xiàn),難度較大,同時(shí)修改太底層,容易出不可預(yù)料的異常。)  判斷頁(yè)面數(shù)據(jù)與需要更新數(shù)據(jù)是否相同,如果相同,不做操作。(這個(gè)方案成本比較低,就用它吧)

代碼實(shí)現(xiàn):


  1. // libs/redux-wechat/connect.js
  2. // 如果更新的數(shù)據(jù)和頁(yè)面數(shù)據(jù)相同,不做操作。
  3. function handleChange(options) {
  4. ...省略代碼
  5. const state = this.store.getState()
  6. const mappedState = mapState(state, options);
  7. // 如果更新的數(shù)據(jù)和頁(yè)面數(shù)據(jù)相同,不做操作。
  8. if (mappedState === this.prevState) return // 新加入代碼
  9. this.setData(mappedState)
  10. // 保存上一次數(shù)據(jù)
  11. this.prevState = mappedState // 新加入代碼
  12. }

另外一個(gè)優(yōu)化:如果store數(shù)據(jù)毫秒級(jí)變化怎么辦,例如更新購(gòu)物車(chē)的同時(shí),還更新了購(gòu)物數(shù)量,能不能把兩次變化合并起來(lái)?因?yàn)閟tore的數(shù)據(jù)是共享的,最后一次的更新就是最新的數(shù)據(jù),可以采用節(jié)流器對(duì)請(qǐng)求進(jìn)行合并。


  1. clearTimeout(this.setDataTMO)
  2. this.setDataTMO = setTimeout(() => {
  3. this.setData(mappedState)
  4. }, 50); // 時(shí)間可以看情況調(diào)整

2、檢查點(diǎn):每次 setData 都傳遞大量新數(shù)據(jù)  檢查結(jié)果:存在  產(chǎn)生原因:

頁(yè)面存在引用沒(méi)用到的store數(shù)據(jù)。  后端返回?cái)?shù)據(jù)直接進(jìn)入store,后端接口返回冗余字段。

問(wèn)題代碼:


  1. /pages/user/index.js
  2. connect(state => ({
  3. member: state.member,
  4. mycoupon: state.mycoupon,
  5. guessLikeList: state.recommend.guessLikeList,
  6. locationInfo: state.common && state.common.locationInfo, //可刪除
  7. selectedseller: state.home.selectedseller,//可刪除
  8. carts: state.carts.carts,//可刪除
  9. ...state.common
  10. }))

解決方案:

刪除頁(yè)面無(wú)用的connect (老業(yè)務(wù)在使用,修改存在風(fēng)險(xiǎn),通過(guò)后續(xù)迭代優(yōu)化)  請(qǐng)求后端接口后,拿到數(shù)據(jù)進(jìn)行優(yōu)化處理再把數(shù)據(jù)傳入store(成本較高)

3、檢查點(diǎn):后臺(tái)態(tài)頁(yè)面進(jìn)行 setData  檢查結(jié)果:存在  產(chǎn)生原因:redux connect設(shè)計(jì)與小程序有差異  問(wèn)題代碼:


  1. // libs/redux-wechat/connect.js
  2. function onLoad(options) {
  3. ...省略部分代碼
  4. if(shouldSubscribe){
  5. this.unsubscribe = this.store.subscribe(handleChange.bind(this, options));
  6. handleChange.call(this, options)
  7. }
  8. }
  9. function onUnload() {
  10. ...省略部分代碼
  11. // 頁(yè)面onUnload時(shí),才解除監(jiān)聽(tīng)
  12. typeof this.unsubscribe === 'function' && this.unsubscribe()
  13. }

小程序生命周期中,onUnload會(huì)在頁(yè)面銷(xiāo)毀時(shí)執(zhí)行,例如A->B->C->D 的跳轉(zhuǎn),A頁(yè)面一直在監(jiān)聽(tīng)store的變化,如果D頁(yè)面修改數(shù)據(jù),會(huì)造成A,B,C頁(yè)面也執(zhí)行setData操作,搶占了D的資源,因此造成卡頓。  解決方案:

后臺(tái)狀態(tài)的頁(yè)面在setData時(shí)直接return(目前采用該方法)  當(dāng)頁(yè)面隱藏時(shí),移除監(jiān)聽(tīng)。

代碼實(shí)現(xiàn):


  1. // 因?yàn)樵诤笈_(tái)的頁(yè)面setData會(huì)搶占前臺(tái)資源,所以在后臺(tái)的頁(yè)面不要執(zhí)行setData操作
  2. if (this.route !== _getActivePage().route) return

但是由于在后臺(tái)的頁(yè)面數(shù)據(jù)沒(méi)法更新,如果D頁(yè)面修改A引用的數(shù)據(jù),就會(huì)出現(xiàn)A引用舊數(shù)據(jù)問(wèn)題,所以在onShow的時(shí)候做一次同步。


  1. // 后臺(tái)的頁(yè)面切換到前臺(tái)的時(shí)候,做一次數(shù)據(jù)同步
  2. function onShow(options) {
  3. if(shouldSubscribe){
  4. handleChange.call(this, options)
  5. }
  6. if (typeof _onShow === 'function') {
  7. _onShow.call(this, options)
  8. }
  9. }

指標(biāo)測(cè)試  做了這么多,到底有沒(méi)用,拿出來(lái)溜一溜就清楚了。  測(cè)試平臺(tái):iphone7、三星s7 、小程序開(kāi)發(fā)工具  測(cè)試流程:首頁(yè) -> 配送到家 -> 加入購(gòu)物車(chē) -> 結(jié)算 ->查看訂單  測(cè)試指標(biāo):調(diào)用setData次數(shù),渲染總耗時(shí),平均單次渲染耗時(shí)

優(yōu)化后setData次數(shù)平均下降150次。  渲染耗時(shí)越是卡頓的機(jī)器,收益越大,三星s7平均每次渲染耗時(shí)降低826ms。

HiShop小程序工具提供多類(lèi)型商城/門(mén)店小程序制作,可視化編輯 1秒生成5步上線。通過(guò)拖拽、拼接模塊布局小程序商城頁(yè)面,所看即所得,只需要美工就能做出精美商城。更多小程序商店請(qǐng)查看:小程序商店

小程序redux性能優(yōu)化,提升三倍渲染速度

 

 

電話咨詢 預(yù)約演示 0元開(kāi)店