跳到主要内容

30 篇带有 “工程” 标签的帖子

查看所有标签

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,并且其他改进也已发布。最近,一些行业领导者开始采用它。可能计划开始在该领域进行全职付费工作。
  • 来自 Jest 团队的 Michał Pierzchała 本月加入了 Callstack。他将帮助维护 Haul,并可能参与 Metro BundlerJest 的工作。
  • Satyajit Sahoo 现在加入了我们,太棒了!
  • 我们的 OSS 部门即将推出一系列很酷的东西。特别是,正在努力将 Material Palette API 引入 React Native。计划最终发布我们的原生 iOS 工具包,旨在提供与原生组件 1:1 的外观和感觉。

Expo

  • 最近推出了 Native Directory,以帮助发现和评估 React Native 生态系统中的库。问题:库太多,难以测试,需要手动应用启发式方法,并且哪些是您应该使用的最佳库并不立即显而易见。也很难知道某些东西是否与 CRNA/Expo 兼容。因此,Native Directory 尝试解决这些问题。查看并添加您的库到其中。库列表位于此处。这只是我们的第一次尝试,我们希望它由社区拥有和运营,而不仅仅是 Expo 团队。因此,如果您认为这有价值并想使其变得更好,请参与进来!
  • Snack 中添加了对安装 npm 包的初始支持,使用了 Expo SDK 19。如果您在使用过程中遇到任何问题,请告知我们,我们仍在努力解决一些错误。与 Native Directory 一起,这应该可以轻松测试仅具有 JS 依赖项或包含在 Expo SDK 中的依赖项的库。尝试一下
  • 发布了 Expo SDK19,在各个方面都有许多改进,我们现在正在使用更新后的 Android JSC
  • 正在与 Alexander Kotliarskyi 合作编写文档中的指南,其中包含有关如何改善您的应用程序用户体验的技巧列表。请加入并添加到列表中或帮助编写其中一些内容!
  • 继续致力于:音频/视频、相机、手势(与 Software Mansion、react-native-gesture-handler 合作)、GL 相机集成,并希望在 SDK20(8 月)中首次实现其中一些功能,并在那时对其他功能进行重大改进。我们才刚刚开始在 Expo 客户端中构建用于后台工作的基础设施(地理位置、音频、处理通知等)。
  • Adam Miskiewicz 在模仿 react-navigationUINavigationController 的过渡方面取得了一些不错的进展。在 他的推文 中查看其早期版本 - 即将发布。还可以查看他 upstreamedMaskedViewIOS。如果您有技能和意愿为 Android 实现 MaskedView,那将非常棒!

Facebook

  • Facebook 内部正在探索能够将原生 ComponentKitLitho 组件嵌入到 React Native 中。
  • 非常欢迎对 React Native 做出贡献!如果您想知道如何贡献,“如何贡献”指南描述了我们的开发过程,并列出了发送您的第一个 pull request 的步骤。还有其他无需编写代码的贡献方式,例如通过 triage issues 或更新文档。
    • 在撰写本文时,React Native 有 635未解决的 issues249未解决的 pull requests。这对我们的维护者来说是压倒性的,并且当内部修复问题时,很难确保相关任务得到更新。
    • 我们不确定处理这种情况的最佳方法是什么,同时让社区满意。一些(但不是全部!)选项包括关闭过时的 issues、给予更多人管理 issues 的权限以及自动关闭不遵循 issue 模板的 issues。我们编写了“维护者期望”指南以设定期望并避免意外。如果您对如何使维护者的体验更好以及确保打开 issues 和 pull requests 的人感到被倾听和重视有任何想法,请告诉我们!

GeekyAnts

  • 我们在 Chain React 上演示了 Designer Tool,它可以与 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 中存在一个已知的调试问题 - 几天后发布的扩展程序的下一个版本将修复该问题。
  • 由于 Microsoft 内部还有许多其他团队也在从事 React Native 的工作,因此我们将努力在下次会议上获得所有小组的更好代表。

Shoutem

  • 完成了在 Shoutem 上简化 React Native 开发的过程。在 Shoutem 上开发应用程序时,您可以使用所有标准的 react-native 命令。
  • 我们做了很多工作,试图找出在 React Native 上进行性能分析的最佳方法。文档的很大一部分已经过时,我们将尽最大努力在官方文档上创建一个 pull request,或者至少在一篇博客文章中写下我们的一些结论。
  • 将我们的导航解决方案切换到 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 上 ping 我。

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,以允许指定它们要运行的线程。
  • 一直在研究潜在的初始化性能改进。
  • 一直在研究一些更复杂的捆绑策略,以在“unbundle”之上使用。

Callstack

  • 正在研究通过使用 Detox 进行 E2E 测试来改进发布过程。Pull request 应该很快就会落地。
  • 他们一直在处理的 Blob pull request 已合并,后续的 pull requests 即将推出。
  • 在内部项目中增加 Haul 的采用率,以了解其与 Metro Bundler 相比的性能。正在与 webpack 团队合作开发更好的多线程性能。
  • 在内部,他们已经实施了更好的基础设施来管理开源项目。计划在未来几周内推出更多内容。
  • React Native Europe 会议即将到来,目前还没有什么有趣的事情,但我们邀请大家参加!
  • 暂时从 react-navigation 中抽身出来,以研究替代方案(尤其是原生导航)。

Expo

Facebook

  • React Native 的打包器现在是 Metro Bundler,位于一个独立的 repo 中。伦敦的 Metro Bundler 团队很高兴能够满足社区的需求,提高模块化以满足 React Native 之外的更多用例,并提高对 issues 和 PR 的响应速度。
  • 在未来几个月内,React Native 团队将致力于改进原始组件的 API。预计布局怪癖、辅助功能和 Flow 类型检查方面将有所改进。
  • React Native 团队还计划通过重构以完全支持 Windows 和 macOS 等第三方平台,来提高今年的核心模块化。

GeekyAnts

  • 该团队正在开发一个 UI/UX 设计应用程序(代号:Builder),该应用程序直接与 .js 文件一起使用。目前,它仅支持 React Native。它类似于 Adobe XD 和 Sketch。
  • 该团队正在努力让您可以在编辑器中加载现有的 React Native 应用程序,进行更改(可视化地,作为设计师),并将更改直接保存到 JS 文件中。
  • 人们正在努力弥合设计师和开发人员之间的差距,并将他们聚集在同一个 repo 中。
  • 此外,NativeBase 最近达到了 5,000 个 GitHub stars。

Microsoft

  • CodePush 现在已集成到 Mobile Center 中。这是提供与分发、分析和其他服务更集成体验的第一步。请参阅他们的公告 此处
  • VS Code 在调试方面存在一个 bug,他们正在努力修复该 bug,并将发布一个新的构建版本。
  • 正在研究 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”。计划将其用于 E2E 模拟。已经有一个名为 react-native-repackager 的库,现在正在处理 PR。
  • 正在研究性能测试的自动化。这是一个名为 DetoxInstruments 的新 repo。您可以查看一下,它正在开源开发中。
  • 正在与来自 KPN 的贡献者合作开发适用于 Android 和支持真实设备的 Detox。
  • 正在考虑将“Detox 作为一个平台”,以允许构建其他需要自动化模拟器/设备的工具。一个示例是 React Native 的 Storybook 或 Ram 关于集成测试的想法。

下次会议

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

更好的 React Native 列表视图

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

在我们在社区群组中发布预告公告后,你们中的许多人已经开始试用我们新的 List 组件,但我们今天正式宣布它们!告别 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)
  • scrollToEnd, scrollToIndexscrollToItem
  • 更好的 Flow 类型检查。

一些注意事项

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

  • 这些组件基于 PureComponent,这意味着如果 props 保持浅相等,它们将不会重新渲染。确保您的 renderItem 函数直接依赖的所有内容都作为 prop 传递,并且在更新后不是 ===,否则您的 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)。
  • 更多功能,只要我们看到/听到需求(请告诉我们!)。
  • 粘性 section header 支持。
  • 更多性能优化。
  • 支持具有状态的功能性项目组件。

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 函数将尝试捕获由于访问 null 或 undefined 的属性而导致的错误。如果捕获到此类错误,它将返回 null 或 undefined。(您可以在 idx.js 中看到这可能是如何实现的。)

实际上,尝试捕获每个嵌套属性访问速度很慢,并且区分特定类型的 TypeErrors 很脆弱。为了解决这些缺点,我们创建了一个 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

·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,请替换为合适的 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 支持 “ejecting”。

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

有问题?反馈?

Create React Native App 现在足够稳定,可以普遍使用,这意味着我们非常渴望听到您使用它的体验!您可以在 Twitter 上找到我,或在 GitHub 存储库上打开一个 issue。非常欢迎 pull requests!

为 Animated 使用原生驱动

·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 的支持,以及在生产应用程序中使用它时发现的 squash 错误。这真的是社区共同努力的结果,我要感谢所有参与者以及赞助了大部分开发的 Expo。它现在被 React Native 中的 Touchable 组件以及新发布的 React Navigation 库中的导航动画所使用。

它是如何工作的?

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

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

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

如您所见,大部分工作发生在 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 之类的事件。

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

资源

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

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

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

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

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

我们很高兴地宣布,React Native 已得到改进,以支持 RTL 布局。这现在在今天的 react-native master 分支中可用,并且将在下一个 RC 中可用:v0.33.0-rc

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

为了在生产环境中测试 RTL 支持,最新版本的 Facebook Ads Manager 应用程序(第一个跨平台 100% RN 应用程序)现在提供阿拉伯语和希伯来语版本,并为 iOSAndroid 提供 RTL 布局。以下是它在这些 RTL 语言中的外观

RN 中 RTL 支持的概述更改

css-layout 已经具有布局的 startend 的概念。在从左到右 (LTR) 的布局中,start 表示 left,而 end 表示 right。但在 RTL 中,start 表示 right,而 end 表示 left。这意味着我们可以使 RN 依赖于 startend 的计算来计算正确的布局,其中包括 positionpaddingmargin

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

下图描述了高级别的更改

这些包括

通过此更新,当您允许应用程序的 RTL 布局时

  • 每个组件布局都将水平翻转
  • 如果您使用支持 RTL 的 OSS 组件,则某些手势和动画将自动具有 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 文件中将 android:supportsRtl="true" 添加到 <application> 元素。

现在,当您重新编译您的应用程序并将设备语言更改为 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 支持应涵盖您应用程序中的大多数 UX;但是,目前有一些局限性

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

    如前所述,我们将 JS 端的 left/right 样式映射到 start/end,RTL 布局代码中的所有 left 在屏幕上都变为 “right”,代码中的 right 在屏幕上变为 “left”。这很方便,因为您无需过多更改产品代码,但这意味着无法在代码中指定 “true left” 或 “true 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 作为语言,因为 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 的工厂函数都尚未执行,因此没有缓存任何导出。然后,用户修改 time 以返回 MM/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 作为顶层 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,它依赖于 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 transform,它使用 webpack 的 HMR API 来解决此问题。简而言之,他的解决方案通过在转换时为每个 React 组件创建一个代理来工作。代理保存组件的状态,并将生命周期方法委托给实际的组件,这些组件是我们热重载的组件

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

React Native 附带的默认 transformer 使用 babel-preset-react-native,它被配置为以与在使用了 webpack 的 React Web 项目中相同的方式使用 react-transform

Redux Store

要在 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,我们有机会重新思考我们构建应用程序的方式,以使其成为出色的开发者体验。热重载只是拼图中的一块,我们还可以进行哪些疯狂的 hack 来使其变得更好?

使 React Native 应用可访问

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

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

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

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

让我们通过查看 Facebook 自己的 React 驱动产品之一:Ads Manager 应用程序,来逐步了解 React AX API 的一个稍微复杂一点的应用。

这是一个摘录。请在 Facebook Code 上阅读帖子的其余部分。