• <td id="ui4cm"></td>
  • <bdo id="ui4cm"><legend id="ui4cm"></legend></bdo>
  • 當前位置:首頁 > 安卓源碼 > 技術博客 >

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    時間:2021-04-19 02:45 來源:互聯網 作者:源碼搜藏 瀏覽: 收藏 挑錯 推薦 打印

    前言 上一篇文章 主要從 Native 的角度分析了 React Native 的初始化流程,并從源碼出發,總結了幾個 React Native 容器初始化的優化點。本文主要從 JavaScript 入手,總結了一些 JS 側的優化要點。 1.JSEngine Hermes Hermes 是 FaceBook 2019 年中旬開源的

    前言

    上一篇文章主要從 Native 的角度分析了 React Native 的初始化流程,并從源碼出發,總結了幾個 React Native 容器初始化的優化點。本文主要從 JavaScript 入手,總結了一些 JS 側的優化要點。

    1.JSEngine

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    Hermes

    Hermes 是 FaceBook 2019 年中旬開源的一款 JS 引擎,從 release 記錄可以看出,這個是專為 React Native 打造的 JS 引擎,可以說從設計之初就是為 Hybrid UI 系統打造。

    Hermes 支持直接加載字節碼,也就是說,Babel、Minify、Parse 和 Compile 這些流程全部都在開發者電腦上完成,直接下發字節碼讓 Hermes 運行就行,這樣做可以省去 JSEngine 解析編譯 JavaScript 的流程,JS 代碼的加載速度將會大大加快,啟動速度也會有非常大的提升。

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    更多關于 Hermes 的特性,大家可以看我的舊文《移動端 JS 引擎哪家強》這篇文章,我做了更為詳細的特性說明與數據對比,這里就不多說了。

    2.JS Bundle

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    前面的優化其實都是 Native 層的優化,從這里開始就進入 Web 前端最熟悉的領域了。

    其實談到 JS Bundle 的優化,來來回回就是那么幾條路:

    • :縮小 Bundle 的總體積,減少 JS 加載和解析的時間
    • :動態導入(dynamic import),懶加載,按需加載,延遲執行
    • :拆分公共模塊和業務模塊,避免公共模塊重復引入

    如果有 webpack 打包優化經驗的小伙伴,看到上面的優化方式,是不是腦海中已經浮現出 webpack 的一些配置項了?不過 React Native 的打包工具不是 webpack 而是 Facebook 自研的 Metro,雖然配置細節不一樣,但道理是相通的,下面我就這幾個點講講 React Native 如何優化 JS Bundle。

    2.1 減小 JS Bundle 體積

    Metro 打包 JS 時,會把 ESM 模塊轉為 CommonJS 模塊,這就導致現在比較火的依賴于 ESM 的 Tree Shaking 完全不起作用,而且根據官方回復,Metro 未來也不會支持 Tree Shaking :

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    因為這個原因,我們減小 bundle 體積主要是三個方向:

    • 對于同樣的功能,優先選擇體積更小的第三方庫
    • 利用 babel 插件,避免全量引用
    • 制定編碼規范,減少重復代碼

    下面我們舉幾個例子來解釋上面的三個思路。

    2.1.0 使用 react-native-bundle-visualizer 查看包體積

    優化 bundle 文件前,一定要知道 bundle 里有些什么,最好的方式就是用可視化的方式把所有的依賴包列出來。web 開發中,可以借助 Webpack 的 webpack-bundle-analyzer 插件查看 bundle 的依賴大小分布,React Native 也有類似的工具,可以借助 react-native-bundle-visualizer 查看依賴關系:

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    使用非常簡單,按照文檔安裝分析就可。

     

    2.1.1 moment.js 替換為 day.js

    這是一個非常經典的例子。同樣是時間格式化的第三方庫, moment.js 體積 200 KB,day.js 體積只有 2KB,而且 API 與 moment.js 保持一致。如果項目里用了 moment.js,替換為 day.js 后可以立馬減少 JSBundle 的體積。

     

    2.1.2 lodah.js 配合 babel-plugin-lodash

    lodash 基本上屬于 Web 前端的工程標配了,但是對于大多數人來說,對于 lodash 封裝的近 300 個函數,只會用常用的幾個,例如 get、 chunk,為了這幾個函數全量引用還是有些浪費的。

    社區上面對這種場景,當然也有優化方案,比如說 lodash-es,以 ESM 的形式導出函數,再借助 Webpack 等工具的 Tree Sharking 優化,就可以只保留引用的文件。但是就如前面所說,React Native 的打包工具 Metro 不支持 Tree Shaking,所以對于 lodash-es 文件,其實還會全量引入,而且 lodash-es 的全量文件比 lodash 要大得多。

    我做了個簡單的測試,對于一個剛剛初始化的 React Native 應用,全量引入 lodash 后,包體積增大了 71.23KB,全量引入 lodash-es 后,包體積會擴大 173.85KB。

    既然 lodash-es 不適合在 RN 中用,我們就只能在 lodash 上想辦法了。lodash 其實還有一種用法,那就是直接引用單文件,例如想用 join 這個方法,我們可以這樣引用:

    // 全量
    import { join } from 'lodash'
    
    // 單文件引用
    import join from 'lodash/join'

    這樣打包的時候就會只打包 lodash/join 這一個文件。

    但是這樣做還是太麻煩了,比如說我們要使用 lodash 的七八個方法,那我們就要分別 import 七八次,非常的繁瑣。對于 lodash 這么熱門的工具庫,社區上肯定有高人安排好了,babel-plugin-lodash 這個 babel 插件,可以在 JS 編譯時操作 AST 做如下的自動轉換:

    import { join, chunk } from 'lodash'
    // ⬇️
    import join from 'lodash/join'
    import chunk from 'lodash/chunk'

    使用方式也很簡單,首先運行 yarn add babel-plugin-lodash -D 安裝,然后在 babel.config.js 文件里啟用插件即可:

    // babel.config.js
    
    module.exports = {
      plugins: ['lodash'],
      presets: ['module:metro-react-native-babel-preset'],
    };

    我以 join 這個方法為例,大家可以看一下各個方法增加的 JS Bundle 體積:

    全量 lodash 全量 loads-es lodash/join 單文件引用 lodash + babel-plugin-lodash
    71.23 KB 173.85 KB 119 Bytes 119 Bytes

    從表格可見 lodash 配合 babel-plugin-lodash 是最優的開發選擇。

     

    2.1.3 babel-plugin-import 的使用

    babel-plugin-lodash 只能轉換 lodash 的引用問題,其實社區還有一個非常實用的 babel 插件:babel-plugin-import,基本上它可以解決所有按需引用的問題。

    我舉個簡單的例子,阿里有個很好用的 ahooks 開源庫,封裝了很多常用的 React hooks,但問題是這個庫是針對 Web 平臺封裝的,比如說 useTitle 這個 hook,是用來設置網頁標題的,但是 React Native 平臺是沒有相關的 BOM API 的,所以這個 hooks 完全沒有必要引入,RN 也永遠用不到這個 API。

    這時候我們就可以用 babel-plugin-import 實現按需引用了,假設我們只要用到 useInterval 這個 Hooks,我們現在業務代碼中引入:

    import { useInterval } from 'ahooks'

    然后運行 yarn add babel-plugin-import -D 安裝插件,在 babel.config.js 文件里啟用插件:

    // babel.config.js
    
    module.exports = {
      plugins: [
        [
          'import',
          {
            libraryName: 'ahooks',
            camel2DashComponentName: false, // 是否需要駝峰轉短線
            camel2UnderlineComponentName: false, // 是否需要駝峰轉下劃線
          },
        ],
      ],
      presets: ['module:metro-react-native-babel-preset'],
    };

    啟用后就可以實現 ahooks 的按需引入:

    import { useInterval } from 'ahooks'
    // ⬇️
    import useInterval from 'ahooks/lib/useInterval'

    下面是各種情況下的 JSBundle 體積增量,綜合來看 babel-plugin-import 是最優的選擇:

    全量 ahooks ahooks/lib/useInterval 單文件引用 ahooks + babel-plugin-import
    111.41 KiB 443 Bytes 443 Bytes

    當然,babel-plugin-import 可以作用于很多的庫文件,比如說內部/第三方封裝的 UI 組件,基本上都可以通過babel-plugin-import 的配置項實現按需引入。若有需求,可以看網上其他人總結的使用經驗,我這里就不多言了。

     

    2.1.4 babel-plugin-transform-remove-console

    移除 console 的 babel 插件也很有用,我們可以配置它在打包發布的時候移除 console 語句,減小包體積的同時還會加快 JS 運行速度,我們只要安裝后再簡單的配置一下就好了:

    // babel.config.js
    
    module.exports = {
        presets: ['module:metro-react-native-babel-preset'],
        env: {
            production: {
                plugins: ['transform-remove-console'],
            },
        },
    };

     

    2.1.5 制定良好的編碼規范

    編碼規范的最佳實踐太多了,為了切合主題(減少代碼體積),我就隨便舉幾點:

    • 代碼的抽象和復用:代碼中重復的邏輯根據可復用程度,盡量抽象為一個方法,不要用一次復制一次
    • 刪除無效的邏輯:這個也很常見,隨著業務的迭代,很多代碼都不會用了,如果某個功能下線了,就直接刪掉,哪天要用到再從 git 記錄里找
    • 刪除冗余的樣式:例如引入 ESLint plugin for React Native,開啟 "react-native/no-unused-styles" 選項,借助 ESLint 提示無效的樣式文件

    說實話這幾個優化其實減少不了幾 KB 的代碼,更大的價值在于提升項目的健壯性和可維護性。

    2.2 Inline Requires

    Inline Requires 可以理解為懶執行,注意我這里說的不是懶加載,因為一般情況下,RN 容器初始化之后會全量加載解析 JS Bundle 文件,Inline Requires 的作用是延遲運行,也就是說只有需要使用的時候才會執行 JS 代碼,而不是啟動的時候就執行。React Native 0.64 版本里,默認開啟了 Inline Requires 。

    首先我們要在 metro.config.js 里確認開啟了 Inline Requires 功能:

    // metro.config.js
    
    module.exports = {
      transformer: {
        getTransformOptions: async () => ({
          transform: {
            experimentalImportSupport: false,
            inlineRequires: true, // <-- here
          },
        }),
      },
    };

    其實 Inline Requires 的原理非常簡單,就是把 require 導入的位置改變了一下。

    比如說我們寫了個工具函數 join 放在 utils.js 文件里:

    // utils.js
    
    export function join(list, j) {
      return list.join(j);
    }

    然后我們在 App.js 里 import 這個庫:

    // App.js
    
    import { join } from 'my-module';
    
    const App = (props) => {
      const result = join(['a', 'b', 'c'], '~');
    
      return <Text>{result}</Text>;
    };

    上面的寫法,被 Metro 編譯后,相當于編譯成下面的樣子:

    const App = (props) => {
      const result = require('./utils').join(['a', 'b', 'c'], '~');
    
      return <Text>{result}</Text>;
    };

    實際編譯后的代碼其實長這個樣子:

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    上圖紅線中的 r() 函數,其實是 RN 自己封裝的 require() 函數,可以看出 Metro 自動把頂層的 import 移動到使用的位置。

    值得注意的是,Metro 的自動 Inline Requires 配置,目前是不支持 export default 導出的,也就是說,如果你的 join 函數是這樣寫的:

    export default function join(list, j) {
      return list.join(j);
    }

    導入時是這樣的:

    import join from './utils';
    
    const App = (props) => {
      const result = join(['a', 'b', 'c'], '~');
    
      return <Text>{result}</Text>;
    };

    Metro 編譯轉換后的代碼,對應的 import 還是處于函數頂層

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    這個需要特別注意一下,社區也有相關的文章,呼吁大家不要用 export default 這個語法,感興趣的可以了解一下:

    深入解析 ES Module(一):禁用 export default object

    深入解析 ES Module(二):徹底禁用 default export

    2.3 JSBundle 分包加載

    分包的場景一般出現在 Native 為主,React Native 為輔的場景里。這種場景往往是這樣的:

    • 假設有兩條基于 RN 的業務線 A 和 B,它們的 JSBundle 都是動態下發的
    • A 的 JSBundle 大小為 700KB,其中包括 600KB 的基礎包(React,React Native 的 JS 代碼)和 100KB 的業務代碼
    • A 的 JSBundle 大小為 800KB,其中包括 600KB 的基礎包和 200KB 的業務代碼
    • 每次從 Native 跳轉到 A/B 的 RN 容器,都要全量下載解析運行

    大家從上面的例子里可以看出,600KB 的基礎包在多條業務線里是重復的,完全沒有必要多次下載和加載,這時候一個想法自然而然就出來了:

    把一些共有庫打包到一個 common.bundle 文件里,我們每次只要動態下發業務包 businessA.bundle 和 businessB.bundle,然后在客戶端實現先加載 common.bundle 文件,再加載 business.bundle 文件就可以了

    這樣做的好處有幾個:

    • common.bundle 可以直接放在本地,省去多業務線的多次下載,節省流量和帶寬
    • 可以在 RN 容器預初始化的時候就加載 common.bundle ,二次加載的業務包體積更小,初始化速度更快

    順著上面的思路,上面問題就會轉換為兩個小問題:

    • 如何實現 JSBundle 的拆包?
    • iOS/Android 的 RN 容器如何實現多 bundle 加載?

     

    2.3.1 JS Bundle 拆包

    拆包之前要先了解一下 Metro 這個打包工具的工作流程。Metro 的打包流程很簡單,只有三個步驟:

    • Resolution:可以簡單理解為分析各個模塊的依賴關系,最后會生成一個依賴圖
    • Transformation:代碼的編譯轉換,主要是借助 Babel 的編譯轉換能力
    • Serialization:所有代碼轉換完畢后,打印轉換后的代碼,生成一個或者多個 bundle 文件

    從上面流程可以看出,我們的拆包步驟只會在 Serialization 這一步。我們只要借助 Serialization 暴露的各個方法就可以實現 bundle 分包了。

    正式分包前,我們先拋開各種技術細節,把問題簡化一下:對于一個全是數字的數組,如何把它分為偶數數組和奇數數組?

    這個問題太簡單了,剛學編程的人應該都能想到答案,遍歷一遍原數組,如果當前元素是奇數,就放到奇數數組里,如果是偶數,放偶數數組里。

    Metro 對 JS bundle 分包其實是一個道理。Metro 打包的時候,會給每個模塊設置 moduleId,這個 id 就是一個從 0 開始的自增 number。我們分包的時候,公有的模塊(例如 react react-native)輸出到 common.bundle,業務模塊輸出到 business.bundle 就行了。

    因為要兼顧多條業務線,現在業內主流的分包方案是這樣的:

    1.先建立一個 common.js 文件,里面引入了所有的公有模塊,然后 Metro 以這個 common.js 為入口文件,打一個 common.bundle 文件,同時要記錄所有的公有模塊的 moduleId

    // common.js
    
    require('react');
    require('react-native');
    ......

    2. 對業務線 A 進行打包,Metro 的打包入口文件就是 A 的項目入口文件。打包過程中要過濾掉上一步記錄的公有模塊 moduleId,這樣打包結果就只有 A 的業務代碼了

    // indexA.js
    
    import {AppRegistry} from 'react-native';
    import BusinessA from './BusinessA';
    import {name as appName} from './app.json';
    
    AppRegistry.registerComponent(appName, () => BusinessA);

    3. 業務線 B C D E...... 打包流程同業務線 A

     

    上面的思路看起來很美好,但是還是存在一個問題:每次啟動 Metro 打包的時候,moduleId 都是從 0 開始自增,這樣會導致不同的 JSBundle ID 重復。

    為了避免 id 重復,目前業內主流的做法是把模塊的路徑當作 moduleId(因為模塊的路徑基本上是固定且不沖突的),這樣就解決了 id 沖突的問題。Metro 暴露了 createModuleIdFactory 這個函數,我們可以在這個函數里覆蓋原來的自增 number 邏輯:

    module.exports = {
      serializer: {
        createModuleIdFactory: function () {
          return function (path) {
            // 根據文件的相對路徑構建 ModuleId
            const projectRootPath = __dirname;
            let moduleId = path.substr(projectRootPath.length + 1);
            return moduleId;
          };
        },
      },
    };

     

    整合一下第一步的思路,就可以構建出下面的 metro.common.config.js 配置文件:

    // metro.common.config.js
    
    const fs = require('fs');
    
    module.exports = {
      transformer: {
        getTransformOptions: async () => ({
          transform: {
            experimentalImportSupport: false,
            inlineRequires: true,
          },
        }),
      },
      serializer: {
        createModuleIdFactory: function () {
          return function (path) {
            // 根據文件的相對路徑構建 ModuleId
            const projectRootPath = __dirname;
            let moduleId = path.substr(projectRootPath.length + 1);
            
            // 把 moduleId 寫入 idList.txt 文件,記錄公有模塊 id
            fs.appendFileSync('./idList.txt', `${moduleId}\n`);
            return moduleId;
          };
        },
      },
    };

    然后運行命令行命令打包即可:

    # 打包平臺:android
    # 打包配置文件:metro.common.config.js
    # 打包入口文件:common.js
    # 輸出路徑:bundle/common.android.bundle
    
    npx react-native bundle --platform android --config metro.common.config.js --dev false --entry-file common.js --bundle-output bundle/common.android.bundle

    通過以上命令的打包,我們可以看到 moduleId 都轉換為了相對路徑,并且 idList.txt 也記錄了所有的 moduleId:

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

     

    第二步的關鍵在于過濾公有模塊的 moduleId,Metro 提供了 processModuleFilter 這個方法,借助它可以實現模塊的過濾。具體的邏輯可見以下代碼:

    // metro.business.config.js
    
    const fs = require('fs');
    
    // 讀取 idList.txt,轉換為數組
    const idList = fs.readFileSync('./idList.txt', 'utf8').toString().split('\n');
    
    function createModuleId(path) {
      const projectRootPath = __dirname;
      let moduleId = path.substr(projectRootPath.length + 1);
      return moduleId;
    }
    
    module.exports = {
      transformer: {
        getTransformOptions: async () => ({
          transform: {
            experimentalImportSupport: false,
            inlineRequires: true,
          },
        }),
      },
      serializer: {
        createModuleIdFactory: function () {
          // createModuleId 的邏輯和 metro.common.config.js 完全一樣
          return createModuleId;
        },
        processModuleFilter: function (modules) {
          const mouduleId = createModuleId(modules.path);
          
          // 通過 mouduleId 過濾在 common.bundle 里的數據
          if (idList.indexOf(mouduleId) < 0) {
            console.log('createModuleIdFactory path', mouduleId);
            return true;
          }
          return false;
        },
      },
    };

    最后運行命令行命令打包即可:

    # 打包平臺:android
    # 打包配置文件:metro.business.config.js
    # 打包入口文件:index.js
    # 輸出路徑:bundle/business.android.bundle
    
    npx react-native bundle --platform android --config metro.business.config.js --dev false --entry-file index.js --bundle-output bundle/business.android.bundle

    最后的打包結果只有 11 行(不分包的話得 398 行),可以看出分包的收益非常大。

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    當然使用相對路徑作為 moduleId 打包時,不可避免的會導致包體積變大,我們可以使用 md5 計算一下相對路徑,然后取前幾位作為最后的 moduleId;或者還是采用遞增 id,只不過使用更復雜的映射算法來保證 moduleId 的唯一性和穩定性。這部分的內容其實屬于非常經典的 Map key 設計問題,感興趣的讀者可以了解學習一下相關的算法理論知識。

     

    2.3.2 Native 實現多 bundle 加載

    分包只是第一步,想要展示完整正確的 RN 界面,還需要做到「合」,這個「合」就是指在 Native 端實現多 bundle 的加載。

    common.bundle 的加載比較容易,直接在 RN 容器初始化的時候加載就好了。容器初始化的流程上一節我已經詳細介紹了,這里就不多言了。這時候問題就轉換為 business.bundle 的加載問題。

    React Native 不像瀏覽器的多 bundle 加載,直接動態生成一個 <script /> 標簽插入 HTML 中就可以實現動態加載了。我們需要結合具體的 RN 容器實現來實現 business.bundle 加載的需求。這時候我們需要關注兩個點:

    1. 時機:什么時候開始加載?
    2. 方法:如何加載新的 bundle?

     

    對于第一個問題,我們的答案是 common.bundle 加載完成后再加載 business.bundle。

    common.bundle 加載完成后,iOS 端會發送事件名稱是 RCTJavaScriptDidLoadNotification 的全局通知,Android 端則會向 ReactInstanceManager 實例中注冊的所有 ReactInstanceEventListener 回調 onReactContextInitialized() 方法。我們在對應事件監聽器和回調中實現業務包的加載即可。

     

    對于第二個問題,iOS 我們可以使用 RCTCxxBridge 的 executeSourceCode 方法在當前的 RN 實例上下文中執行一段 JS 代碼,以此來達到增量加載的目的。不過值得注意的是,executeSourceCode 是 RCTCxxBridge 的私有方法,需要我們用 Category 將其暴露出來。

    Android 端可以使用剛剛建立好的 ReactInstanceManager 實例,通過 getCurrentReactContext() 獲取到當前的 ReactContext 上下文對象,再調用上下文對象的 getCatalystInstance() 方法獲取媒介實例,最終調用媒介實例的 loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) 方法完成業務 JSBundle 的增量加載。

    iOS 和 Android 的示例代碼如下:

    NSURL *businessBundleURI = // 業務包 URI
    NSError *error = nil;
    NSData *sourceData = [NSData dataWithContentsOfURL:businessBundleURI options:NSDataReadingMappedIfSafe error:&error];
    if (error) { return }
    [bridge.batchedBridge executeSourceCode:sourceData sync:NO]
    ReactContext context = RNHost.getReactInstanceManager().getCurrentReactContext();
    CatalystInstance catalyst = context.getCatalystInstance();
    String fileName = "businessBundleURI"
    catalyst.loadScriptFromFile(fileName, fileName, false);

     


    本小節的示例代碼都屬于 demo 級別,如果想要真正接入生產環境,需要結合實際的架構和業務場景做定制。有一個 React Native 分包倉庫 react-native-multibundler 內容挺不錯的,大家可以參考學習一下。

    3.Network

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    我們一般會在 React Component 的 componentDidMount() 執行后請求網絡,從服務器獲取數據,然后再改變 Component 的 state 進行數據的渲染。

    網絡優化是一個非常龐大非常獨立的話題,有非常多的點可以優化,我這里列舉幾個和首屏加載相關的網絡優化點:

    • DNS 緩存:提前緩存 IP 地址,跳過 DNS 尋址時間
    • 緩存復用:進入 RN 頁面前,先提前請求網絡數據并緩存下來,打開 RN 頁面后請求網絡前先檢查緩存數據,如果緩存未過期,直接從本地緩存里拿數據
    • 請求合并:如果還在用 HTTP/1.1,若首屏有多個請求,可以合并多個請求為一個請求
    • HTTP2:利用 HTTP2 的并行請求和多路復用優化速度
    • 減小體積:去除接口的冗余字段,減少圖片資源的體積等等
    • ......

    由于網絡這里相對來說比較獨立,iOS/Android/Web 的優化經驗其實都可以用到 RN 上,這里按照大家以往的優化經驗來就可以了。

    4.Render

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    渲染這里的耗時,基本上和首屏頁面的 UI 復雜度成正相關?梢酝ㄟ^渲染流程查看哪里會出現耗時:

    • VDOM 計算:頁面復雜度越高,JavaScript 側的計算耗時就會越長(VDOM 的生成與 Diff)
    • JS Native 通訊:JS 的計算結果會轉為 JSON 通過 Bridge 傳遞給 Native 側,復雜度越高,JSON 的數據量越大,有可能阻塞 Bridge 通訊
    • Native 渲染:Native 側遞歸解析 render tree,布局越復雜,渲染時間越長

    我們可以在代碼里開啟 MessageQueue 監視,看看 APP 啟動后 JS Bridge 上面有有些啥:

    // index.js
    
    import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'
    MessageQueue.spy(true);

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】

    從圖片里可以看出 JS 加載完畢后有大量和 UI 相關的 UIManager.createView() UIManager.setChildren() 通訊,結合上面的耗時總結,我們對應著就有幾條解決方案:

    • 通過一定的布局技巧降低 UI 嵌套層級,降低 UI 視圖的復雜度
    • 減少 re-render,直接在 JS 側截斷重繪流程,減少 bridge 通訊的頻率和數據量
    • 如果是 React Native 為主架構的 APP,首屏可以直接替換為 Native View,直接脫離 RN 的渲染流程

    上面的這些技巧我都在舊文《React Native 性能優化指南——渲染篇》里做了詳細的解釋,這里就不多解釋了。

    Fraic

    從上面的我們可以看出,React Native 的渲染需要在 Bridge 上傳遞大量的 JSON 數據,在 React Native 初始化時,數據量過大會阻塞 bridge,拖慢我們的啟動和渲染速度。React Native 新架構中的 Fraic 就能解決這一問題,JS 和 Native UI 不再是異步的通訊,可以實現直接的調用,可以大大加速渲染性能。

    Fraic 可以說是 RN 新架構里最讓人期待的了,想了解更多內容,可以去官方 issues 區圍觀。

    總結

    本文主要從 JavaScript 的角度出發,分析了 Hermes 引擎的特點和作用,并總結分析了 JSBundle 的各種優化手段,再結合網絡和渲染優化,全方位提升 React Native 應用的啟動速度。

    React Native 啟動速度優化——JS 篇【全網最全,值得收藏】 轉載http://www.robbiejoe.com/appboke/52911.html
    下一篇:沒有了

    技術博客閱讀排行

    最新文章

    亚洲AV无码一区东京热
  • <td id="ui4cm"></td>
  • <bdo id="ui4cm"><legend id="ui4cm"></legend></bdo>