跳到主要内容

31 篇贴子,标签为“工程”

查看所有标签

Marketplace 中的 React Native 性能

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

React Native 在 Facebook 系列的多个应用程序中的多个地方使用,包括主要 Facebook 应用程序中的顶级标签页。本文的重点是一个高度可见的产品:Marketplace。它在全球十几个国家/地区可用,并允许用户发现其他用户提供的产品和服务。

2017 年上半年,通过 Relay 团队、Marketplace 团队、移动 JS 平台团队和 React Native 团队的共同努力,我们将 Android 2010-11 年级设备的 Marketplace 交互时间 (TTI) 缩短了一半。Facebook 历来将这些设备视为低端 Android 设备,它们在任何平台或设备类型上的 TTI 都是最慢的。

典型的 React Native 启动过程如下所示

免责声明:比例不具代表性,会因 React Native 的配置和使用方式而异。

我们首先初始化 React Native 核心(即“Bridge”),然后运行产品特定的 JavaScript,它决定了 React Native 在原生处理时间内将渲染哪些原生视图。

一种不同的方法

我们早期犯的一个错误是让 Systrace 和 CTScan 来驱动我们的性能工作。这些工具帮助我们在 2016 年找到了许多唾手可得的改进,但我们发现 Systrace 和 CTScan 都不代表生产环境场景,也无法模拟实际发生的情况。细分时间所占的比例通常是不正确的,有时甚至偏差很大。在极端情况下,我们预计只需几毫秒的事情实际上却需要数百或数千毫秒。尽管如此,CTScan 仍然有用,我们发现它能在三分之一的回归问题投入生产之前发现它们。

在 Android 上,我们将这些工具的缺点归因于:1) React Native 是一个多线程框架,2) Marketplace 与 Newsfeed 和其他顶级标签页等大量复杂视图并存,以及 3) 计算时间变化极大。因此,本半年,我们几乎所有决策和优先级都由生产测量和细分数据驱动。

生产环境检测之路

表面上,对生产环境进行检测可能听起来很简单,但事实证明这是一个相当复杂的过程。它需要多个迭代周期,每个周期 2-3 周;由于将提交合入主分支、将应用程序推送到 Play 商店以及收集足够的生产样本以对我们的工作充满信心的延迟。每个迭代周期都涉及发现我们的细分是否准确,是否具有正确的粒度级别,以及它们是否正确地加起来覆盖了整个时间跨度。我们不能依赖 alpha 和 beta 版本,因为它们不代表普通用户群。本质上,我们非常费力地基于数百万个样本的总和构建了一个非常准确的生产跟踪。

我们之所以一丝不苟地验证细分中的每一毫秒都正确地累加到其父指标,原因之一是我们很早就意识到我们的检测存在空白。事实证明,我们最初的细分并未考虑线程跳转导致的停顿。线程跳转本身并不昂贵,但跳转到已经繁忙的线程则非常昂贵。我们最终通过在适当的时候添加 Thread.sleep() 调用,在本地重现了这些阻塞,并通过以下方式解决了它们:

  1. 消除我们对 AsyncTask 的依赖,
  2. 撤销在 UI 线程上强制初始化 ReactContext 和 NativeModules,以及
  3. 消除在初始化时测量 ReactRootView 的依赖。

总而言之,解决这些线程阻塞问题使启动时间减少了 25% 以上。

生产指标也挑战了我们之前的一些假设。例如,我们过去在启动路径上预加载许多 JavaScript 模块,假设将模块放在一个包中会降低其初始化成本。然而,预加载和共置这些模块的成本远远超过了收益。通过重新配置我们的内联 require 黑名单并从启动路径中删除 JavaScript 模块,我们得以避免加载不必要的模块,例如 Relay Classic(当只需要 Relay Modern 时)。如今,我们的 RUN_JS_BUNDLE 细分速度提高了 75% 以上。

我们还通过调查产品特定的原生模块发现了优势。例如,通过惰性注入原生模块的依赖项,我们将其成本降低了 98%。通过消除 Marketplace 启动与其他产品之间的竞争,我们将启动时间缩短了相同的时长。

最棒的是,其中许多改进都广泛适用于所有使用 React Native 构建的屏幕。

结论

人们认为 React Native 启动性能问题是由于 JavaScript 运行缓慢或网络时间过长造成的。虽然加快 JavaScript 等会使 TTI 大幅下降,但每个因素对 TTI 的贡献百分比都比以前认为的要小得多。

到目前为止的教训是:测量,测量,再测量!一些收益来自于将运行时成本转移到构建时,例如 Relay Modern 和 Lazy NativeModules。其他收益来自于通过更智能地并行化代码或删除死代码来避免工作。还有一些收益来自于 React Native 的重大架构变化,例如清理线程阻塞。性能没有一劳永逸的解决方案,长期的性能提升将来自于渐进式的检测和改进。不要让认知偏差影响你的决策。相反,仔细收集和解释生产数据来指导未来的工作。

未来计划

从长远来看,我们希望 Marketplace 的 TTI 能够与使用原生技术构建的类似产品相媲美,并且通常情况下,React Native 的性能能够与原生性能持平。此外,尽管本半年我们大幅降低了桥接启动成本约 80%,但我们计划通过 Prepack 等项目和更多的构建时处理,将 React Native 桥接的成本降至接近零。

React Native 月度会议 #2

·阅读 9 分钟
Tomislav Tenodi
Shoutem 产品经理

React Native 月度会议继续进行!本次会议,我们有幸邀请到 Infinite Red 的优秀人才加入,他们是 Chain React,React Native 大会的幕后主脑。由于这里的大部分人都将在 Chain React 上发表演讲,我们将会议推迟了一周。会议的演讲视频已 发布在线,我鼓励大家去观看。那么,让我们看看我们的团队都在忙些什么。

团队

在第二次会议上,我们有 9 个团队加入

备注

以下是每个团队的备注

Airbnb

Callstack

  • Mike Grabowski 一如既往地管理着 React Native 的月度发布,包括推出了一些测试版。特别是,正在努力将 v0.43.5 构建版本发布到 npm,因为它能为 Windows 用户解除阻塞!
  • Haul 正在缓慢但持续地进行工作。有一个添加 HMR 的 pull request 已经合并,其他改进也已发布。最近有几家行业领导者采用了它。可能计划在该领域开始全职有偿工作。
  • 本月,来自 Jest 团队的 Michał Pierzchała 加入了 Callstack。他将帮助维护 Haul,并可能参与 Metro BundlerJest 的工作。
  • Satyajit Sahoo 现在和我们在一起了,太棒了!
  • 我们的开源部门即将推出一系列很棒的东西。特别是,正在致力于将 Material Palette API 引入 React Native。计划最终发布我们的原生 iOS 工具包,旨在提供与原生组件 1:1 的外观和感觉。

Expo

  • 最近推出了 Native Directory,以帮助发现和评估 React Native 生态系统中的库。问题在于:库很多,难以测试,需要手动应用启发式方法,并且不立即明显哪些是您应该使用的最佳库。也很难知道某些东西是否与 CRNA/Expo 兼容。所以 Native Directory 试图解决这些问题。查看它并 将您的库添加 到其中。库列表在 这里。这只是我们的第一版,我们希望它由社区拥有和运营,而不仅仅是 Expo 团队。因此,如果您认为这很有价值并希望改进它,请加入进来!
  • Snack 中添加了对使用 Expo SDK 19 安装 npm 包的初步支持。如果您在使用过程中遇到任何问题,请告诉我们,我们仍在解决一些 bug。结合 Native Directory,这应该能更容易地测试那些只有 JS 依赖项或包含在 Expo SDK 中的库。试试看:
  • 发布了 Expo SDK19,带来了全面的改进,我们现在正在使用 更新的 Android JSC
  • 正在与 Alexander Kotliarskyi 合作撰写一份文档指南,其中包含如何改善应用程序用户体验的提示列表。请加入并添加到列表中或帮助编写部分内容!
  • 继续致力于:音频/视频、摄像头、手势(与 Software Mansion 合作,react-native-gesture-handler)、GL 摄像头集成,并希望在 SDK20(八月)中首次推出其中一些功能,届时其他方面也会有显著改进。我们刚刚开始为 Expo 客户端构建用于后台工作(地理定位、音频、处理通知等)的基础设施。
  • Adam Miskiewiczreact-navigation 中模仿 UINavigationController 的转场方面取得了不错的进展。在他的 推文 中查看其早期版本——很快将发布包含此功能的版本。另请查看他 上游MaskedViewIOS。如果您有能力并愿意为 Android 实现 MaskedView,那将非常棒!

Facebook

  • Facebook 内部正在探索将原生 ComponentKitLitho 组件嵌入 React Native 的可能性。
  • 非常欢迎为 React Native 贡献代码!如果您想知道如何贡献,“如何贡献”指南 描述了我们的开发流程并列出了发送第一个拉取请求的步骤。还有其他不需要编写代码的贡献方式,例如分类问题或更新文档。
    • 截至撰写本文时,React Native 有 635开放问题249开放拉取请求。这让我们的维护者不堪重负,当内部修复问题时,很难确保相关任务得到更新。
    • 我们不确定在让社区满意的情况下处理此问题的最佳方法是什么。一些(但不是全部!)选项包括关闭陈旧问题、授予更多人管理问题的权限,以及自动关闭不遵循问题模板的问题。我们编写了一份“维护者期望”指南,以设定预期并避免意外。如果您对我们如何改善维护者体验以及确保提出问题和拉取请求的人感受到被倾听和重视有任何想法,请告诉我们!

GeekyAnts

  • 我们在 Chain React 上演示了与 React Native 文件配合使用的设计工具。许多与会者报名了等候名单。
  • 我们还在研究其他跨平台解决方案,如 Google Flutter(即将进行重大比较)、Kotlin NativeApache Weex,以了解它们的架构差异以及我们可以从中学习什么来提高 React Native 的整体性能。
  • 我们的大多数应用程序都已切换到 react-navigation,这提高了整体性能。
  • 此外,宣布了 NativeBase Market - 一个用于 React Native 组件和应用程序的市场(面向开发者,由开发者提供)。

Infinite Red

微软

  • CodePush 现已集成到 Mobile Center 中。现有用户的工作流程不会发生变化。
    • 有些人报告了重复应用的问题——他们已经在 Mobile Center 上有一个应用。我们正在努力解决这些问题,但如果您有两个应用,请告诉我们,我们可以为您合并它们。
  • Mobile Center 现在支持 CodePush 的推送通知。我们还展示了如何将通知和 CodePush 结合起来用于应用程序的 A/B 测试——这是 React Native 架构独有的。
  • VS Code 在 React Native 调试方面有一个已知问题——几天后扩展的下一个版本将修复此问题。
  • 由于微软内部还有许多其他团队也在从事 React Native 的工作,我们将在下次会议上努力争取所有团队的更好代表性。

Shoutem

  • 完成了让 Shoutem 上的 React Native 开发变得更容易的过程。在 Shoutem 上开发应用时,您可以使用所有标准的 react-native 命令。
  • 我们做了很多工作,试图找出在 React Native 上进行性能分析的最佳方法。很大一部分文档已经过时,我们将尽力在官方文档上创建一个拉取请求,或者至少在博客文章中写下我们的一些结论。
  • 正在将我们的导航解决方案切换到 react-navigation,所以我们可能很快就会有一些反馈。
  • 我们在工具包中发布了 一个新的 HTML 组件,它将原始 HTML 转换为 React Native 组件树。

Wix

  • 我们开始为 Metro Bundler 提交一个包含 react-native-repackager 功能的拉取请求。我们更新了 react-native-repackager 以支持 RN 44(我们在生产环境中使用)。我们正在将其用于 detox 的模拟基础设施。
  • 在过去的三个星期里,我们一直在使用 detox 测试覆盖 Wix 应用。这是一次了不起的学习经历,了解如何在如此规模(超过 40 名工程师)的应用中减少手动质量保证。因此,我们已经解决了 detox 的几个问题,新版本刚刚发布。我很高兴地报告,我们正在践行“零不稳定性政策”,到目前为止测试一直稳定通过。
  • Android 版 Detox 进展顺利。我们得到了社区的大力帮助。我们预计在两周内推出初始版本。
  • 我们的性能测试工具 DetoxInstruments 正在变得比我们最初设想的更大。我们现在计划将其转换为一个独立的工具,不再与 detox 紧密耦合。它将允许普遍调查 iOS 应用程序的性能。它还将与 detox 集成,以便我们可以在性能指标上运行自动化测试。

下一届会议

下一届会议定于 2017 年 8 月 16 日举行。由于这只是我们的第二次会议,我们想知道这些会议记录如何使 React Native 社区受益。如果您对我们如何改进会议成果有任何建议,请随时 在 Twitter 上联系我

React Native 月度会议 #1

·阅读 6 分钟
Tomislav Tenodi
Shoutem 产品经理

Shoutem,我们很幸运能从 React Native 诞生之初就开始与它合作。我们决定从第一天起就成为这个了不起社区的一部分。很快,我们意识到几乎不可能跟上社区发展和改进的速度。这就是为什么我们决定组织一个每月会议,所有主要的 React Native 贡献者都可以在会上简要介绍他们的努力和计划。

月度会议

我们于 2017 年 6 月 14 日举行了第一次月度会议。React Native 月度会议的使命简单明了:改善 React Native 社区。展示团队的努力有助于团队之间进行线下协作。

团队

第一次会议,我们有 8 个团队加入

我们希望有更多的核心贡献者加入未来的会议!

备注

由于团队的计划可能对更广泛的受众感兴趣,我们将在 React Native 博客上分享它们。所以,它们在这里:

Airbnb

  • 计划为 ViewAccessibilityInfo 原生模块添加一些 A11y(可访问性)API。
  • 将调查在 Android 原生模块中添加一些 API,以允许指定它们运行的线程。
  • 一直在调查潜在的初始化性能改进。
  • 一直在研究在“解捆”之上使用一些更复杂的捆绑策略。

Callstack

  • 正在研究通过使用 Detox 进行 E2E 测试来改进发布流程。拉取请求应该很快就会合并。
  • 他们一直在处理的 Blob 拉取请求已合并,后续拉取请求即将到来。
  • 在内部项目中增加 Haul 的采用率,以了解其与 Metro Bundler 相比的性能。正在与 webpack 团队合作,以获得更好的多线程性能。
  • 在内部,他们已经建立了一个更好的基础设施来管理开源项目。计划在未来几周内发布更多内容。
  • React Native 欧洲会议正在筹备中,目前还没有什么特别的,但欢迎大家参加!
  • 暂时搁置了 react-navigation,以调查替代方案(特别是原生导航)。

Expo

Facebook

  • React Native 的打包器现在是 Metro Bundler,位于一个独立的仓库中。伦敦的 Metro Bundler 团队很高兴能满足社区的需求,为 React Native 之外的其他用例改进模块化,并提高对问题和 PR 的响应速度。
  • 在接下来的几个月里,React Native 团队将致力于完善原始组件的 API。预计在布局怪癖、可访问性和 Flow 类型方面将有所改进。
  • React Native 团队今年还计划通过重构来改进核心模块化,以完全支持 Windows 和 macOS 等第三方平台。

GeekyAnts

  • 该团队正在开发一款 UI/UX 设计应用程序(代号:Builder),它直接与 .js 文件协同工作。目前,它只支持 React Native。它类似于 Adobe XD 和 Sketch。
  • 该团队正在努力工作,以便您可以在编辑器中加载现有的 React Native 应用程序,进行更改(作为设计师,视觉上),并将更改直接保存到 JS 文件中。
  • 大家正在努力弥合设计师和开发人员之间的鸿沟,并将他们拉到同一个仓库中。
  • 此外,NativeBase 最近达到了 5,000 个 GitHub 星标。

微软

  • CodePush 现已集成到 Mobile Center 中。这是提供与分发、分析和其他服务更加集成体验的第一步。请参阅他们 此处 的公告。
  • VS Code 在调试方面有一个 bug,他们正在努力修复,并将发布一个新版本。
  • 正在调查用于集成测试的 Detox,研究 JSC 上下文以获取与崩溃报告相关的变量。

Shoutem

  • 使用 React Native 社区的工具,让 Shoutem 应用程序的开发变得更加容易。您将能够使用所有 React Native 命令来运行在 Shoutem 上创建的应用程序。
  • 正在研究 React Native 的性能分析工具。他们在设置过程中遇到了很多问题,他们将写下他们在此过程中发现的一些见解。
  • Shoutem 正在努力使 React Native 更容易与现有原生应用程序集成。他们将记录公司内部开发的概念,以征求社区的反馈。

Wix

  • 内部正在努力采用 Detox,以将 Wix 应用程序的重要部分转移到“零手动 QA”。因此,Detox 正在生产环境中被数十名开发人员大量使用,并迅速成熟。
  • 正在努力为 Metro Bundler 添加支持,以便在构建期间覆盖任何文件扩展名。它不仅支持“ios”和“android”,还支持任何自定义扩展名,如“e2e”或“detox”。计划将其用于 E2E 模拟。现在已经有一个名为 react-native-repackager 的库,目前正在处理一个 PR。
  • 正在研究性能测试自动化。这是一个名为 DetoxInstruments 的新仓库。你可以看看,它是开源开发的。
  • 正在与 KPN 的一位贡献者合作,开发 Android 版 Detox 并支持真实设备。
  • 正在考虑将“Detox 作为一个平台”,以允许构建需要自动化模拟器/设备的其他工具。例如,用于 React Native 的 Storybook 或 Ram 关于集成测试的想法。

下一届会议

会议将每四周举行一次。下一次会议定于 2017 年 7 月 12 日。由于我们刚开始这种会议,我们想知道这些会议记录如何使 React Native 社区受益。如果您对我们在接下来的会议中应该涵盖什么内容,或者我们应该如何改进会议成果有任何建议,请随时 在 Twitter 上联系我

React Native 中更好的列表视图

·阅读 6 分钟
Spencer Ahrens
Facebook 软件工程师

在我们在社区群组中发布预告后,许多人已经开始尝试我们的一些新的列表组件,但我们今天正式宣布它们!不再有 ListViewDataSource、陈旧行、被忽略的 bug 或过度的内存消耗——凭借最新的 React Native 2017 年 3 月发布候选版本 (0.43-rc.1),您可以从新的组件套件中选择最适合您用例的组件,开箱即用,具有出色的性能和功能集

<FlatList>

这是用于简单、高性能列表的主力组件。提供一个数据数组和一个 renderItem 函数即可开始使用。

<FlatList
data={[{title: 'Title Text', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

如果您想渲染一组按逻辑部分划分的数据,可能带有部分标题(例如,在按字母排序的地址簿中),并且可能具有异构数据和渲染(例如,一个包含按钮的个人资料视图,然后是合成器,然后是照片网格,然后是朋友网格,最后是故事列表),这就是您应该选择的方法。

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // homogeneous rendering between sections
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // heterogeneous rendering between sections
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

幕后的实现拥有更灵活的 API。如果您的数据不在普通数组中(例如不可变列表),这尤其方便。

功能

列表在许多场景中都会使用,因此我们为新组件提供了丰富的功能,以便开箱即用地处理大多数用例:

  • 滚动加载(onEndReached)。
  • 下拉刷新(onRefresh / refreshing)。
  • 可配置的 可见性 (VPV) 回调(onViewableItemsChanged / viewabilityConfig)。
  • 水平模式(horizontal)。
  • 智能的项目和节分隔符。
  • 多列支持(numColumns
  • scrollToEndscrollToIndexscrollToItem
  • 更好的 Flow 类型。

一些注意事项

  • 当内容滚动出渲染窗口时,项目子树的内部状态不会被保留。请确保您的所有数据都存储在项目数据或外部存储中,例如 Flux、Redux 或 Relay。

  • 这些组件基于 PureComponent,这意味着如果 props 保持浅层相等,它们将不会重新渲染。请确保 renderItem 函数直接依赖的所有内容都作为更新后不为 === 的 props 传递,否则您的 UI 可能不会随着更改而更新。这包括 data 属性和父组件状态。例如

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // New instance breaks `===`
    ...oldState.selected, // copy old data
    [item.key]: !oldState.selected[item.key], // toggle
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem depends on state
    }
    />
    )}
    selected={
    // Can be any prop that doesn't collide with existing props
    this.state.selected // A change to selected should re-render FlatList
    }
    />
  • 为了限制内存使用并实现流畅滚动,内容会在屏幕外异步渲染。这意味着滚动速度可能会快于填充速度,导致短暂地看到空白内容。这是一种权衡,可以根据每个应用程序的需求进行调整,我们正在幕后努力改进它。

  • 默认情况下,这些新列表会在每个项目上查找 key 属性并将其用作 React 键。或者,您可以提供一个自定义的 keyExtractor 属性。

性能

除了简化 API 外,新的列表组件还具有显著的性能增强,主要优点是无论行数多少,内存使用量几乎保持不变。这是通过“虚拟化”渲染窗口之外的元素来实现的,即从组件层次结构中完全卸载它们,并回收 React 组件的 JS 内存,以及影子树和 UI 视图的原生内存。这有一个缺点,即内部组件状态将不会被保留,因此请确保在组件本身之外跟踪任何重要状态,例如在 Relay、Redux 或 Flux 存储中。

限制渲染窗口还减少了 React 和原生平台需要完成的工作量,例如视图遍历。即使您正在渲染数百万个元素中的最后一个,使用这些新列表也无需迭代所有这些元素进行渲染。您甚至可以使用 scrollToIndex 跳转到中间,而不会造成过度渲染。

我们还在调度方面进行了一些改进,这应该有助于提高应用程序的响应能力。渲染窗口边缘的项目在任何活动手势、动画或其他交互完成后,以较低的优先级不频繁地渲染。

高级用法

ListView 不同,渲染窗口中的所有项目在任何 props 更改时都会重新渲染。通常这没有问题,因为窗口化会将项目数量减少到恒定值,但如果您的项目比较复杂,您应该确保遵循 React 性能最佳实践,并在组件内部酌情使用 React.PureComponent 和/或 shouldComponentUpdate,以限制递归子树的重新渲染。

如果您可以在不渲染行的情况下计算其高度,可以通过提供 getItemLayout 属性来改善用户体验。这使得使用例如 scrollToIndex 滚动到特定项目更加流畅,并且会改善滚动指示器 UI,因为可以在不渲染内容的情况下确定内容的高度。

如果您有另一种数据类型,例如不可变列表,那么 <VirtualizedList> 是您的不二选择。它接受一个 getItem 属性,允许您返回任何给定索引的项目数据,并且具有更宽松的 Flow 类型。

如果您有不寻常的用例,还有许多参数可以调整。例如,您可以使用 windowSize 来权衡内存使用与用户体验,maxToRenderPerBatch 来调整填充率与响应性,onEndReachedThreshold 来控制何时进行滚动加载,等等。

未来工作

  • 现有界面的迁移(最终废弃 ListView)。
  • 随着我们发现/听到需求,增加更多功能(请告诉我们!)。
  • 粘性节标题支持。
  • 更多性能优化。
  • 支持带状态的功能项组件。

idx:存在函数

·阅读 2 分钟
Timothy Yung
Facebook 工程经理

在 Facebook,我们经常需要访问通过 GraphQL 获取的数据结构中深度嵌套的值。在访问这些深度嵌套值的过程中,一个或多个中间字段为空是常见的。这些中间字段为空的原因有很多,从隐私检查失败到 null 恰好是表示非致命错误的最灵活方式。

不幸的是,目前访问这些深度嵌套的值既繁琐又冗长。

props.user &&
props.user.friends &&
props.user.friends[0] &&
props.user.friends[0].friends;

有一个 ECMAScript 提案旨在引入存在运算符,这将使其更加方便。但在该提案最终确定之前,我们希望找到一种解决方案,能够改善我们的开发体验,保持现有语言语义,并使用 Flow 鼓励类型安全。

我们提出了一个存在函数,我们称之为 idx

idx(props, _ => _.user.friends[0].friends);

此代码片段中的调用与上述代码片段中的布尔表达式行为类似,但重复显著减少。idx 函数精确地接受两个参数:

  • 任何值,通常是您要访问嵌套值的对象或数组。
  • 一个函数,接收第一个参数并在其上访问嵌套值。

理论上,idx 函数将尝试捕获访问 null 或 undefined 属性时产生的错误。如果捕获到此类错误,它将返回 null 或 undefined。(您可以在 idx.js 中查看其实现方式。)

在实践中,每次嵌套属性访问都进行 try-catch 是缓慢的,并且区分特定类型的 TypeError 是脆弱的。为了解决这些缺点,我们创建了一个 Babel 插件,将上述 idx 调用转换为以下表达式:

props.user == null
? props.user
: props.user.friends == null
? props.user.friends
: props.user.friends[0] == null
? props.user.friends[0]
: props.user.friends[0].friends;

最后,我们为 idx 添加了一个自定义的 Flow 类型声明,允许第二个参数中的遍历得到正确的类型检查,同时允许对可空属性进行嵌套访问。

该函数、Babel 插件和 Flow 声明现在已在 GitHub 上可用。它们通过安装 idxbabel-plugin-idx npm 包,并将“idx”添加到 .babelrc 文件的插件列表中来使用。

推出 Create React Native App

·阅读 3 分钟
Adam Perry
Expo 软件工程师

今天我们宣布推出 Create React Native App:一个让您更容易开始 React Native 项目的新工具!它深受 Create React App 设计的启发,是 FacebookExpo(前身为 Exponent)合作的产物。

许多开发人员在安装和配置 React Native 当前的原生构建依赖项时遇到困难,特别是对于 Android。使用 Create React Native App,无需使用 Xcode 或 Android Studio,您就可以在 Linux 或 Windows 上为 iOS 设备进行开发。这是通过 Expo 应用程序实现的,它加载并运行纯 JavaScript 编写的 CRNA 项目,无需编译任何原生代码。

尝试创建一个新项目(如果已安装 yarn,请替换为相应的 yarn 命令)

$ npm i -g create-react-native-app
$ create-react-native-app my-project
$ cd my-project
$ npm start

这将启动 React Native 打包器并打印一个二维码。在 Expo 应用 中打开它以加载您的 JavaScript。对 console.log 的调用将转发到您的终端。您可以使用任何标准的 React Native API 以及 Expo SDK

那原生代码呢?

许多 React Native 项目都具有需要编译的 Java 或 Objective-C/Swift 依赖项。Expo 应用程序确实包含用于摄像头、视频、联系人等的 API,并捆绑了诸如 Airbnb 的 react-native-mapsFacebook 认证 等流行库。但是,如果您需要 Expo 不捆绑的原生代码依赖项,那么您可能需要为其设置自己的构建配置。就像 Create React App 一样,CRNA 支持“弹出”功能。

您可以运行 npm run eject 来获得一个与 react-native init 生成的项目非常相似的项目。届时,您将需要 Xcode 和/或 Android Studio,就像您使用 react-native init 开始时一样,使用 react-native link 添加库将起作用,并且您将对原生代码编译过程拥有完全控制权。

问题?反馈?

Create React Native App 现在已足够稳定,可供一般使用,这意味着我们非常渴望了解您的使用体验!您可以通过 Twitter 联系我,或者在 GitHub 仓库上提交问题。非常欢迎拉取请求!

为 Animated 使用原生驱动

·阅读 6 分钟
Janic Duplessis
App & Flow 软件工程师

在过去的一年里,我们一直致力于提高使用 Animated 库的动画性能。动画对于创造美观的用户体验非常重要,但也很难做好。我们希望让开发人员能够轻松创建高性能动画,而不必担心某些代码导致卡顿。

这是什么?

Animated API 在设计时考虑了一个非常重要的约束,那就是它是可序列化的。这意味着我们可以在动画开始之前将所有动画信息发送到原生端,并允许原生代码在 UI 线程上执行动画,而无需在每一帧都通过桥接。这非常有用,因为一旦动画开始,JS 线程即使被阻塞,动画仍能流畅运行。在实践中,这种情况经常发生,因为用户代码在 JS 线程上运行,并且 React 渲染也可能长时间锁定 JS。

一些历史...

这个项目大约一年前开始,当时 Expo 在 Android 上构建了 li.st 应用程序。Krzysztof Magiera 受聘构建了 Android 上的初始实现。它最终运行良好,li.st 是第一个使用 Animated 实现原生驱动动画的应用程序。几个月后,Brandon Withrow 构建了 iOS 上的初始实现。之后,Ryan Gomba 和我致力于添加缺失的功能,例如支持 Animated.event 以及解决我们在生产应用程序中使用时发现的错误。这确实是一项社区努力,我要感谢所有参与者以及 Expo 赞助了大部分开发工作。它现在被 React Native 中的 Touchable 组件以及新发布的 React Navigation 库中的导航动画所使用。

它是如何工作的?

首先,让我们看看目前使用 JS 驱动的 Animated 动画是如何工作的。在使用 Animated 时,您声明一个节点图,这些节点表示您想要执行的动画,然后使用驱动器根据预定义的曲线更新 Animated 值。您还可以通过使用 Animated.event 将 Animated 值连接到 View 的事件来更新它。

以下是动画步骤及其发生位置的细分:

  • JS:动画驱动器使用 requestAnimationFrame 在每一帧执行,并根据动画曲线计算出的新值来更新其驱动的值。
  • JS:计算中间值并将其传递给附加到 View 的 props 节点。
  • JS:使用 setNativeProps 更新 View
  • JS 到原生桥接。
  • 原生:UIViewandroid.View 被更新。

如您所见,大部分工作发生在 JS 线程上。如果 JS 线程被阻塞,动画将跳帧。它还需要在每一帧通过 JS 到原生桥接来更新原生视图。

原生驱动所做的是将所有这些步骤移至原生。由于 Animated 生成一个动画节点图,因此可以在动画开始时将其序列化并仅发送到原生一次,从而无需回调到 JS 线程;原生代码可以在 UI 线程上直接更新视图的每一帧。

以下是我们如何序列化一个动画值和一个插值节点的示例(不是精确实现,只是一个示例)。

创建原生值节点,这是将要动画化的值

NativeAnimatedModule.createNode({
id: 1,
type: 'value',
initialValue: 0,
});

创建原生插值节点,这告诉原生驱动器如何插值一个值

NativeAnimatedModule.createNode({
id: 2,
type: 'interpolation',
inputRange: [0, 10],
outputRange: [10, 0],
extrapolate: 'clamp',
});

创建原生 props 节点,这告诉原生驱动器它附加到视图上的哪个 prop

NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});

连接节点

NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);

将 props 节点连接到视图

NativeAnimatedModule.connectToView(3, ReactNative.findNodeHandle(viewRef));

有了这些,原生动画模块就拥有了直接更新原生视图所需的所有信息,而无需前往 JS 计算任何值。

剩下的就是通过指定我们想要的动画曲线类型和要更新的动画值来实际开始动画。计时动画也可以通过在 JS 中提前计算动画的每一帧来简化,从而使原生实现更小。

NativeAnimatedModule.startAnimation({
type: 'timing',
frames: [0, 0.1, 0.2, 0.4, 0.65, ...],
animatedValueId: 1,
});

现在,以下是动画运行时发生的情况细分:

  • 原生:原生动画驱动器使用 CADisplayLinkandroid.view.Choreographer 在每一帧执行,并根据动画曲线计算出的新值来更新其驱动的值。
  • 原生:计算中间值并将其传递给附加到原生视图的 props 节点。
  • 原生:UIViewandroid.View 被更新。

如您所见,不再需要 JS 线程,也不再需要桥接,这意味着更快的动画!🎉🎉

我如何在我的应用程序中使用它?

对于普通动画,答案很简单,只需在启动动画时将 useNativeDriver: true 添加到动画配置中即可。

之前

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
}).start();

之后

Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Add this
}).start();

动画值只与一个驱动器兼容,因此如果在某个值上启动动画时使用原生驱动器,请确保该值上的每个动画也使用原生驱动器。

它也适用于 Animated.event,如果您有一个动画必须跟随滚动位置,这非常有用,因为如果没有原生驱动,由于 React Native 的异步特性,它总是比手势晚一帧运行。

之前

<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }]
)}
>
{content}
</ScrollView>

之后

<Animated.ScrollView // <-- Use the Animated ScrollView wrapper
scrollEventThrottle={1} // <-- Use 1 here to make sure no events are ever missed
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- Add this
)}
>
{content}
</Animated.ScrollView>

注意事项

并非所有您可以使用 Animated 实现的功能目前都受 Native Animated 支持。主要限制是您只能对非布局属性进行动画处理,例如 transformopacity 可以使用,但 Flexbox 和位置属性则不行。另一个是 Animated.event,它只适用于直接事件而非冒泡事件。这意味着它不适用于 PanResponder,但适用于 ScrollView#onScroll 等。

原生动画也作为 React Native 的一部分存在了相当长一段时间,但从未被文档化,因为它被认为是实验性的。因此,如果您想使用此功能,请确保您使用的是 React Native 的最新版本 (0.40+)。

资源

有关动画的更多信息,我推荐观看 Christopher Chedeau这个演讲

如果您想深入了解动画以及如何将其卸载到原生以改善用户体验,还可以观看 Krzysztof Magiera这个演讲

React Native 应用的从右到左布局支持

·阅读 8 分钟
Mengjue (Mandy) Wang
Facebook 软件工程师实习生

将应用程序发布到应用商店后,国际化是扩大受众范围的下一步。全球有 20 多个国家和众多人口使用从右到左 (RTL) 语言。因此,使您的应用程序支持 RTL 对他们来说是必要的。

我们很高兴地宣布,React Native 已经改进以支持 RTL 布局。该功能目前已在 react-native 主分支中可用,并将在下一个 RC 版本 v0.33.0-rc 中提供。

这涉及到修改 css-layout(RN 使用的核心布局引擎)和 RN 核心实现,以及特定的开源 JS 组件以支持 RTL。

为了在生产环境中对 RTL 支持进行实战测试,最新版本的 Facebook 广告管理工具应用程序(第一个 100% RN 跨平台应用程序)现已提供阿拉伯语和希伯来语版本,并支持 iOSAndroid 的 RTL 布局。以下是它在这些 RTL 语言中的显示效果:

RN 中 RTL 支持的变更概述

css-layout 已经有了布局中的 startend 概念。在从左到右 (LTR) 布局中,start 意味着 leftend 意味着 right。但在 RTL 中,start 意味着 rightend 意味着 left。这意味着我们可以让 RN 依赖 startend 的计算来得出正确的布局,其中包括 positionpaddingmargin

此外,css-layout 已经使每个组件的方向从其父组件继承。这意味着,我们只需将根组件的方向设置为 RTL,整个应用程序就会翻转。

下图高层次地描述了这些变化:

这些包括:

通过此更新,当您允许应用使用 RTL 布局时:

  • 每个组件布局将水平翻转
  • 如果您使用的是支持 RTL 的开源组件,一些手势和动画将自动具有 RTL 布局
  • 可能需要最少的额外工作才能使您的应用完全支持 RTL

使应用程序支持 RTL

  1. 要支持 RTL,您应该首先将 RTL 语言包添加到您的应用程序中。

  2. 通过在原生代码开头调用 allowRTL() 函数,允许您的应用程序使用 RTL 布局。我们提供了这个工具,以便仅在您的应用程序准备就绪时才应用 RTL 布局。示例如下:

    iOS

    // in AppDelegate.m
    [[RCTI18nUtil sharedInstance] allowRTL:YES];

    Android

    // in MainActivity.java
    I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
    sharedI18nUtilInstance.allowRTL(context, true);
  3. 对于 Android,您需要在 AndroidManifest.xml 文件中的 <application> 元素中添加 android:supportsRtl="true"

现在,当您重新编译应用程序并将设备语言更改为 RTL 语言(例如阿拉伯语或希伯来语)时,您的应用程序布局应该会自动变为 RTL。

编写支持 RTL 的组件

一般来说,大多数组件已经支持 RTL,例如:

  • 从左到右布局
  • 从右到左布局

但是,有几种情况需要注意,您将需要 I18nManager。在 I18nManager 中,有一个常量 isRTL 来指示应用程序布局是否为 RTL,以便您可以根据布局进行必要的更改。

具有方向意义的图标

如果您的组件包含图标或图像,它们在 LTR 和 RTL 布局中会以相同的方式显示,因为 RN 不会翻转您的源图像。因此,您应该根据布局样式翻转它们。

  • 从左到右布局
  • 从右到左布局

以下是根据方向翻转图标的两种方法:

  • 为图像组件添加 transform 样式

    <Image
    source={...}
    style={{transform: [{scaleX: I18nManager.isRTL ? -1 : 1}]}}
    />
  • 或者,根据方向更改图像源

    let imageSource = require('./back.png');
    if (I18nManager.isRTL) {
    imageSource = require('./forward.png');
    }
    return <Image source={imageSource} />;

手势和动画

在 Android 和 iOS 开发中,当您切换到 RTL 布局时,手势和动画与 LTR 布局是相反的。目前,在 RN 中,手势和动画不支持 RN 核心代码级别,而是在组件级别。好消息是,其中一些组件今天已经支持 RTL,例如 SwipeableRowNavigationExperimental。但是,其他带有手势的组件需要手动支持 RTL。

一个说明手势 RTL 支持的好例子是 SwipeableRow

手势示例
// SwipeableRow.js
_isSwipingExcessivelyRightFromClosedPosition(gestureState: Object): boolean {
// ...
const gestureStateDx = IS_RTL ? -gestureState.dx : gestureState.dx;
return (
this._isSwipingRightFromClosed(gestureState) &&
gestureStateDx > RIGHT_SWIPE_THRESHOLD
);
},
动画示例
// SwipeableRow.js
_animateBounceBack(duration: number): void {
// ...
const swipeBounceBackDistance = IS_RTL ?
-RIGHT_SWIPE_BOUNCE_BACK_DISTANCE :
RIGHT_SWIPE_BOUNCE_BACK_DISTANCE;
this._animateTo(
-swipeBounceBackDistance,
duration,
this._animateToClosedPositionDuringBounce,
);
},

维护支持 RTL 的应用程序

即使在最初发布兼容 RTL 的应用程序之后,您可能仍需要迭代新功能。为了提高开发效率,I18nManager 提供了 forceRTL() 函数,用于更快速地测试 RTL,而无需更改测试设备的语言。您可能希望在应用程序中提供一个简单的开关。以下是 RNTester 中 RTL 示例的一个例子:

<RNTesterBlock title={'Quickly Test RTL Layout'}>
<View style={styles.flexDirectionRow}>
<Text style={styles.switchRowTextView}>forceRTL</Text>
<View style={styles.switchRowSwitchView}>
<Switch
onValueChange={this._onDirectionChange}
style={styles.rightAlignStyle}
value={this.state.isRTL}
/>
</View>
</View>
</RNTesterBlock>;

_onDirectionChange = () => {
I18nManager.forceRTL(!this.state.isRTL);
this.setState({isRTL: !this.state.isRTL});
Alert.alert(
'Reload this page',
'Please reload this page to change the UI direction! ' +
'All examples in this app will be affected. ' +
'Check them out to see what they look like in RTL layout.',
);
};

在开发新功能时,您可以轻松切换此按钮并重新加载应用程序以查看 RTL 布局。这样做的好处是您无需更改语言设置即可进行测试,但是某些文本对齐方式不会改变,这将在下一节中解释。因此,在发布应用程序之前,始终建议使用 RTL 语言测试您的应用程序。

限制和未来计划

RTL 支持应该涵盖您应用程序的大部分用户体验;但是,目前存在一些限制:

  • Android 和 iOS 中的文本对齐行为不同
    • 在 iOS 中,默认文本对齐方式取决于活动的语言包,它们始终保持在一侧。在 Android 中,默认文本对齐方式取决于文本内容的语言,即英语将左对齐,阿拉伯语将右对齐。
    • 理论上,这应该在跨平台保持一致,但有些人在使用应用程序时可能更喜欢一种行为而不是另一种。可能需要进行更多的用户体验研究才能找出文本对齐的最佳实践。
  • 没有“真正”的左/右

    如前所述,我们将 JavaScript 侧的 left/right 样式映射到 start/end。对于 RTL(从右到左)布局,代码中的所有 left 在屏幕上都变为“右”,而代码中的 right 在屏幕上都变为“左”。这很方便,因为您无需过多更改产品代码,但也意味着无法在代码中指定“真正的左”或“真正的右”。未来,可能需要允许组件独立于语言控制其方向。

  • 使手势和动画的 RTL 支持更对开发者友好

    目前,要使手势和动画与 RTL 兼容,仍然需要一些编程工作。未来,找到一种使手势和动画的 RTL 支持对开发者更友好的方法将是理想的。

试试看!

查看 RNTester 中的 RTLExample,了解更多关于 RTL 支持的信息,并告诉我们它对您来说效果如何!

最后,感谢您的阅读!我们希望 React Native 的 RTL 支持能帮助您将应用推广给全球受众!

深入了解 React Native 性能

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

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

为何如此匆忙?

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

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

这是一篇摘录。请在 Facebook Code 上阅读全文。

引入热重载

·阅读 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 版本中可用,您可以启用它

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

简要实现

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

热重载是基于一项名为 模块热替换(Hot Module Replacement,简称 HMR)的功能构建的。它最初由 webpack 引入,我们将其实现在 React Native Packager 内部。HMR 使 Packager 监视文件更改,并将 HMR 更新发送到应用中包含的轻量级 HMR 运行时。

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

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

// 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 的更改,我们需要清除 log 的缓存导出,因为 log 依赖的一个模块被热替换了(步骤 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 执行相同的操作,并在那里结束,因为没有模块依赖于它。

为了遍历依赖树,运行时会在 HMR 更新时从 Packager 接收反向依赖树。对于这个例子,运行时将接收到一个像这样的 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-transform,就像您在使用了 webpack 的 React Web 项目中一样。

Redux 存储

要在 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 不知道如何自行接受,因此它会查找所有引用该 reducer 的模块并尝试接受它们。最终,流程将到达单个 store,即 configureStore 模块,它将接受 HMR 更新。

结论

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

借助 React Native,我们有机会重新思考构建应用的方式,以提供出色的开发者体验。热重载只是其中一部分,我们还能做哪些“疯狂”的改进来使其变得更好?