优化 JavaScript 加载
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
解析和运行 JavaScript 代码需要消耗内存和时间。因此随着应用规模增长,延迟加载代码直到首次需要使用时通常很有帮助。React Native 内置了一些默认启用的优化方案,同时您也可以在代码中采用特定技术帮助 React 更高效地加载应用。此外还有一些适用于大型应用的高级自动优化方案(各有取舍)。
推荐:使用 Hermes
Hermes 是新建 React Native 应用的默认引擎,针对高效代码加载进行了深度优化。在正式版本构建中,JavaScript 代码会预先完全编译为字节码。字节码按需加载到内存中,无需像普通 JavaScript 那样进行解析。
详细了解如何在 React Native 中使用 Hermes 请参阅此文档。
推荐:懒加载大型组件
如果某个包含大量代码/依赖项的组件在应用初始渲染时不太可能被使用,您可以使用 React 的 lazy API 将其代码加载延迟到首次渲染时执行。通常建议对应用中的屏幕级组件进行懒加载,这样新增屏幕不会延长应用的启动时间。
在 React 文档中详细了解使用 Suspense 懒加载组件,包含代码示例。
提示:避免模块副作用
如果您的组件模块(或其依赖项)存在副作用(例如修改全局变量或在组件外部订阅事件),懒加载组件可能会改变应用行为。React 应用中的大多数模块都不应有任何副作用。
import Logger from './utils/Logger';
// 🚩 🚩 🚩 Side effect! This must be executed before React can even begin to
// render the SplashScreen component, and can unexpectedly break code elsewhere
// in your app if you later decide to lazy-load SplashScreen.
global.logger = new Logger();
export function SplashScreen() {
// ...
}
高级:内联调用 require
有时您可能希望在不使用 lazy 或异步 import() 的情况下,延迟加载某些代码直到首次使用。您可以在原本使用文件顶部静态 import 的位置改用 require() 函数实现。
import {Component} from 'react';
import {Text} from 'react-native';
// ... import some very expensive modules
export default function VeryExpensive() {
// ... lots and lots of rendering logic
return <Text>Very Expensive Component</Text>;
}
import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
// Usually we would write a static import:
// import VeryExpensive from './VeryExpensive';
let VeryExpensive = null;
export default function Optimize() {
const [needsExpensive, setNeedsExpensive] = useState(false);
const didPress = useCallback(() => {
if (VeryExpensive == null) {
VeryExpensive = require('./VeryExpensive').default;
}
setNeedsExpensive(true);
}, []);
return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={didPress}>
<Text>Load</Text>
</TouchableOpacity>
{needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
高级:自动内联 require 调用
如果使用 React Native CLI 构建应用,require 调用(不包括 import)会自动内联处理,包括您代码中的调用和使用的第三方包(node_modules)内的调用。
import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
// This top-level require call will be evaluated lazily as part of the component below.
const VeryExpensive = require('./VeryExpensive').default;
export default function Optimize() {
const [needsExpensive, setNeedsExpensive] = useState(false);
const didPress = useCallback(() => {
setNeedsExpensive(true);
}, []);
return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={didPress}>
<Text>Load</Text>
</TouchableOpacity>
{needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
部分 React Native 框架会禁用此行为。特别是在 Expo 项目中,默认不会内联 require 调用。您可以通过编辑项目的 Metro 配置,在 getTransformOptions 中设置 inlineRequires: true 来启用此优化。
内联 require 的隐患
内联 require 调用会改变模块的求值顺序,甚至可能导致某些模块永不执行。由于 JavaScript 模块通常被设计为无副作用,自动执行此操作通常是安全的。
如果您的某个模块确实存在副作用(例如初始化日志机制或修补全局 API),则可能出现意外行为甚至崩溃。此时您可能需要排除特定模块或完全禁用此优化。
要完全禁用 require 调用的自动内联:
更新 metro.config.js 文件,将 inlineRequires 转换器选项设为 false:
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: false,
},
};
},
},
};
要仅排除特定模块的 require 内联:
有两个相关转换器选项:inlineRequires.blockList 和 nonInlinedRequires。请参考代码片段查看每种选项的使用示例。
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: {
blockList: {
// require() calls in `DoNotInlineHere.js` will not be inlined.
[require.resolve('./src/DoNotInlineHere.js')]: true,
// require() calls anywhere else will be inlined, unless they
// match any entry nonInlinedRequires (see below).
},
},
nonInlinedRequires: [
// require('react') calls will not be inlined anywhere
'react',
],
},
};
},
},
};
有关设置和微调内联 require 调用的更多详情,请参阅 Metro 中的 getTransformOptions 文档。
高级:使用随机访问模块包(非 Hermes)
使用 Hermes 时不支持此功能。 Hermes 字节码与 RAM 包格式不兼容,并且在所有用例中提供相同(或更好)的性能。
随机访问模块包(也称为 RAM 包)与上述技术结合使用,以限制需要解析并加载到内存中的 JavaScript 代码量。每个模块都存储为一个单独的字符串(或文件),仅在需要执行该模块时才进行解析。
RAM 包可以在物理上拆分为多个单独的文件,也可以使用 索引 格式,即在一个文件中包含多个模块的查找表。
- Android
- iOS
On Android enable the RAM format by editing your android/app/build.gradle file. Before the line apply from: "../../node_modules/react-native/react.gradle" add or amend the project.ext.react block:
project.ext.react = [
bundleCommand: "ram-bundle",
]
Use the following lines on Android if you want to use a single indexed file:
project.ext.react = [
bundleCommand: "ram-bundle",
extraPackagerArgs: ["--indexed-ram-bundle"]
]
On iOS, RAM bundles are always indexed ( = single file).
Enable the RAM format in Xcode by editing the build phase "Bundle React Native code and images". Before ../node_modules/react-native/scripts/react-native-xcode.sh add export BUNDLE_COMMAND="ram-bundle":
export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh
有关设置和微调 RAM 包构建的更多详情,请参阅 Metro 中的 getTransformOptions 文档。