跳到主要内容

33 篇关于“工程”的文章

查看所有标签

深入了解 React Native 性能

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

React Native 允许你使用 React 和 Relay 的声明式编程模型,用 JavaScript 构建 Android 和 iOS 应用。这使得代码更简洁、易于理解;无需编译周期即可快速迭代;并且易于在多个平台之间共享代码。你可以更快地发布产品,并专注于真正重要的细节,让你的应用看起来和感觉都棒极了。优化性能是其中的重要部分。以下是我们如何让 React Native 应用启动速度加快一倍的故事。

为什么要赶时间?

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

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

这是摘录。阅读其余帖子请访问 Facebook Code。

引入热重载

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

React Native 的目标是为您提供最佳的开发者体验。其中很重要的一部分是您保存文件到看到更改所需的时间。我们的目标是使这个反馈循环在应用程序增长时也能保持在1秒以内。

我们通过以下三个主要功能接近这个理想状态:

  • 使用 JavaScript 作为语言,因为它没有漫长的编译周期。
  • 实现一个名为 Packager 的工具,将 ES6/Flow/JSX 文件转换为虚拟机可理解的普通 JavaScript。它被设计为一个服务器,在内存中保存中间状态以实现快速增量更改,并使用多核。
  • 构建一个名为“实时重载”的功能,在保存时重新加载应用程序。

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

热重载

热重载的理念是保持应用程序运行,并在运行时注入您编辑的文件的最新版本。这样,您不会丢失任何状态,这在调整 UI 时尤其有用。

一图胜千言。看看实时重载(当前)和热重载(新)之间的区别。

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

警告:由于 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`。此时,`log` 和 `time` 的工厂函数都还没有执行,所以没有导出被缓存。然后,用户修改 `time` 以便返回 `MM/DD` 格式的日期

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

module.exports = bar;

Packager 会将 time 的新代码发送到运行时(步骤 1),当 `log` 最终被 require 时,导出的函数会与 `time` 的更改一起执行(步骤 2)。

现在,假设 `log` 的代码将 `time` 作为顶层 `require`。

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`,它依赖于 `MovieSearch` 和 `MovieScreen` 视图,而这些视图又依赖于前面示例中的 `log` 和 `time` 模块

如果用户访问电影搜索视图而不是另一个视图,除了 `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 转换,它使用 webpack 的 HMR API 来解决这个问题。简而言之,他的解决方案通过在转换时为每个 React 组件创建一个代理来实现。这些代理保存组件的状态并将生命周期方法委托给实际的组件,而这些组件正是我们热重载的组件

除了创建代理组件外,转换还定义了 `accept` 函数,其中包含一段强制 React 重新渲染组件的代码。通过这种方式,我们可以在不丢失任何应用程序状态的情况下热重载渲染代码。

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

Redux Stores

要启用 Redux 存储的热重载,您只需像在使用 webpack 的 Web 项目中一样使用 HMR API:

// 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 软件工程师

随着 React 在 Web 和 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 Code 上阅读其余内容。