如果你喜欢我的文章,希望点赞👍 收藏 📁 评论 💬 三连支持一下,谢谢你,这对我真的很重要!
前言
上一篇文章主要从 Native 的角度分析了 React Native 的初始化流程,并从源码出发,总结了几个 React Native 容器初始化的优化点。本文主要从 JavaScript 入手,总结了一些 JS 侧的优化要点。
1.JSEngine
Hermes
Hermes 是 FaceBook 2019 年中旬开源的一款 JS 引擎,从 release 记录可以看出,这个是专为 React Native 打造的 JS 引擎,可以说从设计之初就是为 Hybrid UI 系统打造。
Hermes 支持直接加载字节码,也就是说,Babel
、Minify
、Parse
和 Compile
这些流程全部都在开发者电脑上完成,直接下发字节码让 Hermes 运行就行,这样做可以省去 JSEngine 解析编译 JavaScript 的流程,JS 代码的加载速度将会大大加快,启动速度也会有非常大的提升。
更多关于 Hermes 的特性,大家可以看我的旧文《移动端 JS 引擎哪家强》这篇文章,我做了更为详细的特性说明与数据对比,这里就不多说了。
2.JS Bundle
前面的优化其实都是 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 :
因为这个原因,我们减小 bundle 体积主要是三个方向:
- 对于同样的功能,优先选择体积更小的第三方库
- 利用 babel 插件,避免全量引用
- 制定编码规范,减少重复代码
下面我们举几个例子来解释上面的三个思路。
2.1.0 使用 react-native-bundle-visualizer 查看包体积
优化 bundle 文件前,一定要知道 bundle 里有些什么,最好的方式就是用可视化的方式把所有的依赖包列出来。web 开发中,可以借助 Webpack 的 webpack-bundle-analyzer
插件查看 bundle 的依赖大小分布,React Native 也有类似的工具,可以借助 react-native-bundle-visualizer
查看依赖关系:
使用非常简单,按照文档安装分析就可。
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'