优化 JavaScript 加载
解析和运行 JavaScript 代码需要内存和时间。因此,随着应用程序的增长,通常在第一次需要时延迟加载代码非常有用。React Native 带有一些默认启用的标准优化,并且您可以在自己的代码中采用一些技术来帮助 React 更有效地加载您的应用程序。还有一些适用于超大型应用程序的高级自动优化(及其自身的权衡)。
推荐:使用 Hermes
Hermes 是新 React Native 应用程序的默认引擎,并且针对高效代码加载进行了高度优化。在发布版本中,JavaScript 代码会在提前编译成字节码。字节码按需加载到内存中,并且不需要像普通 JavaScript 那样进行解析。
阅读有关在 React Native 中使用 Hermes 的更多信息 此处。
推荐:延迟加载大型组件
如果包含大量代码/依赖项的组件在最初渲染应用程序时不太可能被使用,则可以使用 React 的 lazy
API 推迟加载其代码,直到它第一次被渲染。通常,您应该考虑延迟加载应用程序中的屏幕级组件,以便向应用程序添加新屏幕不会增加其启动时间。
阅读有关 使用 Suspense 延迟加载组件(包括代码示例)的更多信息,请参阅 React 的文档。
提示:避免模块副作用
如果您的组件模块(或其依赖项)具有副作用(例如,修改全局变量或订阅组件外部的事件),则延迟加载组件可能会更改应用程序的行为。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',
],
},
};
},
},
};
有关在 Metro 中设置和微调 require
内联的更多详细信息,请参阅 getTransformOptions
文档。
高级:使用随机访问模块捆绑包(非 Hermes)
在使用 Hermes时不支持。 Hermes 字节码与 RAM 捆绑包格式不兼容,并且在所有用例中都提供相同(或更好)的性能。
随机访问模块捆绑包(也称为 RAM 捆绑包)与上述技术结合使用,以限制需要解析并加载到内存中的 JavaScript 代码量。每个模块都存储为单独的字符串(或文件),仅在需要执行模块时才对其进行解析。
RAM 捆绑包可能会物理地拆分为单独的文件,或者它们可以使用索引格式,该格式由单个文件中的多个模块的查找表组成。
- Android
- iOS
在 Android 上,通过编辑您的 android/app/build.gradle
文件来启用 RAM 格式。在 apply from: "../../node_modules/react-native/react.gradle"
行之前,添加或修改 project.ext.react
块
project.ext.react = [
bundleCommand: "ram-bundle",
]
如果您想使用单个索引文件,请在 Android 上使用以下行
project.ext.react = [
bundleCommand: "ram-bundle",
extraPackagerArgs: ["--indexed-ram-bundle"]
]
在 iOS 上,RAM 捆绑包始终被索引(= 单个文件)。
通过编辑构建阶段“捆绑 React Native 代码和图像”在 Xcode 中启用 RAM 格式。在 ../node_modules/react-native/scripts/react-native-xcode.sh
之前添加 export BUNDLE_COMMAND="ram-bundle"
export BUNDLE_COMMAND="ram-bundle"
export NODE_BINARY=node
../node_modules/react-native/scripts/react-native-xcode.sh
有关在 Metro 中设置和微调 RAM 捆绑包构建的更多详细信息,请参阅 getTransformOptions
文档。