我的编程空间,编程开发者的网络收藏夹
学习永远不晚

React Native中怎么实现动态导入

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

React Native中怎么实现动态导入

这篇文章主要介绍“React Native中怎么实现动态导入”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“React Native中怎么实现动态导入”文章能帮助大家解决问题。

    背景

    随着业务的发展,每一个 React Native 应用的代码数量都在不断增加,bundle 体积不断膨胀,对应用性能的负面影响愈发明显。虽然我们可以通过 React Native 官方工具 Metro 进行拆包处理,拆分为一个基础包和一个业务包进行一定程度上的优化,但对日益增长的业务代码也无能为力,我们迫切地需要一套方案来减小我们 React Native 应用的体积。

    多业务包

    第一个想到的就是拆分多业务包,既然拆分为一个业务包不够,那我多拆为几个业务包不就可以了。当一个 React Native 应用拆分为多个业务包之后其实就相当于拆分为多个应用了,只不过代码在同一仓库里。这虽然可以解决单个应用不断膨胀的问题,但是有不少局限性。接下来一一分析:

    • 链接替换,不同的应用需要不同的地址,替换成本较高。

    • 页面之间通信,之前是个单页应用,不同页面之间可以直接通信;拆分之后是不同应用相互通信需要借助客户端桥接实现。

    • 性能损耗,打开每个拆分的业务包都需要单独起一个 React Native 容器,容器初始化、维持都需要消耗内存、占用CPU。

    • 粒度不够,最小的维度也是页面,无法继续对页面中的组件进行拆分。

    • 重复打包,部分在不同页面之间共享的工具库,每个业务包都会包含。

    • 打包效率,每一个业务包的打包过程,都要经过一遍完整的 Metro 打包过程,拆分多个业务包打包时间成倍增加。

    动态导入

    作为一个前端想到的另一方案自然就是动态导入(Dynamic import)了,基于其动态特性对于多业务包的众多缺点,此方案都可避免。此外拥有了动态导入我们就可以实现页面按需加载,组件懒加载等等能力。但是 Metro 官方并不支持动态导入,因此需要对 Metro 进行深度定制,这也是本文即将介绍的在 React Native 中实现动态导入。

    Metro 打包原理

    在介绍具体方案之前我们先看下 Metro 的打包机制及其构建产物。

    打包过程

    如下图所示Metro打包会经过三个阶段,分别是 Resolution、Transformation、Serialization。

    React Native中怎么实现动态导入

    image

    Resolution 的作用是从入口开始构建依赖图;Transformation 是和 Resolution 阶段同时执行的,其目的是将所有 module(一个模块就是一个 module ) 转换为目标平台可识别语言,这里面既有高级 JavaCript 语法的转换(依赖 BaBel),也有对特定平台,比如安卓的特殊 polyfills。这两个阶段主要是生产中间产物 IR 为最后一阶段所消费。

    Serialization 则是将所有 module 组合起来生成 bundle,这里需要特别注意 Metro API 文档中 Serializer Options 中的两个配置:

    • 签名为 createModuleIdFactory, type 为 () => (path: string) => number。 这个函数为每个 module 生成一个唯一的 moduleId,默认情况下是自增的数字。所有的依赖关系都依仗此 moduleId。

    • 签名为 processModuleFilter, type 为 (module: Array) => boolean。这个函数用来过滤模块,决定是否打入 bundle。

    bundle 分析

    一个 React Native 典型的 bundle 从上到下可以分为三个部分:

    • 第一部分为 polyfills,主要是一些全局变量如 DEV;以及通过 IIFE 声明的一些重要全局函数,如: __d、 __r 等;

    • 第二部分是各个 module 的定义,以 __d 开头,业务代码全部在这一块;

    • 第三部分是应用的初始化 __r(react-native/Libraries/Core/InitializeCore.js moduleId) 和 __r(${入口 moduleId})
      我们看下具体函数的分析

    __d函数

    function define(factory, moduleId, dependencyMap) {    const mod = {        dependencyMap,        factory,        hasError: false,        importedAll: EMPTY,        importedDefault: EMPTY,        isInitialized: false,        publicModule: {            exports: {}        }    };    modules[moduleId] = mod;}

    __d 其实就是 define 函数,可以看到其实现很简单,做的就是声明一个 mode,同时 moduleId 与 mode 做了一层映射,这样通过 moduleId 就可以拿到 module 实现。我们看下 __d 如何使用:

    __d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {    var _reactNative = _$$_REQUIRE(_dependencyMap[0], "react-native");    var _reactNavigation = _$$_REQUIRE(_dependencyMap[1], "react-navigation");    var _reactNavigationStack = _$$_REQUIRE(_dependencyMap[2], "react-navigation-stack");    var _routes = _$$_REQUIRE(_dependencyMap[3], "./class="lazy" data-src/routes");    var _appJson = _$$_REQUIRE(_dependencyMap[4], "./appJson.json");    var AppNavigator = (0, _reactNavigationStack.createStackNavigator)(_routes.RouteConfig, (0, _routes.InitConfig)());    var AppContiner = (0, _reactNavigation.createAppContainer)(AppNavigator);    _reactNative.AppRegistry.registerComponent(_appJson.name, function () {        return AppContiner;    });}, 0, [1, 552, 636, 664, 698], "index.android.js");

    这是 __d 的唯一用处,定义一个 module。这里解释下入参,第一个是个函数,就是 module 的工厂函数,所有的业务逻辑都在这里面,其是在 __r 之后调用的;第二个是 moduleId,模块的唯一标识;第三部分是其依赖的模块的 moduleId;第四个是此模块的文件名称。

    __r函数

    function metroRequire(moduleId) {    ...    const moduleIdReallyIsNumber = moduleId;    const module = modules[moduleIdReallyIsNumber];    return module && module.isInitialized        ? module.publicModule.exports        : guardedLoadModule(moduleIdReallyIsNumber, module);}function guardedLoadModule(moduleId, module) {    ...    return loadModuleImplementation(moduleId, module);}function loadModuleImplementation(moduleId, module) {    ...    const moduleObject = module.publicModule;    moduleObject.id = moduleId;    factory(        global,        metroRequire,        metroImportDefault,        metroImportAll,        moduleObject,        moduleObject.exports,        dependencyMap    );     return moduleObject.exports;    ...}

    __r 其实就是 require 函数。如上精简后的代码所示,require 方法首先判断所要加载的模块是否已经存在并初始化完成,若是则直接返回模块,否则调用 guardedLoadModule 方法,最终调用的是 loadModuleImplementation 方法。loadModuleImplementation 方法获得模块定义时传入的 factory 方法并调用,最后返回。

    方案设计

    基于以上对 Metro 工作原理及其产物 bundle 的分析,我们可以大致得出这样一个结论:React Native 启动时,JS 测(即 bundle)会先初始化一些变量,接着通过 IIFE 声明核心方法 define 和 require;接着通过 define 方法定义所有的模块,各个模块的依赖关系通moduleId 维系,维系的纽带就是 require;最后通过 require 应用的注册方法实现启动。

    实现动态导入自然需要将目前的 bundle 进行重新拆分和组合,整个方案的关键点在于:分和合,分就是 bundle 如何拆分,什么样的 module 需要拆分出去,什么时候进行拆分,拆分之后的 bundle 存储在哪里(涉及到后续如何获取);合就是拆出去的 bundle 如何获取,并在获取之后仍在正确的上下文内执行。

    前面有说过 Metro 工作的三个阶段,其中之一就是 Resolution,这一阶段的主要任务是从入口开始构建整个应用依赖图,这里为了方便示意以树来代替。

    React Native中怎么实现动态导入

    image

    识别入口

    如上所示就是一个依赖树,正常情况下会打出一个 bundle,包含模块 A、B、C、D、E、F、G。现在我想对模块 B 和 F 做动态导入。怎么做呢第一步当然是标识,既然叫动态导入自然而然的想到了 JavaScript 语法上的动态导入。

    只需要将 import A from '.A' 改成 const A = import('A') 即可,这就需要引入 Babel 插件()了,事实上官方 Metro 相关配置包 metro-config 已经集成了此插件。官方做的不仅仅于此,在 Transformation 阶段还对采用动态导入的 module 增加了唯一标识 Async = true

    此外在最终产物 bundle 上 Metro 提供了一个名叫 AsyncRequire.js 的文件模版来做动态导入的语法的 polyfill,具体实现如下

    const dynamicRequire = require;module.exports = function(moduleID) {    return Promise.resolve().then(() => dynamicRequire.importAll(moduleID));};

    总结一下 Metro 默认会如何处理动态导入:在 Transformation 通过 Babel 插件处理动态导入语法,并在中间产物上增加标识 Async,在 Serialization 阶段用 Asyncrequire.js 作为模板替换动态导入的语法,即

    const A = import(A);//变为const A = function(moduleID) {    return Promise.resolve().then(() => dynamicRequire.importAll(moduleID));};

    Asyncrequire.js 不仅关乎我们如何拆分,还和我们最后的合息息相关,留待后续再谈。

    树拆分

    通过上文我们知道构建过程中会生成一颗依赖树,并对其中使用动态的导入的模块做了标识,接下来就是树如何进行拆分了。对于树的通用处理办法就是 DFS,通过对上图依赖树做 DFS 分析之后可以得到如下做了拆分的树,包含一颗主树和两颗异步树。对于每棵树的依赖进行收集即可得到如下三组 module 集合:A、E、C;B、D、E、G;F、G。

    React Native中怎么实现动态导入

    image

    当然在实际场景中,各个模块的依赖远比这个复杂,甚至存在循环依赖的情况,在做 DFS 的过程中需要遵循两个原则:

    • 已经在处理过的 module,后续遇到直接退出循环

    • 各个异步树依赖的非主树 module 都需要包含进来

    bundle 生成

    通过这三组 module 集合即可得到三个bundle(我们将主树生成的 bundle 称为主 bundle;异步树生成的称为异步 bundle)。至于如何生成,直接借助前文提到的 Metro 中 processBasicModuleFilter 方法即可。Metro 原本在一次构建过程中,只会经过一次 Serialization 阶段生成一个 bundle。现在我们需要对每一组 module 都进行一次 bundle 生成。

    这里需要注意几个问题:

    • 去重,一种是已经打入主 bundle 的 module 异步 bundle 不需要打入;一种是同时存在于不同异步树内的 module,对于这种 module,我们可以将其标记为动态导入单独打包,见下图

    React Native中怎么实现动态导入

    image

    • 生成顺序,需要先生成异步 bundle,再生成主 bundle。因为需要将异步 bundle 的信息(比如文件名称、地址)与 moduleId 做映射填入主 bundle,这样在真正需要的时候可以通过 moduleId 的映射拿到异步 bundle 的地址信息。

    • 缓存控制,为了保证每个异步 bundle 在能够享受缓存机制的同时能够及时更新,需要对异步 bundle 做 content hash 添加到文件名上

    • 存储,异步 bundle 如何存储,是和主 bundle 一起,还是单独存储,需要时再去获取呢。这个需要具体分析:对于采用了bundle 预加载的可以将异步 bundle 和主 bundle 放到一起,需要时直接从本地拿即可(所谓预加载就是在客户端启动时就已经将所有 bundle 下载下来了,在用户打开 React Native 页面时无需再去下载 bundle)。对于大部分没有采用预加载技术的则分开存储更合适。

    至此我们已经获得了主 bundle 和异步 bundle,大致结构如下:

    // moduleId 与 路径映射var REMOTE_SOURCE_MAP = {${id}: ${path}, ... }// IIFE __r 之类定义(function (global) {  "use strict";  global.__r = metroRequire;  global.__d = define;  global.__c = clear;  global.__registerSegment = registerSegment;  ...})(typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this);//  业务模块__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {  var _reactNative = _$$_REQUIRE(_dependencyMap[0], "react-native");  var _asyncModule = _$$_REQUIRE(_dependencyMap[4], "metro/class="lazy" data-src/lib/bundle-modules/asyncRequire")(_dependencyMap[5], "./asyncModule")  ...},0,[1,550,590,673,701,855],"index.ios.js");...// 应用启动__r(91);__r(0);// 业务模块__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {  var _reactNative = _$$_REQUIRE(_dependencyMap[0], "react-native");  ...},855,[956, 1126],"asyncModule.js");

    大部分工作其实在分这一阶段已经做完了,接下来就是如何合了,前面有提到过动态导入的语法在生成的 bundle 中会被 AsyncRequire.js 中的模板所替代。仔细研究下其代码发现其是用 Promise 包裹了一层 require(moduleId) 来实现。

    现在我们直接 require(moduleId) 必然是拿不到真正的 module 实现了,因为异步 bundle 还没有获取到,module 还没有定义。但可以对 AsyncRequire.js 做如下改造

    const dynamicRequire = require;module.exports = function (moduleID) {    return fetch(REMOTE_SOURCE_MAP[moduleID]).then(res => {  // 行1        new Function(res)();                                 // 行2        return dynamicRequire.importAll(moduleID)            // 行3    });};

    接下来一行行进行分析

    • 行1将之前 mock 的 Promise 替换为真正的 Promise 请求,先去获取 bundle 资源,REMOTE_SOURCE_MAP 是在生成阶段写入主 bundle 的 moduleId 与异步 bundle 资源地址的映射。fetch 根据异步 bundle 的存储方式的不同选择不同的方式获取真正的代码资源;

    • 行2通过 Function 方法执行获取到的代码,即是模块的声明,这样最后返回 module 的时候就已经是定义过的了;

    • 行3 返回真正的模块实现。
      这样我们就实现了合,异步 bundle 的获取、执行就都在 AsyncRequire.js 内完成了。

    关于“React Native中怎么实现动态导入”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识,可以关注编程网行业资讯频道,小编每天都会为大家更新不同的知识点。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

    React Native中怎么实现动态导入

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

    React Native中怎么实现动态导入

    这篇文章主要介绍“React Native中怎么实现动态导入”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“React Native中怎么实现动态导入”文章能帮助大家解决问题。背景随着业务的发展,每一
    2023-07-02

    React Native Popup怎么实现

    这篇文章主要介绍“React Native Popup怎么实现”,在日常操作中,相信很多人在React Native Popup怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”React Native
    2023-06-30

    怎么使用react native reanimated实现动画

    这篇“怎么使用react native reanimated实现动画”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么使用r
    2023-07-05

    Java中怎么实现静态导入

    Java中怎么实现静态导入,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。先看下面这段示例代码:public class SayHelloTest{
    2023-06-17

    怎么用react native实现圆弧拖动进度条

    这篇“怎么用react native实现圆弧拖动进度条”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“怎么用react nat
    2023-06-05

    React怎么实现导入导出Excel文件

    这篇文章主要介绍“React怎么实现导入导出Excel文件”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“React怎么实现导入导出Excel文件”文章能帮助大家解决问题。表示层这里我是使用的是ant
    2023-06-05

    react native路由跳转怎么实现

    react native路由跳转的实现方法:1、使用“yarn add react-navigation”命令安装“react-navigation”;2、通过“yarn add react-native-gesture-handler”命令安装“react-native-gesture-handler”组件;3、设置好初始路由,然后以类的组件的形式导出即可。
    2023-05-14

    java中怎么实现动态载入

    java中怎么实现动态载入,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。java动态载入class的两种方式:implicit隐式,即利用实例化才载入的特性来java动态载
    2023-06-17

    React远程动态组件怎么实现

    这篇文章主要介绍了React远程动态组件怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇React远程动态组件怎么实现文章都会有所收获,下面我们一起来看看吧。远程动态组件实现远程动态组件库远程动态组件库项
    2023-07-05

    React Native工程中TSLint静态检查工具怎么用

    这期内容当中小编将会给大家带来有关React Native工程中TSLint静态检查工具怎么用,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。TSLint为TypeScript提供了代码检查能力,对使用Ty
    2023-06-04

    eclipse自动导入包怎么实现

    在Eclipse中,可以通过以下几种方式实现自动导入包:使用快捷键:当你输入一个未导入的类或方法时,可以使用快捷键Ctrl + Shift + O (Windows/Linux)或 Cmd + Shift + O (Mac)来自动导入所需的
    2023-10-24

    怎么用react实现引导页

    用react实现引导页的方法:1、创建一个启动界面,代码如“import React, { Component } from 'react';import{AppRegistry,StyleSheet,Text,View,AsyncStorage...}”;2、判断每次启动,获取之前是否保存过第一次加载的属性,如果加载过就显示主页,没加载过就显示引导页。
    2023-05-14

    react怎么实现滑动

    react实现滑动的方法:1、在onTouchStart事件找到touches,根据identifier中记录新的touch出现;2、在onTouchMove事件中根据identifier来记录每个touch经过的点的坐标;3、在onTouchEnd事件中,找到结束的touch事件,然后通过结束的touch事件划过的点来计算要执行的手势即可。
    2023-05-14

    Python中怎么实现自动导入缺失的库

    本篇内容主要讲解“Python中怎么实现自动导入缺失的库”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python中怎么实现自动导入缺失的库”吧!在写 Python 项目的时候,我们可能经常会遇
    2023-06-02

    react18中react-redux状态管理怎么实现

    这篇文章主要介绍“react18中react-redux状态管理怎么实现”,在日常操作中,相信很多人在react18中react-redux状态管理怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”rea
    2023-06-30

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录