跳到主要内容
新架构实战课 实操 + 基建 + 原理全维度包揽,抢先掌握 React Native 新架构精髓 立即查看 >Version: 0.74

Optimizing JavaScript loading

解析和运行 JavaScript 代码需要内存和时间。因此,随着你的应用程序增长,通常将代码加载延迟到首次需要时是有用的。React Native 自带一些默认开启的标准优化,并且你可以在自己的代码中采用一些技术来帮助 React 更有效地加载你的应用程序。还有一些适合非常大应用程序的高级自动优化(它们也有自己的权衡)。

推荐:使用 Hermes

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

推荐:延迟加载大型组件

如果一个包含大量代码/依赖项的组件在最初渲染应用程序时不太可能被使用,您可以使用 React 的 lazy API 来推迟加载其代码,直到它首次呈现。通常,您应该考虑延迟加载应用程序中的屏幕级组件,这样添加新屏幕到您的应用程序就不会增加其启动时间。

info

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

小贴士:避免模块副作用

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

SideEffects.tsx
import Logger from './utils/Logger';

// 🚩 🚩 🚩 副作用!这必须在 React 开始渲染 SplashScreen 组件之前执行,
// 如果你决定延迟加载 SplashScreen,它可能会意外地破坏应用中其他地方的代码。(比如需要依赖logger的代码)
global.logger = new Logger();

export function SplashScreen() {
// ...
}

高级:内联调用 require

有时,您可能希望将一些代码的加载推迟到第一次使用时,而不是使用 lazy 或异步的 import()。您可以通过在文件顶部本来会静态使用 import 的地方,改用 require() 函数来实现这一点。

VeryExpensive.tsx
import {Component} from 'react';
import {Text} from 'react-native';
// ... import 一些开销非常大的模块

export default function VeryExpensive() {
// ... 开销非常大的渲染逻辑
return <Text>Very Expensive Component</Text>;
}
Optimized.tsx
import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
// 一般我们会静态导入某个组件
// import VeryExpensive from './VeryExpensive';
// 但由于这个组件开销非常大,这里我们可以改用 require

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>
);
}
info

一些 React Native 框架禁用了这种行为。例如在 Expo 项目中,默认情况下不会内联 require 调用。你可以通过编辑项目的 Metro 配置,并在 getTransformOptions 中设置 inlineRequires: true 来启用此优化。

内联 require 的缺陷

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

如果你的一个模块确实有副作用 - 例如它初始化了某种日志记录机制,或者修补了需要在其他地方调用的全局 API - 那么你可能看到意外的行为甚至崩溃。在这些情况下,你可能想要从这种优化中排除某些模块或完全禁用它。

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

更新你的 metro.config.js 文件以将 inlineRequires transformer 选项设置为 false

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

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

有两个相关的转换器选项:inlineRequires.blockListnonInlinedRequires。请参见代码片段,了解如何使用每个选项的示例。

metro.config.js
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: {
blockList: {
// 在 `DoNotInlineHere.js` 中的 require() 调用将不会被内联。
[require.resolve('./src/DoNotInlineHere.js')]: true,

// 其他地方的 require() 调用将被内联,除非它们
// 与 nonInlinedRequires 中的任何条目匹配(见下文)。
},
},
nonInlinedRequires: [
// 在任何地方的 require('react') 调用都不会被内联
'react',
],
},
};
},
},
};

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

查看 Metro 文档中的 getTransformOptions 以获取有关设置和微调内联 require 的更多详细信息。

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

info

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

随机访问模块捆绑包(也称为 RAM bundle)与上述技术一起工作,以限制需要解析并加载到内存中的 JavaScript 代码量。每个模块都被存储为单独的字符串(或文件),只有在需要执行该模块时才进行解析。

RAM bundle 可以物理分割成单独的文件,或者它们可以使用 索引 格式,由单个文件中的多个模块查找表组成。

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

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