跳到主要内容

React Native 月刊 #4

· 阅读需 3 分钟
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack

React Native 月度会议持续进行!以下是各团队的会议记录:

Callstack

  • React Native EU 已经结束。来自33个国家的300多名参与者访问了弗罗茨瓦夫。演讲视频可以在 YouTube 上观看。
  • 会议结束后,我们正逐步恢复开源项目的发布时间表。值得一提的是,我们正在开发react-native-opentok的下一个版本,修复了大部分现有问题。

GeekyAnts

尝试通过以下方式降低开发者使用 React Native 的门槛:

  • React Native EU 宣布了 BuilderX.io。BuilderX 是一款设计工具,可以直接操作 JavaScript 文件(目前仅支持 React Native),生成美观、易读且可编辑的代码。
  • 推出了 ReactNativeSeed.com ,为你的下一个 React Native 项目提供一套初始化模板。模板选项丰富,包括 TypeScript 和 Flow 用于数据类型,状态管理支持 MobX、Redux 和 mobx-state-tree,支持 CRNA 及纯 React-Native 技术栈。

Expo

  • 即将发布 SDK 21,支持 react-native 0.48.3,同时带来一系列修复、稳定性提升和新功能,包括视频录制、新的启动屏 API、支持 react-native-gesture-handler 以及改进的错误处理。
  • 关于 react-native-gesture-handlerKrzysztof Magiera 来自 Software Mansion 持续推进此项目,我们协助测试并资助了部分开发时间。SDK 21 集成该功能后,大家可以在 Snack 中轻松试用,非常期待大家的创意展现。
  • 关于改进的错误日志和处理——详情可见这段 Expo 内部 PR 的 gist(特别是“问题2”部分),以及这次提交,它处理了导入 npm 标准库模块失败的情况。React Native 有很多机会在上游改进错误信息,我们会持续推动相关 PR,也欢迎社区加入。
  • native.directory 持续增长,你可以从GitHub 仓库添加你的项目。
  • 参加北美各地的黑客马拉松活动,包括 PennAppsHack The NorthHackMIT 以及即将到来的 MHacks

Facebook

  • 正在改进 Android 平台上的 <Text><TextInput> 组件。(包括 <TextInput> 的原生自动增长,深层嵌套的 <Text> 组件布局问题,更好的代码结构以及性能优化)。
  • 我们依旧在寻找更多贡献者,帮助处理问题和 Pull Request。

Microsoft

  • 发布了 CodePush 的代码签名功能。React Native 开发者现在可以在 CodePush 中对应用包进行签名。相关公告见 这里
  • 正在完成 CodePush 与 Mobile Center 的集成,同时考虑增加测试和崩溃集成。

下一次会议

下一次会议定于 2017 年 10 月 10 日星期三。鉴于这仅是我们的第四次会议,我们希望了解这些会议记录如何为 React Native 社区带来帮助。如果你有任何关于如何改进会议成果的建议,欢迎通过 Twitter 联系我。

React Native 月刊 #3

· 阅读需 5 分钟
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack

React Native 月度会议继续进行!本月的会议稍微短了一些,因为我们的大多数团队都忙于发布产品。下个月,我们将在波兰弗罗茨瓦夫举办的 React Native EU 大会上见面。务必抢购门票,现场见!与此同时,让我们看看团队们正在做些什么。

团队

在第三次会议上,有 5 个团队加入了我们:

会议纪要

以下是各团队的纪要:

Callstack

  • 最近开源了 react-native-material-palette。它能从图片中提取主要颜色,帮助你创建视觉上更吸引人的应用。目前仅支持 Android,但我们正在考虑未来添加 iOS 支持。
  • 我们已经把 HMR 支持合并到了 haul 以及其它一堆很酷的新功能!请查看最新版本发布。
  • React Native EU 2017 即将来临!下个月全是关于 React Native 和波兰的内容!务必在这里抢购最后的门票。

Expo

  • 推出了对 Snack 中安装 npm 包的支持。Expo 的常规限制依然适用——包不能依赖 Expo 中尚未包含的自定义原生 API。我们还在努力支持在 Snack 中使用多文件和上传资源。Satyajit 将在 React Native Europe 上介绍 Snack。
  • 发布了带有相机、支付、安全存储、磁力计、暂停/恢复文件系统下载和改进的启动/加载屏幕的 SDK20。
  • 继续与 Krzysztof 合作开发 react-native-gesture-handler。欢迎大家试用一下,用它重建之前用 PanResponder 或原生手势识别器实现的手势,并告诉我们遇到了哪些问题。
  • 试验 JSC 调试协议,正在根据 Canny 上的许多功能需求进行工作。

Facebook

  • 上个月我们讨论了 GitHub 问题追踪器的管理,希望尝试改进,以提升项目的可维护性。
  • 目前,打开的问题数保持在约 600 个,并且看起来可能会维持一段时间。过去一个月,我们关闭了 690 个由于缺少活动(定义为过去 60 天内无评论)的问题。在这690个问题中,有 58 个因多种原因被重新打开(例如维护者承诺修复,或贡献者提出保留问题的有力理由)。
  • 我们计划在可预见的未来继续自动关闭陈旧问题。我们的目标是每个有影响力的问题都能得到响应,但我们还没达到。我们需要所有维护者协助完成问题筛查,确保不遗漏引入回归或破坏性变更的问题,尤其是那些影响新建项目的问题。有意帮忙的朋友可以使用 Facebook GitHub Bot 来筛查问题和拉取请求。新的维护者指南包含了筛查和使用 GitHub Bot 的更多信息。请加入我们的问题特别工作组,并鼓励其他活跃社区成员一同加入!

Microsoft

  • 新版 Skype 应用基于 React Native 构建,旨在实现跨平台尽可能多的代码共享。基于 React Native 的 Skype 应用目前已在 Android 和 iOS 应用商店上线。
  • 在使用 React Native 构建 Skype 应用时,我们向 React Native 发送了许多拉取请求,修复遇到的 bug 和缺失功能。目前,我们已有约 70个拉取请求被合并
  • React Native 使我们能够基于同一代码库驱动 Android 和 iOS 版 Skype 应用。我们还希望用该代码库支持 Skype Web 应用。为此,我们打造并开源了名为 ReactXP 的轻量层,位于 React/React Native 之上。ReactXP 提供一套跨平台组件,目标为 iOS/Android 时映射到 React Native,目标为 Web 时映射到 react-dom。ReactXP 的目标类似于另一个开源库 React Native for Web。关于这两个库方法的区别,详见 ReactXP FAQ

Shoutem

  • 我们持续努力改进和简化使用 Shoutem 构建应用的开发者体验。
  • 开始将所有应用迁移到 react-navigation,但最终推迟,等待更稳定版本的发布,或等待某个原生导航方案成熟。
  • 将我们所有的 扩展 以及大部分开源库(animationthemeui)升级到 React Native 0.47.1。

下次会议

下次会议定于 2017 年 9 月 13 日星期三。作为我们仅举行的第三次会议,我们想知道这些纪要对 React Native 社区有何帮助。如果你有关于如何改进会议输出的建议,欢迎随时通过我的 Twitter 联系我。

React Native 在 Marketplace 的性能表现

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

React Native 在 Facebook 旗下的多个应用中均有使用,包括主 Facebook 应用中的顶级标签页。本文主要聚焦一个高度可见的产品,Marketplace。该产品已在十几个国家上线,帮助用户发现其他用户提供的商品和服务。

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

典型的 React Native 启动过程大致如下图所示:

免责声明:图中比例不具代表性,具体情况将根据 React Native 的配置和使用方式有所不同。

我们首先初始化 React Native 核心(亦即“Bridge”),接着运行产品特定的 JavaScript,这部分决定了 React Native 将在本地用哪些视图进行渲染,这一步在“Native Processing Time”中完成。

不同的做法

我们早期犯的一个错误是让Systrace 和 CTScan主导了我们的性能工作。这些工具帮助我们发现了许多明显的问题(低垂的果实),但我们后来发现 Systrace 和 CTScan 并不能代表真实生产环境的场景,也无法模拟实际使用时的情况。时间分解中的比例常常不准确,有时偏差极大。在极端情况下,一些我们以为只需几毫秒的操作,实际耗时却达数百甚至数千毫秒。话虽如此,CTScan 仍然有用,我们发现它能在生产前捕获三分之一的性能回归。

在 Android 上,我们将这些工具的局限归因于:1)React Native 是一个多线程框架;2)Marketplace 与复杂视图(如新闻推送和其他顶级标签页)共存;3)计算时间波动极大。因此,本半年度,我们几乎完全依靠生产环境的测量数据和时间分解,作为决策和优先级排序的依据。

生产环境检测之路

在生产环境中做性能监测乍看简单,但实际上过程颇为复杂。该过程经历了多个 2-3 周的迭代周期;原因在于从提交代码到主分支、发布应用到 Play 商店,到收集足够生产环境样本进而确认工作有效性存在较大延迟。每个迭代周期,我们都要验证时间分解的准确性、粒度合适性以及各项数值是否正确累计到整体时间。我们无法依赖 alpha 和 beta 版本,因为它们的数据无法代表整体用户群。其实质是,我们非常细致地基于数百万样本的汇总结果构建了一份非常精确的生产追踪。

我们细致核实每个毫秒在时间分解中的正确计入,原因之一是早期发现检测工具存在遗漏。初期时间分解未考虑到因线程切换导致的阻塞。线程切换本身花费不大,但切换到已经繁忙的线程会非常耗时。我们最终通过在关键时刻人为插入 Thread.sleep() 调用,在本地重现了这些阻塞情形,并通过以下方式予以解决:

  1. 移除对 AsyncTask 的依赖,
  2. 取消在 UI 线程上强制初始化 ReactContext 和 NativeModules,
  3. 取消启动时对 ReactRootView 的强制测量依赖。

这三步合力减少了启动时间超过 25%。

生产数据指标还挑战了我们之前的一些假设。例如,我们过去习惯在启动路径中预加载许多 JavaScript 模块,认为将模块合并到一个包中可降低初始化成本。但事实上,预加载和模块集中加载的成本远大于带来的好处。通过重新配置 inline require 黑名单,并剔除启动路径中的多余模块(如仅需 Relay Modern时不引入 Relay Classic),我们实现了 RUN_JS_BUNDLE 阶段比之前快超过 75%。

产品特定的本地模块优化也带来了显著提升。例如,通过对本地模块依赖采用懒注入策略,我们将其耗时削减了 98%。通过避免 Marketplace 启动时与其他模块竞争资源,我们同样缩短了启动时间。

最棒的是,许多这种改进对所有用 React Native 构建的页面均有广泛适用性。

总结

许多人认为 React Native 的启动性能瓶颈是 JavaScript 执行缓慢或网络延迟过高。虽然优化 JavaScript 执行确实能非同小可地减少 TTI,但这些因素各自占据的时间比之前普遍认为的要少得多。

到目前为止,最重要的经验是——测量,测量,再测量! 一些优化得益于将运行时开销转移到构建时,比如 Relay Modern 和 Lazy NativeModules;另一些则来自更聪明的代码并行化或死代码剔除;还有一部分依赖 React Native 体系内的大规模架构改进,如解决线程阻塞问题。性能优化没有“一劳永逸”的方案,更长远的成效基于持续的检测和改进。不要被认知偏见左右决策,而应仔细收集并解读生产数据,引导后续工作。

未来规划

长期来看,我们期望 Marketplace 的 TTI 能与类似原生产品相当,整体上实现 React Native 性能与原生性能并驾齐驱。此外,尽管本半年度我们已将 Bridge 启动成本大幅降低约 80%,后续计划通过类似 Prepack 等项目以及更多构建时处理,将 React Native Bridge 的开销逼近零。

React Native 月刊 #2

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

React Native 月度会议继续进行!本次会议我们迎来了 Infinite Red,他们是 Chain React,React Native 大会 的幕后高手。由于这里大多数人都在 Chain React 做了演讲,我们将会议推迟了一周。大会中的演讲已发布到线上,我鼓励大家去看看。那么,让我们来看看我们的团队最近都在忙些什么。

团队

在第二次会议中,有 9 个团队参加了我们:

会议记录

以下是各团队的会议记录:

Airbnb

Callstack

  • Mike Grabowski 一如既往地管理着 React Native 的月度发布,包括发布了一些 Beta 版本。尤其是在推进 v0.43.5 版本发布到 npm,因为它为 Windows 用户解除了阻碍!
  • Haul 的开发进展缓慢但持续。已有一个 pull request 添加了热模块替换(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 的团队。如果你觉得有价值并希望参与改进,欢迎加入!
  • 在 Expo SDK 19 的 Snack 中新增了安装 npm 包的初步支持。如果遇到任何问题请告诉我们,我们还在修复一些 bug。配合 Native Directory,这应能方便测试纯 JS 依赖或包含在 Expo SDK 里的库。试试这些示例:
  • 发布了 Expo SDK19,带来了一系列提升,现在使用了更新版 Android JSC
  • 正在与 Alexander Kotliarskyi 一起在文档中制作指南,列出提升应用用户体验的技巧。欢迎参与贡献内容或帮忙撰写!
  • 持续推进音频/视频、相机、手势(与 Software Mansion 合作开发的 react-native-gesture-handler)、GL 相机集成及希望在 SDK20(8 月)中首次推出部分功能,同时其他也会有重大改进。开始构建基础设施以支持 Expo 客户端的后台工作(地理位置、音频、通知等)。
  • Adam Miskiewicz 在模仿 UINavigationController 转场效果的 react-navigation 中取得了不错进展。可以先看看他早期的版本,见他的推文——相关版本即将发布。还可关注他提上游MaskedViewIOS。如果你有能力且愿意实现 Android 版的 MaskedView,那就太棒了!

Facebook

  • Facebook 内部正在探索是否可以将原生的 ComponentKitLitho 组件内嵌到 React Native 中。
  • 欢迎为 React Native 贡献!如果你想知道如何贡献,我们的"贡献指南"详细描述了开发流程及提交首个 pull request 的步骤。除编写代码之外,还能通过 triaging issues(问题处理)或更新文档等方式贡献。
    • 截稿时,React Native 有 635未关闭的问题249待合入的 PR。这些数量对维护者是巨大压力,且内部修复的问题很难保证及时更新相关任务。
    • 我们尚未确定处理方式以在满足社区的同时减轻维护者负担。一些(非全部)方案包括关闭陈旧的问题、赋予更多人员管理权限、自动关闭不符合模板的问题。我们编写了“维护者期待”指南,设定预期并避免意外。如果你有好建议帮助维护者提高体验,同时让提出问题和 PR 的人感受到被尊重和重视,请告诉我们!

GeekyAnts

  • 在 Chain React 上演示了 Designer 工具,它可处理 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 测试——这是 React Native 架构的独特优势。
  • VS Code 已知在调试 React Native 时出现问题,新版本扩展将在几天内修复。
  • 鉴于微软内部还有许多团队也在致力于 React Native,我们将争取下一次会议时有更广泛团队的代表。

Shoutem

  • 完成使 React Native 开发在 Shoutem 上更便捷的工作。开发应用时你可以使用所有标准的 react-native 命令。
  • 在解决如何最佳进行 React Native 性能分析上做了大量工作。许多文档已经过时,我们将尽力提交 PR 更新官方文档,或至少在博客中分享结论。
  • 将导航方案切换到 react-navigation,可能很快会给出反馈。
  • 发布了新的 HTML 组件,它能将原始 HTML 转换成 React Native 组件树。

Wix

  • 开始向 Metro Bundler 提交包含 react-native-repackager 功能的 PR。已升级 react-native-repackager 以支持 RN 44(我们生产环境使用的版本)。用于我们的 detox 模拟测试框架。
  • 过去三周一直在为 Wix 应用编写 detox 测试。这是一次极好的学习体验,了解如何减少如此规模(超 40 名工程师)的应用中的手动 QA。我们修复了若干 detox 问题,新版本刚发布。很高兴地报告,我们目前严格遵循“零闪烁政策”,测试稳定通过。
  • Detox for Android 正在良好推进。社区大力协助,预计两周内发布初版。
  • 我们的性能测试工具 DetoxInstruments 正在逐渐膨胀。计划将其变为独立工具,不再紧耦合 detox。它将支持一般 iOS 应用的性能分析,并与 detox 集成,实现自动化性能指标测试。

下次会议

下一次会议定于 2017 年 8 月 16 日。作为仅第 2 次会议,我们希望知道这些记录对 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

  • 计划向 View 组件和 AccessibilityInfo 原生模块添加一些 A11y(无障碍)API。
  • 将调查在 Android 原生模块中添加 API,以便指定它们运行的线程。
  • 正在研究潜在的初始化性能改进。
  • 正在研究一些更复杂的打包策略,以配合“拆包”(unbundle)使用。

Callstack

  • 正在尝试通过使用 Detox 进行端到端测试来改进发布流程,相关拉取请求即将提交。
  • 一直在开发的 Blob 功能的拉取请求已合并,后续的拉取请求也在进行中。
  • 在内部项目中推广使用 Haul ,以观察其相较于 Metro Bundler 的性能表现。正在与 webpack 团队合作提升多线程性能。
  • 内部已实现更完善的开源项目管理基础设施,未来几周将发布更多相关内容。
  • React Native 欧洲会议正在筹备中,目前暂无特别内容,但大家均被邀请参加!
  • 暂时从 react-navigation 退后,探索其他替代方案(尤其是原生导航)。

Expo

Facebook

  • React Native 的打包工具现已变更为独立仓库的 Metro Bundler。伦敦的 Metro Bundler 团队非常期待响应社区需求,提升模块化以支持 React Native 以外的更多用例,并加快对问题和 PR 的响应速度。
  • 接下来几个月,React Native 团队将致力于优化基础组件的 API。期待在布局细节、无障碍以及 Flow 类型方面的改进。
  • 今年团队还计划通过重构改善核心模块化,以全面支持第三方平台如 Windows 和 macOS。

GeekyAnts

  • 团队正在开发一款 UI/UX 设计应用(代号:Builder),能够直接操作 .js 文件。目前仅支持 React Native。这类似于 Adobe XD 和 Sketch。
  • 团队正在努力实现:你可以在编辑器中加载已有的 React Native 应用,进行视觉化修改(设计师操作),然后将更改直接保存到 JS 文件中。
  • 致力于缩小设计师与开发者之间的差距,让他们共用同一个代码仓库。
  • 此外,NativeBase 最近已获得了 5,000 个 GitHub 星标。

Microsoft

  • CodePush 现已整合进 Mobile Center,这是实现分发、分析和其他服务更紧密集成体验的第一步。相关公告见 这里
  • VS Code 存在调试相关的一个 Bug,目前团队正在修复,并会发布新版本。
  • 正在调研使用 Detox 进行集成测试,研究 JSC 上下文以获取变量和崩溃报告。

Shoutem

  • 正在利用 React Native 社区的工具简化 Shoutem 应用的开发。你将可以使用所有 React Native 命令来运行在 Shoutem 上创建的应用。
  • 正在调研 React Native 的性能分析工具,期间遇到不少问题,将会记录下这些经验分享给社区。
  • Shoutem 正在努力简化 React Native 与现有原生应用的集成,计划公开分享公司内部开发的方案,以便获得社区反馈。

Wix

  • 内部推广使用 Detox ,目标是实现 Wix 应用的“零手动测试”流程。目前 Detox 已被几十位开发者在生产环境广泛使用,并在快速成熟。
  • 正在为 Metro Bundler 添加支持,允许在构建过程中覆盖任意文件扩展名。除了“ios”和“android”,将支持诸如“e2e”或“detox”等自定义扩展,计划用于端到端模拟测试。目前已有一个叫 react-native-repackager 的库,正在推进相关拉取请求。
  • 研究性能测试的自动化,开发了一个新仓库 DetoxInstruments,欢迎查看,开源开发中。
  • 与 KPN 的贡献者合作,优化 Detox 在 Android 上的表现,并支持真实设备。
  • 构思“Detox 作为平台”的模式,支持搭建其他需要自动化模拟器或设备的工具。例如 React Native 的 Storybook 或 Ram 提出的集成测试方案。

下一次会议

会议每四周举行一次。下一次会议定于 2017 年 7 月 12 日召开。鉴于我们刚刚开始举办此会议,希望了解这些纪要对 React Native 社区有哪些帮助。如果你有针对未来会议内容的建议,或者对会议产出如何改进有想法,欢迎随时在Twitter上联系我。

React Native 中更好的列表视图

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

很多人已经开始尝试我们的新列表组件了,这要归功于我们之前在社区群组中的预告公告,今天我们正式发布它们!不再需要 ListViewDataSource,告别死板的行、被忽略的 Bug 和过度的内存消耗 —— 使用最新的 React Native 2017 年 3 月发布候选版(0.43-rc.1),你可以从新推出的一系列组件中选择最符合你使用场景的,开箱即用,性能和功能兼备:

<FlatList>

这是用于简单且高性能列表的主力组件。只需提供一个数据数组和一个 renderItem 函数即可:

<FlatList
data={[{title: '标题文本', key: 'item1'}, ...]}
renderItem={({item}) => <ListItem title={item.title} />}
/>

<SectionList>

如果你想渲染一组按逻辑分段的数据,带有分区头部(例如按字母排序的通讯录),或者数据和渲染异构(例如一个配置文件视图,包含一些按钮、接着是一个编写器、然后是图片网格、好友网格,最后是故事列表),这就是你要用的组件。

<SectionList
renderItem={({item}) => <ListItem title={item.title} />}
renderSectionHeader={({section}) => <H1 title={section.key} />}
sections={[ // 各分区渲染均匀
{data: [...], key: ...},
{data: [...], key: ...},
{data: [...], key: ...},
]}
/>

<SectionList
sections={[ // 各分区渲染异构
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
{data: [...], key: ..., renderItem: ...},
]}
/>

<VirtualizedList>

这是后台实现的组件,提供了更灵活的 API。尤其适合你的数据不是普通数组的情况(例如不可变列表)。

功能

列表在许多场景中都会用到,因此我们给新组件添加了丰富的功能以覆盖大多数使用案例:

  • 滚动加载(onEndReached)。
  • 下拉刷新(onRefresh / refreshing)。
  • 可配置的 可视回调(VPV)(onViewableItemsChanged / viewabilityConfig)。
  • 横向模式(horizontal)。
  • 智能的行与分区分割线。
  • 多列支持(numColumns)。
  • scrollToEndscrollToIndex,和 scrollToItem
  • 更完善的 Flow 类型支持。

一些注意事项

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

  • 这些组件基于 PureComponent,意味着如果 props 浅比较相等,不会重新渲染。确保你的 renderItem 函数直接依赖的所有内容,都作为非 === 相等的 prop 传递,否则 UI 可能不会实时更新。这包括 data 属性和父组件的状态。例如:

    <FlatList
    data={this.state.data}
    renderItem={({item}) => (
    <MyItem
    item={item}
    onPress={() =>
    this.setState(oldState => ({
    selected: {
    // 新实例打破了 `===`
    ...oldState.selected, // 复制旧数据
    [item.key]: !oldState.selected[item.key], // 切换选中状态
    },
    }))
    }
    selected={
    !!this.state.selected[item.key] // renderItem 依赖于state
    }
    />
    )}
    selected={
    // 可以是任何不冲突的 prop
    this.state.selected // selected 变化应触发 FlatList 重新渲染
    }
    />
  • 为限制内存并支持流畅滚动,内容是异步离屏渲染的。这意味着可能会滚动速度快于填充速度,短暂看到空白内容。这是一个折中,可以根据应用需求调整,我们也在后台持续改进。

  • 默认情况下,这些新列表会查找每个项目上的 key 属性作为 React key。你也可以提供自定义的 keyExtractor 属性。

性能

除了简化 API,新列表组件还带来了显著的性能提升,最主要的是对任意行数几乎保持恒定的内存使用。实现方式是对渲染窗口外的元素进行“虚拟化”,即将它们完全从组件树卸载,回收 React 组件的 JS 内存,以及 Shadow 树和 UI 视图的原生内存。但这意味着组件内部状态不会被保留,因此请确保你把重要状态存储在组件外部,比如 Relay、Redux 或 Flux 存储。

限制渲染窗口还减少了 React 和原生层需要处理的工作量,例如视图遍历。即使是在渲染数百万元素的末尾,也无需遍历所有元素。你甚至可以用 scrollToIndex 直接跳转到列表中间,无需额外渲染。

我们还对调度做了改进,提升应用响应速度。渲染窗口边缘的项目会在任何活动的手势、动画或其它交互完成后,以较低优先级和较少频率渲染。

高级用法

ListView 不同,渲染窗口中的所有项目只要任何 prop 变化都会重新渲染。通常这没问题,因为窗口大小限制了项目数量,但如果你的项目非常复杂,应遵循 React 性能优化最佳实践,使用 React.PureComponent 和/或 shouldComponentUpdate 来限制递归子树的重渲染。

如果你能在不渲染的情况下计算行高,可以通过提供 getItemLayout 属性来提升用户体验。这使得用 scrollToIndex 等方法滚动到指定项目更平滑,也能提升滚动条 UI,因为内容高度不用渲染就能确定。

如果你使用其他类型数据,比如不可变列表,<VirtualizedList> 是更好的选择。它接受一个 getItem 属性,可返回任意索引的项目数据,Flow 类型也更宽松。

对于特殊需求,还可以调整多个参数,例如用 windowSize 在内存使用和用户体验之间权衡,maxToRenderPerBatch 调整填充率和响应速度,onEndReachedThreshold 控制滚动加载触发门槛等。

未来工作

  • 迁移现有界面(最终废弃 ListView)。
  • 根据反馈添加更多功能(欢迎告诉我们!)。
  • 支持固定分区头。
  • 更多性能优化。
  • 支持带状态的函数式项目组件。

idx:存在性函数

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

在 Facebook,我们经常需要访问通过 GraphQL 获取的数据结构中深层嵌套的值。在访问这些深层嵌套值的过程中,一个或多个中间字段通常是可空的。这些中间字段可能为 null 的原因有很多,比如隐私检查失败,或者仅仅因为 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 函数会利用 try-catch 捕获访问 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 包,并在 .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 打包器并打印二维码。用 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 支持“弹出”(eject)。

你可以运行 npm run eject 来获得一个非常类似于 react-native init 生成的项目。此时你需要使用 Xcode 和/或 Android Studio,使用 react-native link 添加库,且能完全控制原生代码的编译过程。

有疑问?反馈?

Create React Native App 现在已足够稳定可供通用,欢迎大家分享使用体验!你可以在 Twitter 上找到我,或者在 GitHub 仓库 提交问题。欢迎贡献代码!

使用原生驱动实现动画

· 阅读需 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 的支持,并修复了我们在生产应用中遇到的各种 bug。这真的是一次社区协作,我要感谢所有参与者,也感谢 Expo 对开发的大力支持。如今它被 React Native 的 Touchable 组件和新发布的React Navigation库中的导航动画广泛使用。

它是如何工作的?

首先,让我们看看使用 JS 驱动的 Animated 动画是如何工作的。当使用 Animated 时,你声明一个动画节点图,表示你想执行的动画,然后用驱动器根据预设曲线实时更新 Animated 值。你也可以通过 Animated.event 将 Animated 值连接到 View 的事件。

动画各步骤及其所在线程说明:

  • JS:动画驱动器使用 requestAnimationFrame 在每帧执行,基于动画曲线计算新值并更新目标值。
  • JS:计算中间值并传递到附加到 View 的 props 节点。
  • JS:通过 setNativeProps 更新 View
  • JS 到 Native 桥接。
  • Native:更新 UIViewandroid.View

如你所见,大部分工作都在 JS 线程上。如果 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 节点,告诉驱动附加的是视图的哪个属性:

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 计算值。

剩下的就是通过指定动画曲线类型和要更新的 Animated 值来启动动画。时间动画也可以通过在 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, // <-- 添加这一行
}).start();

一个 Animated 值只兼容一种驱动,如果你用原生驱动启动了某个值的动画,确保该值的所有动画都用原生驱动。

它同样支持 Animated.event,这在动画需跟随滚动位置时非常有用。没有原生驱动时,由于 React Native 的异步特性,动画总是滞后于手势一帧。

之前:

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

之后:

<Animated.ScrollView // <-- 使用 Animated 的 ScrollView 包装器
scrollEventThrottle={1} // <-- 设置 1 保证不丢失任何事件
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.animatedValue } } }],
{ useNativeDriver: true } // <-- 添加这一行
)}
>
{content}
</Animated.ScrollView>

注意事项

并不是 Animated 所支持的所有功能,原生驱动都支持。主要限制是你只能动画非布局属性,比如 transformopacity 可用,但 Flexbox 以及 position 属性不可用。另一个限制是 Animated.event 只支持直接事件,不支持冒泡事件,这意味着不能与 PanResponder 一起用,但支持 ScrollView#onScroll 这类事件。

Native Animated 已经存在 React Native 很久了,但一直没有文档,因为它之前被视为实验性质。所以请确保使用的是较新的 React Native 版本(0.40 及以上)才能使用该功能。

参考资源

想了解更多关于 Animated 的内容,我推荐观看 Christopher Chedeau这场演讲

如果你想深入了解动画以及把动画卸载给原生如何提升用户体验,可以看 Krzysztof Magiera这场讲座

每月发布节奏:发布 12 月和 1 月的候选版本

· 阅读需 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 促成其实现)