reactNative性能優化

来源:https://www.cnblogs.com/floor/archive/2019/12/31/12127769.html
-Advertisement-
Play Games

本文將簡單介紹一下我所收集到的React Native應用優化方法,希望對你有所啟發。很多方法也是適用React web應用的。 ...


頭圖

本文將簡單介紹一下我所收集到的React Native應用優化方法,希望對你有所啟發。很多方法也是適用React web應用的。

包體積優化

無論是熱更新方案走網路下載js,還是直接將js打進apk,減小js bundle體積都很必要。

走網路的js體積大影響首次載入速度,打進apk的增加包體積。

  1. 壓縮

為了測試,直接使用react-native init命令生成了一個rn工程,將其中的App.js改為下麵這樣簡單的代碼,驗證這樣簡單的代碼打包生成的js bundle體積情況。

import React from 'react';
import {
  Text,
} from 'react-native';

const App: () => React$Node = () => {
  return (
    <Text>1</Text>
  )

};

export default App;

使用 下邊的命令可以打包js bundle

# 非壓縮

react-native bundle --entry-file ./index.js --bundle-output ./testBundle.js --dev true
# 壓縮

react-native bundle --entry-file ./index.js --bundle-output ./testBundle_min.js --dev false


# 查看體積

ls -h testBundle.js testBundle_min.js 

-rw-r--r--  1 bingxiongyi  staff   3.3M Dec 24 11:02 testBundle.js
-rw-r--r--  1 bingxiongyi  staff   629K Dec 24 11:06 testBundle_min.js

可見不壓縮的體積遠遠大於壓縮後的體積,因此壓縮十分必要。將js打包進apk的,預設就會壓縮。

關鍵要註意的是走網路下載js的這種方式需要壓縮js.

  1. 包拆分

上邊可以看到一個空工程打包出來的js就壓縮後有629k, 他們主要是react-native,react這些框架的代碼。

如果是純的react-native應用,我們可能會直接用react-navigation做路由,這個應用就是生成一個js bundle,打到apk, 框架部分的代碼不會重覆生成,自然沒有什麼問題。

但是大多情況為了實現熱更新,react-native是嵌入到原生app中的,可能一個頁面就會是一個js bundle, 走網路下載,如果每個js bundle都包含框架的代碼,必然造成不必要的重覆下載。

因此,需要將框架和不常變動的代碼單獨抽取,最好是將這部分代碼打進apk, 這樣業務代碼體積會大大減小。實際發現我們一個包含三個複雜頁面的js bundle業務代碼壓縮體積僅僅180k多。

關於如何拆包可參考React native 拆包

render次數優化

為什麼要減少render次數

走到了render並不一定會有真實dom的操作,但是一定會走一次dom diff。react大名鼎鼎的diff演算法確實高效,看了一眼vitual-dom原理與簡單實現,確實像大佬們說的,是個O(n)的演算法。

需要更新dom時去算diff無可厚非,但是diff了半天發現無差別,不需要更新就白瞎了,O(n)雖好,做不必要的O(n)也是一件浪費資源的事,因此需要儘可能減少diff次數,也就是減少render次數。

如何減少render的次數

  1. 減少setState的次數

需要說明的是setState可以是批量的,可能多次setState只會有一次render; 也可以是setState一次就render一次。

  • 批量:在合成事件, 生命周期函數中的setState是非同步批量更新的, 多次setState只會走一次render
  • 非批量:在setTimeOut, setInterval, 原生事件, Promise中的setState是同步逐個更新的, 而且每次setState都會走一次render

因此減少setState次數需要減少的是非批量的情形,在合成事件,比如onClick裡邊去多次setState是沒有關係的。

舉個例子:

通常寫一個上拉載入的列表會像下邊這樣,每次請求開始時將狀態置為載入中。但是可能進頁面其實就是載入中的狀態了。

 getList(pageNum) {
    this.setState({
      status: 1, // 1 loading, 2 success, 3 error
    });
    fetchList(pageNum)
      .then(res => {
        this.setState({
          data: [...this.statedata, ...res],
        });
      })
      .catch(e => {
        console.log(e);
      });
  }

這樣存在的一個問題是第一頁會多一次不必要的setState, 因為初始狀態一般就是載入中,當你使用Component而且沒有重寫shouldComponentUpdate時,這會導致一次不必要的render的。改成下邊這樣就ok了。

  getList(pageNum) {
    // 加上這樣一個判斷
    if (this.state.status !== 1) {
      this.setState({
        status: 1,
      });
    }
    fetchList(pageNum)
      .then(res => {
        this.setState({
          data: [...this.state.data, ...res],
        });
      })
      .catch(e => {
        console.log(e);
      });
  }

第一段代碼首屏會有3次render, 第二段代碼只有兩次。

  1. 使用PureComponent

Component是每次setState都會去render, 即使你setState的值和之前相同,也會render。

PureComponent重寫了shouldComponentUpdate,在裡邊做了一次淺比較,如果setState後新state和舊state相同是不會走render的。

潛在的問題是當你把state里一個對象的某個屬性值改了,由於淺比較是相等的,所以不會走render, 造成顯示異常,這個註意下就行。

舉個例子

還是上邊這個,使用會render 3次的方案,但是使用PureComponent。

class App extends React.PureComponent{}

可以發現也只render了兩次。這是因為我們的那次多餘的setState set的是和原來相同的值,淺比較相等,所以沒有render。

  1. 重寫shouldComponentUpdate

這裡有一個react生命周期的圖, 從圖中可以看出更新的時候每次render之前都會走shouldComponentUpdate, 當shouldComponentUpdate返回false的時候就不會render了,因此我們可以在shouldComponentUpdate中合理控制是否render.

同上,使用會render 3次的方案。

重寫一下shouldComponentUpdate, 也來做一個淺比較。

  shouldComponentUpdate(nextProps, nextState) {
    for (let key in nextState) {
      if (nextState[key] !== this.state[key]) {
        return true;
      }
    }
    for (let key in nextProps) {
      if (nextProps[key] !== this.props[key]) {
        return true;
      }
    }
    return false;
  }

發現這樣做後也只render了2次。

  1. 減少
    diff代價(這個不是減少次數的)

將state的儘量放在更小的組件中,這樣render時計算diff的成本更小一些.

舉個例子

import React from 'react';
import { Text, Button, View } from 'react-native';

class SubComponent extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  state = {
    text: 'A',
  };

  onPress = () => {
    this.setState({
      text: 'B',
    });
  };

  render() {
    console.log('SubComponent render');
    return <Button title={this.state.text} onPress={this.onPress} />;
  }
}

class SubComponent2 extends React.Component {
  render() {
    console.log('SubComponent2 render');
    return (
      <Text>F</Text>
    )
  }
}


function FunctionComponent() {
  console.log('FunctionComponent excuted')
  return <Text>G</Text>
}

class App extends React.Component {
  constructor(props, context) {
    super(props, context);
  }

  state = {
    text: 'D',
  }

  onPress = () => {
    this.setState({
      text: 'E'
    })
  }

  render() {
    console.log('App render');
    return (
      <View>
        <Button title={this.state.text} onPress={this.onPress} color="red"/>
        <SubComponent />
        <SubComponent2 />
        <FunctionComponent />
      </View>
    )
  }
}

export default App;

點紅色按鈕執行結果如下

App render
App.js:20 SubComponent render
App.js:27 SubComponent2 render
App.js:36 FunctionComponent excuted

點藍色按鈕執行結果如下

SubComponent render

若父組件內狀態變更,則他和他所有子組件都會render, 而子組件的變更則不會影響到父組件和兄弟組件。因此在實際開發中應該儘量減小state的作用範圍,如果一個狀態能收斂到組件內部,就不應該放在外邊。這樣可以降低render操作的代價。

動畫優化

  1. 啟用原生動畫渲染

Animated的 API 是可序列化的(即可轉化為字元串表達以便通信或存儲)。通過啟用原生驅動,我們在啟動動畫前就把其所有配置信息都發送到原生端,利用原生代碼在 UI 線程執行動畫,而不用每一幀都在兩端間來回溝通。如此一來,動畫一開始就完全脫離了 JS 線程,因此此時即便 JS 線程被卡住,也不會影響到動畫了。(來自reactnative中文文檔)

Animated.timing(this.state.animatedValue, {
  toValue: 1,
  duration: 500,
  useNativeDriver: true // <-- 加上這一行
}).start();
  1. setNativeProps

setNativeProps方法可以使我們直接修改基於原生視圖的組件的屬性,而不需要使用setState來重新渲染整個組件樹。

如果我們要更新的組件有一個非常深的內嵌結構,並且沒有使用shouldComponentUpdate來優化,那麼使用setNativeProps就將大有裨益。
(來自reactnative中文文檔)

  1. InteractionManager

Interactionmanager 可以將一些耗時較長的工作安排到所有互動或動畫完成之後再進行。這樣可以保證 JavaScript 動畫的流暢運行。

如果動畫執行期間可能有比較耗時的代碼可以如下操作

InteractionManager.runAfterInteractions(() => {
  // 把耗時比較多的代碼放到這裡,防止他們影響動畫效果
});

過渡繪製優化

過度繪製(Overdraw)描述的是屏幕上的某個像素在同一幀的時間內被繪製了多次。在多層次重疊的 UI 結構裡面,如果不可見的 UI 也在做繪製的操作,會導致某些像素區域被繪製了多次,同時也會浪費大量的 CPU 以及 GPU 資源。

在rn應用開發中解決過度繪製問題方法如下:

子組件能將父組件占滿的情況下不要父組件背景色,而是指定子組件背景色

這裡可以看個例子來瞭解為什麼會這樣

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  bg: {
    backgroundColor: '#aaa',
  },
  text: {
    padding: 30,
  }
})

export default class App extends React.PureComponent {

  render() {
    return (
      <View>
        <Text style={styles.text}>0次</Text>
        <View style={styles.bg}>
          <Text style={styles.text}>1次</Text>
          <View style={styles.bg}>
            <Text style={styles.text}>2次</Text>
            <View style={styles.bg}>
              <Text style={styles.text}>3次</Text>
              <View style={styles.bg}>
                <Text style={styles.text}>4次</Text>
              </View>
            </View>
          </View>
        </View>
      </View>
    );
  }
}

這個例子中我們不斷的疊加顏色,這樣就會產生過度繪製問題,我們可以在開發者選項開啟"顯示過度繪製"查看效果,如下

過度繪製演示

從這個例子可以看出我們應該儘量減少不必要的背景疊加來減少過度繪製。

小結

本文的優化方法基本都是從前輩大佬文章借鑒來的,部分方法做了一些小測試,大多沒有。有一些方法也是適用react web應用的。

寫的比較粗,後邊可能會對這些優化點逐一測試,做一些數據上的支撐,實實在在看看這些方法對性能有何種提升。使用的工具應該會是騰訊的性能狗, 非常容易用,大家感興趣也可以先測測。

參考文章


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

-Advertisement-
Play Games
更多相關文章
  • 因為客戶最近有一臺CentOS7的虛擬機,但是沒有聯網,需要安裝離線安裝PostgreSQL 1、首先去官網下載離線安裝包 https://www.postgresql.org/download/ 說明:可以點擊相應系統下載,也可以點擊左邊導航source下載tar.gz 選擇網頁下端的 我此次安裝 ...
  • 1. 集群搭建之主從複製 MySQL主從複製是一個非同步的複製過程,主庫發送更新事件到從庫,從庫讀取更新記錄,並執行更新記錄,使得從庫的內容與主庫保持一致。 1.1 主伺服器的配置 1.1.1 第一步:修改my.conf文件: 在[mysqld]段下添加: #啟用二進位日誌 log-bin=mysql ...
  • 資料庫瞭解 概念 資料庫就是一種特殊的文件,其中存儲著需要的數據 一個資料庫可以有多張表 MySQL是一種關係型資料庫 具有關聯性數據的就是關係型資料庫 MySQL是一種軟體可以用來創建mysql資料庫 MySQL也是C/S構架(底層TCP) MySQL客戶端 客戶端連接服務端使用TCP協議連接 使 ...
  • 本文源碼: "GitHub·點這裡" || "GitEE·點這裡" 一、系統封裝函數 MySQL 有很多內置的函數,可以快速解決開發中的一些業務需求,大概包括流程式控制制函數,數值型函數、字元串型函數、日期時間函數、聚合函數等。以下列出了這些分類中常用的函數。 1、控制流程函數 case...when ...
  • 最近學習c++,看到很多常用的例子,比如文件切割,切割後尾碼可以自定義,別人就無法從錶面的一個文件看出是什麼,也無法查看到原文件信息,只有合併後才能識別這廬山真面目 實現也比較粗暴,首先在應用層定義好兩個方法 先是文件分割,寫一個測試方法,去調用定義好的 fileSplit ,傳入生成的路徑,自定義 ...
  • 之前學習了3.Android-ADT之helloworld項目結構介紹後,本章便來寫個簡單的電話撥號器程式. 實現的步驟如下所示: 1.創建項目 2.寫layout/activity_main.xml佈局文件(畫ui),實現顯示效果 3.寫MainActivity.java代碼,實現具體邏輯功能 4 ...
  • [1]概述 [2]git 配置 [3]git 基礎操作 [4]git 查看 [5]git 版本切換 [6]git 分支管理 [7]git 遠程倉庫與分支 [8]git 其他操作 [9]註意事項 [10]git 常用命令 ...
  • 案例:隨機小方塊 產生隨機數對象,自調用構造函數 產生小方塊對象,自調用構造函數,裡面存放: 食物的構造函數 給原型對象添加方法:初始化小方塊的顯示的效果及位置 顯示地圖上 給原型對象添加方法,產生隨機位置 實例化對象 <!DOCTYPE html> <html lang="en"> <head> ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...