Skip to main content

优化 JavaScript 加载

解析和运行 JavaScript 代码需要内存和时间。因此,随着应用的增长,延迟加载代码直到第一次需要时通常很有用。React Native 附带一些默认启用的标准优化,你可以在自己的代码中采用一些技术来帮助 React 更有效地加载你的应用。还有一些适用于非常大的应用的高级自动优化(有其自身的权衡)。

¥Parsing and running JavaScript code requires memory and time. Because of this, as your app grows, it's often useful to delay loading code until it's needed for the first time. React Native comes with some standard optimizations that are on by default, and there are techniques you can adopt in your own code to help React load your app more efficiently. There are also some advanced automatic optimizations (with their own tradeoffs) that are suitable for very large apps.

¥Recommended: Use Hermes

Hermes 是新 React Native 应用的默认引擎,并且针对高效代码加载进行了高度优化。在发布版本中,JavaScript 代码会提前完全编译为字节码。字节码按需加载到内存中,不需要像纯 JavaScript 那样进行解析。

¥Hermes is the default engine for new React Native apps, and is highly optimized for efficient code loading. In release builds, JavaScript code is fully compiled to bytecode ahead of time. Bytecode is loaded to memory on-demand and does not need to be parsed like plain JavaScript does.

信息

阅读有关在 React Native 此处 中使用 Hermes 的更多信息。

¥Read more about using Hermes in React Native here.

¥Recommended: Lazy-load large components

如果在最初渲染应用时不太可能使用具有大量代码/依赖的组件,则可以使用 React 的 lazy API 推迟加载其代码,直到第一次渲染它。通常,你应该考虑在应用中延迟加载屏幕级组件,以便向你的应用添加新屏幕不会增加其启动时间。

¥If a component with a lot of code/dependencies is not likely to be used when initially rendering your app, you can use React's lazy API to defer loading its code until it's rendered for the first time. Typically, you should consider lazy-loading screen-level components in your app, so that adding new screens to your app does not increase its startup time.

信息

在 React 的文档中阅读有关 使用 Suspense 延迟加载组件 的更多信息,包括代码示例。

¥Read more about lazy-loading components with Suspense , including code examples, in React's documentation.

提示:避免模块副作用

¥Tip: Avoid module side effects

如果组件模块(或其依赖)有副作用,例如修改全局变量或订阅组件外部的事件,则延迟加载组件可能会改变应用的行为。React 应用中的大多数模块不应有任何副作用。

¥Lazy-loading components can change the behavior of your app if your component modules (or their dependencies) have side effects, such as modifying global variables or subscribing to events outside of a component. Most modules in React apps should not have any side effects.

SideEffects.tsx
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

¥Advanced: Call require inline

有时你可能希望推迟加载某些代码,直到第一次使用它,而不使用 lazy 或异步 import()。你可以使用 require() 函数来执行此操作,否则你将在文件顶部使用静态 import

¥Sometimes you may want to defer loading some code until you use it for the first time, without using lazy or an asynchronous import(). You can do this by using the require() function where you would otherwise use a static import at the top of the file.

VeryExpensive.tsx
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>;
}
Optimized.tsx
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 调用

¥Advanced: Automatically inline require calls

如果你使用 React Native CLI 构建应用,require 调用(但不是 import)将自动为你内联,无论是在你的代码中还是在你使用的任何第三方软件包(node_modules)中。

¥If you use the React Native CLI to build your app, require calls (but not imports) will automatically be inlined for you, both in your code and inside any third-party packages (node_modules) you use.

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 来启用此优化。

¥Some React Native frameworks disable this behavior. In particular, in Expo projects, require calls are not inlined by default. You can enable this optimization by editing your project's Metro config and setting inlineRequires: true in getTransformOptions.

内联 require 的陷阱

¥Pitfalls of inline requires

内联 require 调用会改变模块评估的顺序,甚至会导致某些模块永远不会被评估。这通常可以安全地自动执行,因为 JavaScript 模块通常被编写为无副作用。

¥Inlining require calls changes the order in which modules are evaluated, and can even cause some modules to never be evaluated. This is usually safe to do automatically, because JavaScript modules are often written to be side-effect-free.

如果你的某个模块确实有副作用 - 例如,如果它初始化某些日志记录机制,或修补其余代码使用的全局 API - 那么你可能会看到意外行为甚至崩溃。在这些情况下,你可能希望从此优化中排除某些模块,或完全禁用它。

¥If one of your modules does have side effects - for example, if it initializes some logging mechanism, or patches a global API used by the rest of your code - then you might see unexpected behavior or even crashes. In those cases, you may want to exclude certain modules from this optimization, or disable it entirely.

要禁用所有 require 调用的自动内联:

¥To disable all automatic inlining of require calls:

更新你的 metro.config.js,将 inlineRequires 转换器选项设置为 false

¥Update your metro.config.js to set the inlineRequires transformer option to false:

metro.config.js
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: false,
},
};
},
},
};

要仅从 require 内联中排除某些模块:

¥To only exclude certain modules from require inlining:

有两个相关的转换器选项:inlineRequires.blockListnonInlinedRequires。查看代码片段以获取如何使用每个代码的示例。

¥There are two relevant transformer options: inlineRequires.blockList and nonInlinedRequires. See the code snippet for examples of how to use each one.

metro.config.js
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 的文档。

¥See the documentation for getTransformOptions in Metro for more details on setting up and fine-tuning your inline requires.

高级:使用随机访问模块包(非 Hermes)

¥Advanced: Use random access module bundles (non-Hermes)

信息

使用 Hermes 时不支持。Hermes 字节码与 RAM 打包格式不兼容,并在所有用例中提供相同(或更好)的性能。

¥Not supported when using Hermes. Hermes bytecode is not compatible with the RAM bundle format, and provides the same (or better) performance in all use cases.

随机访问模块包(也称为 RAM 包)与上面提到的技术配合使用,以限制需要解析并加载到内存中的 JavaScript 代码量。每个模块都存储为单独的字符串(或文件),仅在需要执行模块时才进行解析。

¥Random access module bundles (also known as RAM bundles) work in conjunction with the techniques mentioned above to limit the amount of JavaScript code that needs to be parsed and loaded into memory. Each module is stored as a separate string (or file) which is only parsed when the module needs to be executed.

RAM 包可以物理地分成单独的文件,也可以使用索引格式,由单个文件中的多个模块的查找表组成。

¥RAM bundles may be physically split into separate files, or they may use the indexed format, consisting of a lookup table of multiple modules in a single file.

在 Android 上,通过编辑 android/app/build.gradle 文件启用 RAM 格式。在 apply from: "../../node_modules/react-native/react.gradle" 行之前添加或修改 project.ext.react 块:

¥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",
]

如果你想使用单个索引文件,请在 Android 上使用以下行:

¥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"]
]

查看 Metro 中的 getTransformOptions 的文档以获取有关设置和微调 RAM 打包包构建的更多详细信息。

¥See the documentation for getTransformOptions in Metro for more details on setting up and fine-tuning your RAM bundle build.