Taro:高性能小程式的最佳實踐

来源:https://www.cnblogs.com/Jcloud/archive/2023/11/29/17864755.html
-Advertisement-
Play Games

作為一個開放式的跨端跨框架解決方案,Taro 在大量的小程式和 H5 應用中得到了廣泛應用。本文將為大家提供一些小程式開發的最佳實踐,幫助大家最大程度地提升小程式應用的性能表現。 ...


前言

作為一個開放式的跨端跨框架解決方案,Taro 在大量的小程式和 H5 應用中得到了廣泛應用。我們經常收到開發者的反饋,例如“渲染速度較慢”、“滑動不夠流暢”、“性能與原生應用相比有差距” 等。這表明性能問題一直是困擾開發者的一個重要問題。

熟悉 Taro 的開發者應該知道,相比於 Taro 1/2,Taro 3 是一個更加註重運行時而輕量化編譯時的框架。它的優勢在於提供了更高效的代碼編寫方式,並擁有更豐富的生態系統。然而,這也意味著在性能方面可能會有一些損耗。

但是,使用 Taro 3 並不意味著我們必須犧牲應用的性能。事實上,Taro 已經提供了一系列的性能優化方法,並且不斷探索更加極致的優化方案。

本文將為大家提供一些小程式開發的最佳實踐,幫助大家最大程度地提升小程式應用的性能表現。

一、如何提升初次渲染性能

如果初次渲染的數據量非常大,可能會導致頁面在載入過程中出現一段時間的白屏。為瞭解決這個問題,Taro 提供了預渲染功能(Prerender)。

使用 Prerender 非常簡單,只需在項目根目錄下的 config 文件夾中找到 index.js/dev.js/prod.js 三者中的任意一個項目配置文件,並根據項目情況進行修改。在編譯時,Taro CLI 會根據你的配置自動啟動預渲染功能。

const config = {
  ...
  mini: {
    prerender: {
      match: 'pages/shop/**', // 所有以 `pages/shop/` 開頭的頁面都參與 prerender
      include: ['pages/any/way/index'], // `pages/any/way/index` 也會參與 prerender
      exclude: ['pages/shop/index/index'] // `pages/shop/index/index` 不用參與 prerender
    }
  }
};

module.exports = config

更詳細說明請參考官方文檔: https://taro-docs.jd.com/docs/prerender

二、如何提升更新性能

由於 Taro 使用小程式的 template 進行渲染,這會引發一個問題:所有的 setData 更新都需要由頁面對象調用。當頁面結構較為複雜時,更新的性能可能會下降。

當層級過深時,setData 的數據結構如下:

page.setData({
  'root.cn.[0].cn.[0].cn.[0].cn.[0].markers': [],
})

期望的 setData 數據結構:

component.setData({
  'cn.[0].cn.[0].markers': [],
})

目前有兩種方法可以實現上述結構,以實現局部更新的效果,從而提升更新性能:

1. 全局配置項 baseLevel

對於不支持模板遞歸的小程式(例如微信、QQ、京東小程式等),當 DOM 層級達到一定數量後,Taro 會利用原生自定義組件來輔助遞歸渲染。簡單來說,當 DOM 結構超過 N 層時,Taro 將使用原生自定義組件進行渲染(可以通過修改配置項 baseLevel 來調整 N 的值,建議設置為 8 或 4)。

需要註意的是,由於這是全局設置,可能會帶來一些問題,例如:

  • 在跨原生自定義組件時,flex 佈局會失效(這是影響最大的問題);
  • SelectorQuery.select 方法中,跨自定義組件的後代選擇器寫法需要增加 >>>:.the-ancestor >>> .the-descendant

2. 使用 CustomWrapper 組件

CustomWrapper 組件的作用是創建一個原生自定義組件,用於調用後代節點的 setData 方法,以實現局部更新的效果。

我們可以使用它來包裹那些遇到更新性能問題的模塊,例如:

import { View, Text } from '@tarojs/components'

export default function () {
  return (
    <View className="index">
      <Text>Demo</Text>
      <CustomWrapper>
        <GoodsList />
      </CustomWrapper>
    </View>
  )
}

三、如何提升長列表性能

長列表是常見的組件,當生成或載入的數據量非常大時,可能會導致嚴重的性能問題,尤其在低端機上可能會出現明顯的卡頓現象。

為瞭解決長列表的問題,Taro 提供了 VirtualList 組件和 VirtualWaterfall 組件。它們的原理是只渲染當前可見區域(Visible Viewport)的視圖,非可見區域的視圖在用戶滾動到可見區域時再進行渲染,以提高長列表滾動的流暢性。

image.png

1. VirtualList 組件(虛擬列表)

以 React Like 框架使用為例,可以直接引入組件:

import VirtualList from '@tarojs/components/virtual-list'

一個最簡單的長列表組件如下所示:

function buildData(offset = 0) {
  return Array(100)
    .fill(0)
    .map((_, i) => i + offset)
}

const Row = React.memo(({ id, index, data }) => {
  return (
    <View id={id} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'}>
      Row {index} : {data[index]}
    </View>
  )
})

export default class Index extends Component {
  state = {
    data: buildData(0),
  }

  render() {
    const { data } = this.state
    const dataLen = data.length
    return (
      <VirtualList
        height={800} /* 列表的高度 */
        width="100%" /* 列表的寬度 */
        item={Row} /* 列表單項組件,這裡只能傳入一個組件 */
        itemData={data} /* 渲染列表的數據 */
        itemCount={dataLen} /* 渲染列表的長度 */
        itemSize={100} /* 列表單項的高度  */
      />
    )
  }
}

更多詳情可以參考官方文檔: https://taro-docs.jd.com/docs/virtual-list

2. VirtualWaterfall 組件(虛擬瀑布流)

以 React Like 框架使用為例,可以直接引入組件:

import { VirtualWaterfall } from `@tarojs/components-advanced`

一個最簡單的長列表組件如下所示:

function buildData(offset = 0) {
  return Array(100)
    .fill(0)
    .map((_, i) => i + offset)
}

const Row = React.memo(({ id, index, data }) => {
  return (
    <View id={id} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'}>
      Row {index} : {data[index]}
    </View>
  )
})

export default class Index extends Component {
  state = {
    data: buildData(0),
  }

  render() {
    const { data } = this.state
    const dataLen = data.length
    return (
      <VirtualWaterfall
        height={800} /* 列表的高度 */
        width="100%" /* 列表的寬度 */
        item={Row} /* 列表單項組件,這裡只能傳入一個組件 */
        itemData={data} /* 渲染列表的數據 */
        itemCount={dataLen} /* 渲染列表的長度 */
        itemSize={100} /* 列表單項的高度  */
      />
    )
  }
}

更多詳情可以參考官方文檔: https://taro-docs.jd.com/docs/virtual-waterfall

四、如何避免 setData 數據量較大

眾所周知,對小程式性能的影響較大的主要有兩個因素,即 setData 的數據量和單位時間內調用 setData 函數的次數。在 Taro 中,會對 setData 進行批量更新操作,因此通常只需要關註 setData 的數據量大小。下麵通過幾個例子來說明如何避免數據量過大的問題:

例子 1:刪除樓層節點要謹慎處理

目前 Taro 在處理節點刪除方面存在一些缺陷。假設存在以下代碼寫法:

<View>
  <!-- 輪播 -->
  <Slider />
  <!-- 商品組 -->
  <Goods />
  <!-- 模態彈窗 -->
  {isShowModal && <Modal />}
</View>

isShowModaltrue 變為 false 時,模態彈窗會消失。此時,Modal 組件的兄弟節點都會被更新,setData 的數據是 Slider + Goods 組件的 DOM 節點信息。

一般情況下,這不會對性能產生太大影響。然而,如果待刪除節點的兄弟節點的 DOM 結構非常複雜,比如一個個樓層組件,刪除操作的副作用會導致 setData 的數據量變大,從而影響性能。

為瞭解決這個問題,可以通過隔離刪除操作來進行優化。

<View>
  <!-- 輪播 -->
  <Slider />
  <!-- 商品組 -->
  <Goods />
  <!-- 模態彈窗 -->
  <View>
    {isShowModal && <Modal />}
  </View>
</View>

例子 2:基礎組件的屬性要保持引用

當基礎組件(例如 ViewInput 等)的屬性值為非基本類型時,假設存在以下代碼寫法:

<Map
  latitude={22.53332}
  longitude={113.93041}
  markers={[
    {
      latitude: 22.53332,
      longitude: 113.93041,
    },
  ]}
/>

每次渲染時,React 會對基礎組件的屬性進行淺比較。如果發現 markers 的引用不同,就會觸發組件屬性的更新。這最終導致了 setData 操作的頻繁執行和數據量的增加。 為瞭解決這個問題,可以使用狀態(state)或閉包等方法來保持對象的引用,從而避免不必要的更新。

<Map
  latitude={22.53332}
  longitude={113.93041}
  markers={this.state.markers}
/>

五、更多最佳實踐

1. 阻止滾動穿透

在小程式開發中,當存在滑動蒙層、彈窗等覆蓋式元素時,滑動事件會冒泡到頁面上,導致頁面元素也會跟著滑動。通常我們會通過設置 catchTouchMove 來阻止事件冒泡。

然而,由於 Taro3 事件機制的限制,小程式事件都是以 bind 的形式進行綁定。因此,與 Taro1/2 不同,調用 e.stopPropagation() 並不能阻止滾動事件的穿透。

解決辦法 1:使用樣式(推薦)

可以為需要禁用滾動的組件編寫以下樣式:

{
  overflow:hidden;
  height: 100vh;
}

解決辦法 2:使用 catchMove

對於極個別的組件,比如 Map 組件,即使使用樣式固定寬高也無法阻止滾動,因為這些組件本身具有滾動的功能。因此,第一種方法無法處理冒泡到 Map 組件上的滾動事件。 在這種情況下,可以為 View 組件添加 catchMove 屬性:

// 這個 View 組件會綁定 catchtouchmove 事件而不是 bindtouchmove
<View catchMove />

2. 跳轉預載入

在小程式中,當調用 Taro.navigateTo 等跳轉類 API 後,新頁面的 onLoad 事件會有一定的延時。因此,為了提高用戶體驗,可以將一些操作(如網路請求)提前到調用跳轉 API 之前執行。

對於熟悉 Taro 的開發者來說,可能會記得在 Taro 1/2 中有一個名為 componentWillPreload 的鉤子函數。然而,在 Taro 3 中,這個鉤子函數已經被移除了。不過,開發者可以使用 Taro.preload() 方法來實現跳轉預載入的效果:

// pages/index.js
Taro.preload(fetchSomething())
Taro.navigateTo({ url: '/pages/detail' })

// pages/detail.js
console.log(getCurrentInstance().preloadData)

3. 建議把 Taro.getCurrentInstance() 的結果保存下來

在開發過程中,我們經常會使用 Taro.getCurrentInstance() 方法來獲取小程式的 apppage 對象以及路由參數等數據。然而,頻繁地調用該方法可能會導致一些問題。

因此,建議將 Taro.getCurrentInstance() 的結果保存在組件中,併在需要時直接使用,以避免頻繁調用該方法。這樣可以提高代碼的執行效率和性能。

class Index extends React.Component {
  inst = Taro.getCurrentInstance()

  componentDidMount() {
    console.log(this.inst)
  }
}

六、預告:小程式編譯模式(CompileMode)

Taro 一直追求並不斷突破性能的極限,除了以上提供的最佳實踐,我們即將推出小程式編譯模式(CompileMode)。

什麼是 CompileMode?

前面已經說過,Taro3 是一種重運行時的框架,當節點數量增加到一定程度時,渲染性能會顯著下降。 因此,為瞭解決這個問題,Taro 引入了 CompileMode 編譯模式。

CompileMode 在編譯階段對開發者的代碼進行掃描,將 JSXVue template 代碼提前編譯為相應的小程式模板代碼。這樣可以減少小程式渲染層虛擬 DOM 樹節點的數量,從而提高渲染性能。 通過使用 CompileMode,可以有效減少小程式的渲染負擔,提升應用的性能表現。

如何使用?

開發者只需為小程式的基礎組件添加 compileMode 屬性,該組件及其子組件將會被編譯為獨立的小程式模板。

function GoodsItem () {
  return (
    <View compileMode>
      ...
    </View>
  )
}

目前第一階段的開發工作已經完成,我們即將發佈 Beta 版本,歡迎大家關註! 想提前瞭解的可以查看 RFC 文檔: https://github.com/NervJS/taro-rfcs/blob/feat/compile-mode/rfcs/0000-compile-mode.md

結尾

通過採用 Taro 的最佳實踐,我們相信您的小程式應用性能一定會有顯著的提升。未來,我們將持續探索更多優化方案,覆蓋更廣泛的應用場景,為開發者提供更高效、更優秀的開發體驗。

如果您在項目中有任何經驗總結或思考,歡迎向我們投稿併進行交流,讓我們一起分享給更多開發者,非常感謝您的支持!

作者:京東零售 利齊諾

來源:京東雲開發者社區 轉載請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 稀疏索引 密集索引:文件中的每個搜索碼值都對應一個索引值,就是葉子節點保存了整行. 稀疏索引:文件只為索引碼的某些值建立索引項. 稀疏索引的創建過程包括將集合中的元素分段,並給每個分段中的最小元素創建索引。在搜索時,先定位到第一個大於搜索值的索引的前一個索引,然後從該索引所在的分段中從前向後順序遍歷 ...
  • WebSocket 是一種用於實現持久連接的通信協議,它的原理和工作方式相對複雜,但我們可以嘗試以儘可能簡單和清晰的方式來解釋它。 WebSocket 的原理 在理解 WebSocket 的工作原理之前,我們首先要瞭解 HTTP 協議的短連接性質。在傳統的 HTTP 通信中,客戶端發送一個請求到服務 ...
  • 一個變數如果聲明為聯合類型,而後續操作需要針對其具體的單一類型做不同處理,這個過程就叫做類型收窄(`Narrowing`) ...
  • 項目代碼同步至碼雲 weiz-vue3-template Vue Router 是 Vue.js 的官方路由。它與 Vue.js 核心深度集成,讓用 Vue.js 構建單頁應用變得輕而易舉。 1. 安裝 npm i vue-router@4 2. 集成 1. 新建兩頁面進行示例 在src/view下 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 函數創建與定義的過程 函數定義階段 在堆記憶體中開闢一段空間 把函數體內的代碼一模一樣的存儲在這段空間內 把空間賦值給棧記憶體的變數中 函數調用階段 按照變數名內的存儲地址找到堆記憶體中對應的存儲空間 在調用棧中開闢一個新的函數執行空間 在執行 ...
  • TS官方Handbook: TypeScript: Handbook - The TypeScript Handbook (typescriptlang.org) 基礎 相關概念 運行時錯誤:JS 的大多數錯誤都只能在運行的過程中被髮現。 靜態類型系統:TS 可以在運行代碼之前發現錯誤。 非異常失敗 ...
  • 無論你做什麼,都要相信自己可以做到,因為你的潛力是無限的。 把父組件的狀態變成屬性傳遞給子組件,子組件接受這個屬性,聽命於父組件。這個子組件就是叫做受控組件。在受控與非受控組件有兩種理解方案,第一:狹義上的受控與非受控,就是我們在表單中的受控與非受控組件。第二:廣義上的受控與非受控組件,就是 Rea ...
  • 不要因為別人的評價而改變自己的想法,因為你的生活是你自己的。 1. React 中 Ref 的應用 1.1 給標簽設置 ref 給標簽設置 ref,ref="username", 通過 this.refs.username 可以獲取引用的標簽,ref 可以獲取到應用的真實 Dom 節點。但是 thi ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...