跳到主要内容

深入 React Native 性能

·2 分钟阅读
Pieter De Baets
Facebook 软件工程师

React Native 允许您使用 React 和 Relay 的声明式编程模型,以 JavaScript 构建 Android 和 iOS 应用。这使得代码更简洁、更易于理解;无需编译周期即可快速迭代;并且可以轻松地跨多个平台共享代码。您可以更快地交付产品,并专注于真正重要的细节,使您的应用看起来和感觉都很棒。优化性能是其中的重要组成部分。这里讲述了我们如何使 React Native 应用启动速度提高一倍的故事。

为什么要着急?

对于运行速度更快的应用,内容加载速度更快,这意味着人们有更多时间与之互动,流畅的动画使应用使用起来更愉快。在新兴市场,2011 年的手机2G 网络中占多数,对性能的关注可能决定一个应用是可用还是不可用。

自从在 iOSAndroid 上发布 React Native 以来,我们一直在改进列表视图滚动性能、内存效率、UI 响应速度和应用启动时间。启动时间设定了应用的第一印象,并对框架的所有部分施加压力,因此它是最值得和最具挑战性的问题。

这是一个摘录。请在 Facebook 代码上阅读文章的其余部分。

介绍热重载

·9 分钟阅读
Martín Bigio
Instagram 软件工程师

React Native 的目标是为您提供最佳的开发者体验。其中很大一部分是您保存文件到能够看到更改之间的时间。我们的目标是使这个反馈循环保持在 1 秒以内,即使您的应用不断增长。

我们通过三个主要功能接近了这个理想

  • 使用 JavaScript 作为语言,它没有很长的编译周期时间。
  • 实现一个名为 Packager 的工具,将 es6/flow/jsx 文件转换为 VM 可以理解的普通 JavaScript。它被设计为一个服务器,将中间状态保存在内存中,以实现快速的增量更改,并使用多个核心。
  • 构建一个名为 Live Reload 的功能,在保存时重新加载应用。

此时,开发人员的瓶颈不再是重新加载应用所需的时间,而是应用状态的丢失。一个常见的场景是处理一个距离启动屏幕多个屏幕的功能。每次重新加载时,您都必须一次又一次地单击相同的路径才能回到您的功能,这使得周期变得长达数秒。

热重载

热重载背后的想法是保持应用运行,并在运行时注入您编辑的文件的新版本。这样,您就不会丢失任何状态,如果您正在调整 UI,这将特别有用。

一图胜千言。查看 Live Reload(当前)和 Hot Reload(新)之间的区别。

如果您仔细观察,您会注意到可以从红色框中恢复,并且您还可以开始导入以前不存在的模块,而无需进行完全重新加载。

警告: 由于 JavaScript 是一种非常有状态的语言,因此无法完美实现热重载。在实践中,我们发现当前的设置在大量常用用例中运行良好,并且在出现问题时始终可以进行完全重新加载。

热重载从 0.22 版本开始可用,您可以启用它

  • 打开开发者菜单
  • 点击“启用热重载”

简而言之的实现

现在我们已经了解了我们为什么需要它以及如何使用它,有趣的部分开始了:它实际上是如何工作的。

热重载构建于 热模块替换或 HMR 功能之上。它最初由 webpack 引入,我们在 React Native Packager 内部实现了它。HMR 使 Packager 监视文件更改并将 HMR 更新发送到应用中包含的精简 HMR 运行时。

简而言之,HMR 更新包含已更改的 JS 模块的新代码。当运行时接收到它们时,它会将旧模块的代码替换为新代码

HMR 更新包含的内容不仅仅是我们想要更改的模块的代码,因为仅替换它不足以让运行时获取更改。问题在于模块系统可能已经缓存了我们想要更新的模块的导出。例如,假设您有一个由这两个模块组成的应用

// log.js
function log(message) {
const time = require('./time');
console.log(`[${time()}] ${message}`);
}

module.exports = log;
// time.js
function time() {
return new Date().getTime();
}

module.exports = time;

模块 log 打印出提供的消息,包括模块 time 提供的当前日期。

当应用被捆绑时,React Native 使用 __d 函数在模块系统上注册每个模块。对于此应用,在许多 __d 定义中,将有一个用于 log 的定义

__d('log', function() {
... // module's code
});

此调用将每个模块的代码包装到匿名函数中,我们通常将其称为工厂函数。模块系统运行时跟踪每个模块的工厂函数、它是否已执行以及执行结果(导出)。当需要模块时,模块系统要么提供已缓存的导出,要么首次执行模块的工厂函数并保存结果。

假设您启动应用并需要 log。此时,logtime 的工厂函数都尚未执行,因此没有缓存导出。然后,用户修改 timeMM/DD 格式返回日期

// time.js
function bar() {
const date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}

module.exports = bar;

Packager 会将 time 的新代码发送到运行时(步骤 1),当最终需要 log 时,导出的函数将被执行,它将使用 time 的更改(步骤 2)

现在假设 log 的代码需要 time 作为顶级需求

const time = require('./time'); // top level require

// log.js
function log(message) {
console.log(`[${time()}] ${message}`);
}

module.exports = log;

当需要 log 时,运行时将缓存其导出和 time 的导出。(步骤 1)。然后,当 time 被修改时,HMR 过程不能简单地在替换 time 的代码后结束。如果这样做,当执行 log 时,它将使用 time 的缓存副本(旧代码)。

为了让 log 获取 time 的更改,我们需要清除其缓存的导出,因为其依赖的模块之一已被热交换(步骤 3)。最后,当再次需要 log 时,其工厂函数将被执行,需要 time 并获取其新代码。

HMR API

React Native 中的 HMR 通过引入 hot 对象扩展了模块系统。此 API 基于 webpack 的 API。hot 对象公开了一个名为 accept 的函数,该函数允许您定义一个回调,该回调将在需要热交换模块时执行。例如,如果我们按如下方式更改 time 的代码,每次我们保存 time 时,我们都会在控制台中看到“time changed”

// time.js
function time() {
... // new code
}

module.hot.accept(() => {
console.log('time changed');
});

module.exports = time;

请注意,只有在极少数情况下您才需要手动使用此 API。对于最常见的用例,热重载应该开箱即用。

HMR 运行时

正如我们之前所见,有时仅接受 HMR 更新是不够的,因为使用正在热交换的模块的模块可能已经执行并且其导入已缓存。例如,假设电影应用示例的依赖树具有顶级的 MovieRouter,它依赖于 MovieSearchMovieScreen 视图,而这些视图又依赖于前面示例中的 logtime 模块

如果用户访问电影搜索视图,但不访问另一个视图,则除 MovieScreen 之外的所有模块都将具有缓存的导出。如果对模块 time 进行了更改,则运行时必须清除 log 的导出,以便它获取 time 的更改。该过程不会在那里结束:运行时将递归地重复此过程,直到所有父级都被接受。因此,它将获取依赖于 log 的模块并尝试接受它们。对于 MovieScreen,它可以放弃,因为它尚未被请求。对于 MovieSearch,它将必须清除其导出并递归处理其父级。最后,它将对 MovieRouter 执行相同的操作并在那里结束,因为没有模块依赖于它。

为了遍历依赖树,运行时从 Packager 接收 HMR 更新上的反向依赖树。对于此示例,运行时将接收如下所示的 JSON 对象

{
modules: [
{
name: 'time',
code: /* time's new code */
}
],
inverseDependencies: {
MovieRouter: [],
MovieScreen: ['MovieRouter'],
MovieSearch: ['MovieRouter'],
log: ['MovieScreen', 'MovieSearch'],
time: ['log'],
}
}

React 组件

React 组件与热重载一起工作起来有点困难。问题是我们不能简单地用新代码替换旧代码,因为我们会丢失组件的状态。对于 React Web 应用程序,Dan Abramov 实现了 babel transform,它使用 webpack 的 HMR API 来解决此问题。简而言之,他的解决方案通过在转换时为每个 React 组件创建一个代理来工作。代理持有组件的状态,并将生命周期方法委托给实际组件,而实际组件是我们热重载的组件

除了创建代理组件外,转换还定义了 accept 函数,其中包含一段代码以强制 React 重新渲染组件。这样,我们就可以热重载渲染代码,而不会丢失任何应用状态。

React Native 附带的默认 转换器使用 babel-preset-react-native,它 配置为以您在使用 webpack 的 React Web 项目中使用的相同方式使用 react-transform

Redux Stores

要在 Redux store 上启用热重载,您只需使用 HMR API,类似于您在使用 webpack 的 Web 项目中所做的那样

// configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import reducer from '../reducers';

export default function configureStore(initialState) {
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk),
);

if (module.hot) {
module.hot.accept(() => {
const nextRootReducer = require('../reducers/index').default;
store.replaceReducer(nextRootReducer);
});
}

return store;
};

当您更改 reducer 时,用于接受该 reducer 的代码将被发送到客户端。然后客户端将意识到 reducer 不知道如何接受自身,因此它将查找所有引用它的模块并尝试接受它们。最终,流程将到达单个 store,即 configureStore 模块,它将接受 HMR 更新。

结论

如果您有兴趣帮助改进热重载,我鼓励您阅读 Dan Abramov 关于热重载未来的文章并做出贡献。例如,Johny Days 将 使其与多个连接的客户端一起工作。我们依靠大家来维护和改进此功能。

借助 React Native,我们有机会重新思考构建应用的方式,以使其成为出色的开发者体验。热重载只是难题的一部分,我们还可以做哪些疯狂的技巧来使其更好?

使 React Native 应用可访问

·2 分钟阅读
Georgiy Kassabli
Facebook 软件工程师

随着最近在 Web 上推出 React 和在移动设备上推出 React Native,我们为开发者提供了一个新的前端框架来构建产品。构建强大产品的关键方面之一是确保任何人都可以使用它,包括视力丧失或其他残疾人士。React 和 React Native 的辅助功能 API 使您能够让任何由 React 驱动的体验都可供可能使用辅助技术(如盲人和视障人士的屏幕阅读器)的人使用。

在本文中,我们将重点关注 React Native 应用。我们设计的 React 辅助功能 API 在外观和感觉上类似于 Android 和 iOS API。如果您之前为 Android、iOS 或 Web 开发过可访问的应用程序,您应该对 React AX API 的框架和命名感到舒适。例如,您可以使 UI 元素可访问(因此暴露给辅助技术)并使用 accessibilityLabel 为元素提供字符串描述

<View accessible={true} accessibilityLabel=”This is simple view”>

让我们通过查看 Facebook 自己的 React 驱动产品之一:广告管理工具应用,来了解 React AX API 的稍微复杂的应用。

这是一个摘录。请在 Facebook 代码上阅读文章的其余部分。

React Native for Android:我们如何构建第一个跨平台 React Native 应用

·一分钟阅读
Facebook 软件工程师

今年早些时候,我们推出了 React Native for iOS。React Native 将开发者在 Web 上的 React 中习惯的——声明式的自包含 UI 组件和快速的开发周期——带到移动平台,同时保留了原生应用的速度、保真度和感觉。今天,我们很高兴发布 React Native for Android。

在 Facebook,我们已经在生产环境中使用 React Native 超过一年了。大约一年前,我们的团队开始开发 广告管理工具应用。我们的目标是创建一个新的应用,让数百万在 Facebook 上投放广告的人员能够管理他们的帐户并在旅途中创建新的广告。它最终不仅成为 Facebook 的第一个完全 React Native 应用,而且也是第一个跨平台应用。在这篇文章中,我们想与您分享我们是如何构建这个应用的,React Native 如何使我们能够更快地行动,以及我们学到的经验教训。

这是一个摘录。请在 Facebook 代码上阅读文章的其余部分。

React Native:将现代 Web 技术带到移动端

·2 分钟阅读
Tom Occhino
Facebook 工程经理

两年前,我们向世界介绍了 React,从那时起,它在 Facebook 内外都取得了令人印象深刻的增长。如今,即使没有人被迫使用它,Facebook 的新 Web 项目通常也以某种形式使用 React 构建,并且它正在整个行业中被广泛采用。工程师们每天都选择使用 React,因为它使他们能够将更多时间集中在他们的产品上,而将更少的时间用于与框架作斗争。但是,直到我们使用 React 构建了一段时间后,我们才开始了解使其如此强大的原因。

React 迫使我们将应用程序分解为离散的组件,每个组件代表一个单独的视图。这些组件使我们可以更轻松地迭代我们的产品,因为我们不需要将整个系统记在脑海中即可对其一部分进行更改。但是,更重要的是,React 使用声明式 API 包装了 DOM 的可变、命令式 API,这提高了抽象级别并简化了编程模型。我们发现,当我们使用 React 构建时,我们的代码更具可预测性。这种可预测性使我们可以更有信心地更快地进行迭代,并且我们的应用程序也因此更加可靠。此外,当我们的应用程序是使用 React 构建时,不仅更容易扩展我们的应用程序,而且我们还发现更容易扩展我们团队本身的规模。

借助 Web 的快速迭代周期,我们已经能够使用 React 构建一些很棒的产品,包括 Facebook.com 的许多组件。此外,我们在 React 之上使用 JavaScript 构建了出色的框架,例如 Relay,这使我们能够极大地简化我们的大规模数据获取。当然,Web 只是故事的一部分。Facebook 还拥有广泛使用的 Android 和 iOS 应用,这些应用构建在不相关的专有技术堆栈之上。不得不在多个平台上构建我们的应用使我们的工程组织一分为二,但这只是使原生移动应用开发变得困难的原因之一。

这是一个摘录。请在 Facebook 代码上阅读文章的其余部分。