跳至主要内容

Marketplace 中的 React Native 性能

·阅读时长 5 分钟
Facebook 软件工程师

React Native 用于 Facebook 家族中多个地方的多个应用,包括 Facebook 主应用中的顶级标签。我们这篇文章的重点是一个高度可见的产品,Marketplace。它在十几个国家/地区可用,使用户能够发现其他用户提供的产品和服务。

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

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

免责声明:比例并非代表实际情况,并且会根据 React Native 的配置和使用方式而有所不同。

我们首先初始化 React Native 核心(也称为“桥”),然后运行特定于产品的 JavaScript,该 JavaScript 确定 React Native 将在原生处理时间内呈现哪些原生视图。

不同的方法

我们早期犯的一个错误是让 Systrace 和 CTScan 驱动我们的性能工作。这些工具帮助我们在 2016 年找到了许多唾手可得的成果,但我们发现 Systrace 和 CTScan 并不能代表生产场景,也无法模拟实际情况下的发生情况。时间段内各个部分的比例通常不准确,有时甚至完全偏离目标。在极端情况下,我们预计需要几毫秒才能完成的事情实际上需要数百或数千毫秒。也就是说,CTScan 很有用,我们发现它在生产之前捕获了三分之一的回归。

在 Android 上,我们将这些工具的缺点归因于以下事实:1)React Native 是一个多线程框架,2)Marketplace 与许多复杂的视图(如 Newsfeed 和其他顶级标签)位于同一位置,以及 3)计算时间差异很大。因此,在本学期,我们让生产测量和分解几乎驱动了我们所有的决策和优先级。

生产检测之路

在表面上,对生产进行检测可能听起来很简单,但事实证明这是一个非常复杂的过程。它需要多个 2-3 周的迭代周期;由于在 master 中落地提交的延迟、将应用推送到 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

·阅读时长:8分钟
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 的拉取请求,并且已经发布了其他改进。最近有几位行业领导者采用了它。可能计划在该领域开始全职付费工作。
  • Michał Pierzchała 来自 Jest 团队,本月加入了 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 中添加了对安装 npm 包的初始支持,使用 Expo SDK 19。如果您遇到任何问题,请告诉我们,我们仍在解决一些 bug。与 Native Directory 结合使用,这应该可以轻松测试那些只有 JS 依赖项或包含在 Expo SDK 中的依赖项的库。试试看
  • 发布了 Expo SDK19,其中包含大量改进,并且我们现在正在使用 更新后的 Android JSC
  • Alexander Kotliarskyi 合作编写文档中的指南,其中包含一些关于如何改善应用程序用户体验的技巧。请加入进来,添加内容或帮助撰写部分内容!
  • 继续进行以下工作:音频/视频、相机、手势(与 Software Mansion 合作,react-native-gesture-handler)、GL 相机集成,并希望在 SDK20(8 月)中首次实现其中一些功能,并在那时对其他功能进行重大改进。我们才刚刚开始在 Expo 客户端中构建用于后台工作的基础设施(地理位置、音频、处理通知等)。
  • Adam Miskiewiczreact-navigation 中模仿 UINavigationController 的过渡方面取得了一些不错的进展。查看他 推文 中的早期版本 - 即将发布。还可以查看 MaskedViewIOS,他已将其 上游化。如果您有能力和愿望为 Android 实现 MaskedView,那将非常棒!

Facebook

  • Facebook 在内部探索能够在 React Native 中嵌入原生 ComponentKitLitho 组件。
  • 非常欢迎对 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

Microsoft

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

Shoutem

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

Wix

  • 我们开始为Metro Bundler添加一个pull request,使其具备react-native-repackager的功能。我们更新了react-native-repackager以支持RN 44(我们在生产环境中使用)。我们将其用于detox的模拟基础设施。
  • 过去三周里,我们一直在使用detox测试Wix应用程序。这是一个了不起的学习经历,了解如何在如此规模的应用程序(超过40名工程师)中减少手动QA。因此我们解决了一些detox的问题,新版本刚刚发布。我很高兴地报告说,我们正在践行“零不稳定性政策”,到目前为止,测试一直稳定地通过。
  • Detox for Android进展顺利。我们正在获得社区的大力帮助。我们预计大约两周后发布初始版本。
  • 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 Monthly的任务简单明了:**改进React Native社区**。展示团队的工作可以促进团队之间离线合作。

团队

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

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

笔记

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

Airbnb

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

Callstack

  • 正在研究通过使用Detox进行端到端测试来改进发布流程。Pull request很快就会上线。
  • 他们一直在处理的Blob pull request已经合并,后续的pull request即将到来。
  • 在内部项目中增加Haul的采用率,以查看它与Metro Bundler相比的性能。与webpack团队一起致力于更好的多线程性能。
  • 在内部,他们已经实施了更好的基础设施来管理开源项目。计划在未来几周内发布更多内容。
  • React Native欧洲大会即将到来,目前还没有什么有趣的事情,但大家都被邀请了!
  • 暂时搁置了react-navigation一段时间,以研究替代方案(尤其是原生导航)。

Expo

Facebook

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

GeekyAnts

  • 团队正在开发一个UI/UX设计应用程序(代号:Builder),它直接使用.js文件。目前,它只支持React Native。它类似于Adobe XD和Sketch。
  • 团队正在努力,以便您可以在编辑器中加载现有的React Native应用程序,进行更改(以视觉方式,作为设计师)并将更改直接保存到JS文件中。
  • 人们正试图弥合设计师和开发人员之间的差距,并将他们带到同一个仓库中。
  • 此外,NativeBase最近在GitHub上获得了5000颗星。

Microsoft

  • CodePush现在已集成到Mobile Center中。这是提供更完整集成体验的第一步,包括分发、分析和其他服务。请查看他们的公告此处
  • VS Code在调试方面存在一个错误,他们目前正在修复该错误,并将发布一个新的版本。
  • 正在研究将Detox用于集成测试,查看JSC Context以获取崩溃报告中的变量。

Shoutem

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

Wix

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

下一期

会议将每四周举行一次。下一期会议定于2017年7月12日举行。由于我们刚刚开始这次会议,我们想知道这些笔记如何惠及React Native社区。如果您有任何关于我们在后续会议中应该涵盖哪些内容或如何改进会议输出的建议,请随时在Twitter上联系我。

改进 React Native 中的列表视图

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

在我们在社区群组中发布的预告公告之后,你们中的许多人已经开始使用我们的一些新的列表组件了,但我们今天正式宣布它们!不再有ListViewDataSource、陈旧的行、被忽略的错误或过度的内存消耗 - 使用最新的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 prop 和父组件状态。例如

    <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 prop 并将其用作 React key。或者,您可以提供自定义的 keyExtractor prop。

性能

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

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

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

高级用法

ListView 不同,渲染窗口中的所有项目在任何 props 发生更改时都会重新渲染。通常情况下,这很好,因为窗口化将项目数量减少到一个常数,但是如果您的项目比较复杂,则应确保遵循 React 的最佳性能实践,并在您的组件中根据需要使用 React.PureComponent 和/或 shouldComponentUpdate 来限制递归子树的重新渲染。

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

如果您有其他数据类型,例如不可变列表,则 <VirtualizedList> 是最佳选择。它采用一个 getItem prop,允许您为任何给定的索引返回项目数据,并且具有更宽松的 Flow 类型。

如果您有特殊用例,还可以调整很多参数。例如,您可以使用 windowSize 在内存使用量与用户体验之间进行权衡,使用 maxToRenderPerBatch 调整填充率与响应能力,使用 onEndReachedThreshold 控制滚动加载何时发生,等等。

未来工作

  • 迁移现有的界面(最终弃用 ListView)。
  • 根据我们的观察和反馈添加更多功能(请告知我们!)。
  • 支持粘性节标题。
  • 更多性能优化。
  • 支持具有状态的函数式项目组件。

idx:存在函数

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

在 Facebook,我们经常需要访问使用 GraphQL 获取的数据结构中的深度嵌套值。在访问这些深度嵌套值的路上,一个或多个中间字段通常是可为空的。这些中间字段可能由于各种原因为空,从隐私检查失败到仅仅是空值恰好是表示非致命错误的最灵活的方式。

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

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中实现它。)

在实践中,尝试捕获每个嵌套属性访问速度很慢,并且区分特定类型的 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 包并向 .babelrc 文件中的插件列表添加“idx”来使用。

介绍 Create React Native App

·阅读时间:2 分钟
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 命令)

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

这将启动 React Native 打包器并打印一个 QR 码。在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 使用 Native Driver

·阅读时间:7 分钟
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 值连接到 View 的事件(使用 Animated.event)来更新它。

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

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

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

原生驱动器所做的是将所有这些步骤移动到原生端。由于 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,
});

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

  • Native:原生动画驱动器使用 CADisplayLinkandroid.view.Choreographer 在每一帧执行,并根据动画曲线计算的新值更新它驱动的值。
  • Native:计算中间值并传递给附加到原生视图的 props 节点。
  • Native:更新 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 值仅与一个驱动器兼容,因此如果您在某个值的动画开始时使用原生驱动器,请确保该值上的每个动画也使用原生驱动器。

它也适用于 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 等内容。

Native Animated 也已成为 React Native 的一部分有一段时间了,但从未记录在案,因为它被认为是实验性的。因此,如果您想使用此功能,请确保您使用的是最新版本 (0.40+) 的 React Native。

资源

有关动画的更多信息,我建议观看 此演讲,演讲者是 Christopher Chedeau

如果您想深入了解动画以及如何将它们卸载到原生端以改善用户体验,还可以观看 此演讲,演讲者是 Krzysztof Magiera

每月发布节奏:发布 12 月和 1 月 RC 版

·阅读时间:2 分钟
Eric Vicenti
Facebook 工程师

在 React Native 推出后不久,我们开始每两周发布一次,以帮助社区采用新功能,同时保持版本的稳定性以供生产使用。在 Facebook,我们必须每两周稳定一次代码库,以便发布我们的生产 iOS 应用,因此我们决定以相同的节奏发布开源版本。现在,许多 Facebook 应用每周发布一次,尤其是在 Android 上。因为我们每周从 master 发布,所以我们需要保持它的稳定性。因此,每两周发布一次的节奏对内部贡献者也没有任何好处了。

我们经常从社区收到反馈,认为发布速度难以跟上。像 Expo 这样的工具不得不跳过每次发布,以便管理版本快速变化的情况。因此,很明显,每两周发布一次的做法对社区没有带来好处。

现在每月发布

我们很高兴地宣布新的每月发布节奏,以及 2016 年 12 月的版本 v0.40,它在过去一个月里一直在稳定,并且已准备好被采用。(只需确保 更新 iOS 上原生模块中的头文件)。

虽然可能会有一些偏差(几天),以避免周末或处理意外问题,但您现在可以预期某个版本会在每个月的第一天可用,并在最后一天发布。

使用当前月份以获得最佳支持

1 月份的候选版本已准备好进行尝试,您可以在 此处查看新功能

要查看即将发生的更改并向 React Native 贡献者提供更好的反馈,请尽可能始终使用当前月份的候选版本。到每个版本在月末发布时,它包含的更改已经在 Facebook 生产应用中发布了两周以上。

您可以使用新的 react-native-git-upgrade 命令轻松升级您的应用

npm install -g react-native-git-upgrade
react-native-git-upgrade 0.41.0-rc.0

我们希望这种更简单的方法能让社区更容易跟踪 React Native 中的更改,并尽快采用新版本!

(感谢 Martin Konicek 提出这个计划,以及 Mike Grabowski 使其成为现实)

借助 Git 更轻松地升级

·阅读时间:4分钟
Nicolas Cuillery
Zenika 的 JavaScript 顾问和培训师

升级到新版本的 React Native 一直很困难。您可能之前见过类似这样的情况

这些选项都不理想。通过覆盖文件,我们会丢失本地更改。如果不覆盖,我们就无法获得最新更新。

今天,我自豪地推出了一种有助于解决此问题的新工具。该工具称为 react-native-git-upgrade,并在后台使用 Git,尽可能自动解决冲突。

用法

**要求**:Git 必须在 PATH 中可用。您的项目不必由 Git 管理。

全局安装 react-native-git-upgrade

$ npm install -g react-native-git-upgrade

或者,使用 Yarn

$ yarn global add react-native-git-upgrade

然后,在您的项目目录中运行它

$ cd MyProject
$ react-native-git-upgrade 0.38.0

注意:请**不要**运行“npm install”来安装新版本的 react-native。该工具需要能够比较旧项目模板和新项目模板才能正常工作。只需在您的应用文件夹中运行它(如上所示),同时仍在使用旧版本。

示例输出

您也可以在不带任何参数的情况下运行 react-native-git-upgrade 以升级到最新版本的 React Native。

我们尝试保留您在 Android 和 iOS 构建文件中的更改,因此您无需在升级后运行 react-native link

我们设计了尽可能不侵入性的实现方式。它完全基于一个在临时目录中即时创建的本地 Git 仓库。它不会干扰您的项目仓库(无论您使用什么 VCS:Git、SVN、Mercurial……或者没有)。如果发生意外错误,您的源代码将被恢复。

它是如何工作的?

关键步骤是生成一个 Git 补丁。该补丁包含 React Native 模板在您的应用正在使用的版本和新版本之间所做的所有更改。

为了获得此补丁,我们需要从 node_modules 目录中 react-native 包中嵌入的模板生成一个应用(这些是 react-native init 命令使用的相同模板)。然后,在从当前版本和新版本的模板生成原生应用后,Git 能够生成一个适用于您的项目的补丁(即包含您的应用名称)。

[...]

diff --git a/ios/MyAwesomeApp/Info.plist b/ios/MyAwesomeApp/Info.plist
index e98ebb0..2fb6a11 100644
--- a/ios/MyAwesomeApp/Info.plist
+++ b/ios/MyAwesomeApp/Info.plist
@@ -45,7 +45,7 @@
<dict>
<key>localhost</key>
<dict>
- <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
+ <key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
[...]

现在我们需要做的就是将此补丁应用于您的源文件。虽然旧的 react-native upgrade 过程会提示您进行任何细微的差异,但 Git 能够使用其 3 路合并算法自动合并大多数更改,并最终留下我们熟悉的冲突分隔符。

    13B07F951A680F5B00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
<<<<<<< ours
CODE_SIGN_IDENTITY = "iPhone Developer";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/HockeySDK.embeddedframework",
"$(PROJECT_DIR)/HockeySDK-iOS/HockeySDK.embeddedframework",
);
=======
CURRENT_PROJECT_VERSION = 1;
>>>>>>> theirs
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../node_modules/react-native/React/**",
"$(SRCROOT)/../node_modules/react-native-code-push/ios/CodePush/**",
);

这些冲突通常很容易理解。分隔符 ours 代表“您的团队”,而 theirs 可以被视为“React Native 团队”。

为什么引入一个新的全局包?

React Native 带有一个全局 CLI(react-native-cli 包),它将命令委托给 node_modules/react-native/local-cli 目录中嵌入的本地 CLI。

如上所述,该过程必须从您当前的 React Native 版本开始。如果我们将实现嵌入到 local-cli 中,那么在使用旧版本的 React Native 时将无法享受此功能。例如,如果此新的升级代码仅在 0.38.0 版本中发布,则您将无法从 0.29.2 升级到 0.38.0。

基于 Git 的升级极大地改善了开发人员体验,并且使其对每个人都可用非常重要。通过使用一个单独的全局安装的包 react-native-git-upgrade,您可以立即使用此新代码,无论您的项目使用哪个版本的 React Native。

另一个原因是 Martin Konicek 最近对 Yeoman wipeout 的操作。我们不想将这些 Yeoman 依赖项放回 react-native 包中,以便能够评估旧模板以创建补丁。

试一试并提供反馈

总之,我想说,享受这个功能,并随时提出改进建议、报告问题,尤其是发送 pull 请求。每个环境都略有不同,每个 React Native 项目也各不相同,我们需要您的反馈才能让它对每个人都运作良好。

谢谢!

我要感谢很棒的公司 ZenikaM6 Web (已存档),如果没有他们,这一切将无法实现!

引入 Button、使用 Yarn 加速安装以及公开路线图

·阅读时间:3 分钟
Héctor Ramos
Héctor Ramos
Facebook 前开发者布道师

我们从很多人那里听说,React Native 正在发生很多事情,很难跟踪正在发生的事情。为了帮助传达正在进行的工作,我们现在发布了 React Native 的路线图。在高层次上,这项工作可以分解为三个优先级

  • 核心库。为最常用的组件和 API 添加更多功能。
  • 稳定性。改进底层基础设施以减少错误并提高代码质量。
  • 开发者体验。帮助 React Native 开发人员更快地开发。

如果您对路线图上您认为有价值的功能有任何建议,请查看 Canny,您可以在其中提出新功能并讨论现有提案。

React Native 的新功能

React Native 0.37 版于今天发布,引入了一个新的核心组件,可以非常轻松地将可触摸按钮添加到任何应用中。我们还引入了对新的 Yarn 包管理器的支持,这应该会加快更新应用依赖项的整个过程。

引入 Button

今天,我们引入了一个基本的 <Button /> 组件,它在每个平台上都看起来很棒。这解决了我们收到的最常见的反馈之一:React Native 是唯一一个没有开箱即用的按钮的移动开发工具包。

Simple Button on Android, iOS

<Button
onPress={onPressMe}
title="Press Me"
accessibilityLabel="Learn more about this Simple Button"
/>

经验丰富的 React Native 开发人员知道如何制作按钮:使用 TouchableOpacity 获取 iOS 上的默认外观,使用 TouchableNativeFeedback 获取 Android 上的波纹效果,然后应用一些样式。自定义按钮并不难构建或安装,但我们的目标是使 React Native 变得非常易于学习。通过在核心组件中添加一个基本的按钮,新手可以在第一天就开发出很棒的东西,而不是将时间花在格式化按钮和学习 Touchable 的细微差别上。

Button 旨在在每个平台上都能正常工作并具有原生外观,因此它不支持自定义按钮具有的所有花里胡哨的功能。它是一个很好的起点,但并非旨在替代您所有现有的按钮。要了解更多信息,请查看 新的 Button 文档,其中包含一个可运行的示例!

使用 Yarn 加速 react-native init

您现在可以使用 Yarn(JavaScript 的新包管理器)来显著加快 react-native init 的速度。要查看加速效果,请安装 yarn并将您的 react-native-cli 升级到 1.2.0

$ npm install -g react-native-cli

现在,在设置新应用时,您应该会看到“Using yarn”。

Using yarn

在简单的本地测试中,react-native init良好的网络环境下大约 1 分钟内完成(而使用 npm 3.10.8 时大约需要 3 分钟)。安装 yarn 是可选的,但强烈推荐。

谢谢!

我们要感谢所有为本次发布做出贡献的人。完整的 发行说明现已在 GitHub 上发布。凭借超过 20 多个错误修复和新功能,React Native благодаря вам变得越来越好。