# 合理使用 setData
setData 是小程序開發(fā)中使用最頻繁、也是最容易引發(fā)性能問題的接口。
# 1. setData 的流程
setData 的過程,大致可以分成幾個(gè)階段:
- 邏輯層虛擬 DOM 樹的遍歷和更新,觸發(fā)組件生命周期和 observer 等;
- 將 data 從邏輯層傳輸?shù)揭晥D層;
- 視圖層虛擬 DOM 樹的更新、真實(shí) DOM 元素的更新并觸發(fā)頁面渲染更新。
# 2. 數(shù)據(jù)通信
對(duì)于第 2 步,由于小程序的邏輯層和視圖層是兩個(gè)獨(dú)立的運(yùn)行環(huán)境、分屬不同的線程或進(jìn)程,不能直接進(jìn)行數(shù)據(jù)共享,需要進(jìn)行數(shù)據(jù)的序列化、跨線程/進(jìn)程的數(shù)據(jù)傳輸、數(shù)據(jù)的反序列化,因此數(shù)據(jù)傳輸過程是異步的、非實(shí)時(shí)的。
iOS/iPadOS/MacOS 上,數(shù)據(jù)傳輸是通過
evaluateJavascript實(shí)現(xiàn)的,還會(huì)有額外 JS 腳本解析和執(zhí)行的耗時(shí)。
數(shù)據(jù)傳輸?shù)暮臅r(shí)與數(shù)據(jù)量的大小正相關(guān),如果對(duì)端線程處于繁忙狀態(tài),數(shù)據(jù)會(huì)在消息隊(duì)列中等待。
# 3. 使用建議
# 3.1 data 應(yīng)只包括渲染相關(guān)的數(shù)據(jù)
setData 應(yīng)只用來進(jìn)行渲染相關(guān)的數(shù)據(jù)更新。用 setData 的方式更新渲染無關(guān)的字段,會(huì)觸發(fā)額外的渲染流程,或者增加傳輸?shù)臄?shù)據(jù)量,影響渲染耗時(shí)。
- ? 頁面或組件的 data 字段,應(yīng)用來存放和頁面或組件渲染相關(guān)的數(shù)據(jù)(即直接在 wxml 中出現(xiàn)的字段);
- ? 頁面或組件渲染間接相關(guān)的數(shù)據(jù)可以設(shè)置為「純數(shù)據(jù)字段」,可以使用 setData 設(shè)置并使用 observers 監(jiān)聽變化;
- ? 頁面或組件渲染無關(guān)的數(shù)據(jù),應(yīng)掛在非 data 的字段下,如
this.userData = {userId: 'xxx'}; - ? 避免在 data 中包含渲染無關(guān)的業(yè)務(wù)數(shù)據(jù);
- ? 避免使用 data 在頁面或組件方法間進(jìn)行數(shù)據(jù)共享;
- ? 避免濫用 純數(shù)據(jù)字段 來保存可以使用非 data 字段保存的數(shù)據(jù)。
# 3.2 控制 setData 的頻率
每次 setData 都會(huì)觸發(fā)邏輯層虛擬 DOM 樹的遍歷和更新,也可能會(huì)導(dǎo)致觸發(fā)一次完整的頁面渲染流程。過于頻繁(毫秒級(jí))的調(diào)用 setData,會(huì)導(dǎo)致以下后果:
- 邏輯層 JS 線程持續(xù)繁忙,無法正常響應(yīng)用戶操作的事件,也無法正常完成頁面切換;
- 視圖層 JS 線程持續(xù)處于忙碌狀態(tài),邏輯層 -> 視圖層通信耗時(shí)上升,視圖層收到消息的延時(shí)較高,渲染出現(xiàn)明顯延遲;
- 視圖層無法及時(shí)響應(yīng)用戶操作,用戶滑動(dòng)頁面時(shí)感到明顯卡頓,操作反饋延遲,用戶操作事件無法及時(shí)傳遞到邏輯層,邏輯層亦無法及時(shí)將操作處理結(jié)果及時(shí)傳遞到視圖層。
因此,開發(fā)者在調(diào)用 setData 時(shí)要注意:
- ? 僅在需要進(jìn)行頁面內(nèi)容更新時(shí)調(diào)用 setData;
- ? 對(duì)連續(xù)的 setData 調(diào)用盡可能的進(jìn)行合并;
- ? 避免不必要的 setData;
- ? 避免以過高的頻率持續(xù)調(diào)用 setData,例如毫秒級(jí)的倒計(jì)時(shí);
- ? 避免在 onPageScroll 回調(diào)中每次都調(diào)用 setData。
# 3.3 選擇合適的 setData 范圍
組件的 setData 只會(huì)引起當(dāng)前組件和子組件的更新,可以降低虛擬 DOM 更新時(shí)的計(jì)算開銷。
- ? 對(duì)于需要頻繁更新的頁面元素(例如:秒殺倒計(jì)時(shí)),可以封裝為獨(dú)立的組件,在組件內(nèi)進(jìn)行 setData 操作。必要時(shí)可以使用 CSS contain 屬性限制計(jì)算布局、樣式和繪制等的范圍。
# 3.4 setData 應(yīng)只傳發(fā)生變化的數(shù)據(jù)
setData 的數(shù)據(jù)量會(huì)影響數(shù)據(jù)拷貝和數(shù)據(jù)通訊的耗時(shí),增加頁面更新的開銷,造成頁面更新延遲。
- ? setData 應(yīng)只傳入發(fā)生變化的字段;
- ? 建議以數(shù)據(jù)路徑形式改變數(shù)組中的某一項(xiàng)或?qū)ο蟮哪硞€(gè)屬性,如
this.setData({'array[2].message': 'newVal', 'a.b.c.d': 'newVal'}),而不是每次都更新整個(gè)對(duì)象或數(shù)組; - ? 不要在 setData 中偷懶一次性傳所有data:
this.setData(this.data)。
# 3.5 控制后臺(tái)態(tài)頁面的 setData
由于小程序邏輯層是單線程運(yùn)行的,后臺(tái)態(tài)頁面去 setData 也會(huì)搶占前臺(tái)頁面的運(yùn)行資源,且后臺(tái)態(tài)頁面的的渲染用戶是無法感知的,會(huì)產(chǎn)生浪費(fèi)。在某些平臺(tái)上,小程序渲染層各 WebView 也是共享同一個(gè)線程,后臺(tái)頁面的渲染和邏輯執(zhí)行也會(huì)導(dǎo)致前臺(tái)頁面的卡頓。
- ? 頁面切后臺(tái)后的更新操作,應(yīng)盡量避免,或延遲到頁面
onShow后延遲進(jìn)行; - ? 避免在切后臺(tái)后仍進(jìn)行高頻的 setData,例如倒計(jì)時(shí)更新。
# 4. 性能分析
開發(fā)者可以通過組件的 setUpdatePerformanceListener 接口獲取更新性能統(tǒng)計(jì)信息,來分析產(chǎn)生性能瓶頸的組件。