跳到主要内容

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 促成其实现)

借助 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 仓库。它不会干扰你的项目仓库(无论你用的是 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 则利用其三方合并算法自动合并大部分更改,并最终留下熟悉的冲突界定符:

    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 版本启动。如果把实现嵌入本地 CLI,你无法在旧版本 React Native 中享用新功能。举例来说,如果这套新升级代码只发布于 0.38.0,那你就无法从 0.29.2 升级到 0.38.0。

基于 Git 的升级大大提升开发者体验,并且要让所有人都能用得上非常重要。通过使用独立的全局安装包 react-native-git-upgrade,无论你的项目用哪个版本 React Native,今天都可以使用这套新代码。

另外原因是 Martin Konicek 最近的 Yeoman 淘汰。我们不希望为了评估旧模板进而生成补丁,再把 Yeoman 依赖重新带回 react-native 包。

试试看并提供反馈

总结一句,尽情享用该功能,欢迎随时提出改进建议、报告问题,尤其是提交 Pull Request。每种环境和每个 React Native 项目都各不相同,我们需要你的反馈,让这个工具为更多人顺利工作。

感谢!

特别感谢了不起的公司 ZenikaM6 Web(存档),没有他们这一切都不可能实现!

介绍 Button、更快的 Yarn 安装以及公开路线图

· 阅读需 3 分钟
Héctor Ramos
Héctor Ramos
Former Developer Advocate @ Facebook

我们听到很多人反映,React Native 的工作内容非常多,跟踪进展可能会很困难。为了帮助大家了解正在进行的工作,我们现在发布了 React Native 路线图。大致来说,相关工作可以分为三个优先重点:

  • 核心库。为最有用的组件和 API 添加更多功能。
  • 稳定性。改进底层架构,减少 bug 并提升代码质量。
  • 开发者体验。帮助 React Native 开发者能够更快开发。

如果你有觉得有价值的新功能建议,欢迎访问 Canny,在这里你可以建议新功能并讨论已有提案。

React Native 的新内容

今天发布的 React Native 0.37 版本 引入了一个新的核心组件,让在任何应用中添加一个可点击按钮变得非常简单。我们还引入了对新包管理工具 Yarn 的支持,这将加速更新应用依赖的整个过程。

介绍 Button 组件

今天我们推出了一个基础的 <Button /> 组件,在所有平台上都拥有良好的展示效果。这个新组件解决了我们收到的最常见反馈之一:React Native 是少数没有开箱即用按钮的移动开发工具之一。

简单按钮在 Android 和 iOS 上的效果

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

有经验的 React Native 开发者知道如何制作按钮:在 iOS 上用 TouchableOpacity 实现默认样式,在 Android 上用 TouchableNativeFeedback 实现水波纹效果,然后加上一些样式。自定义按钮的制作和安装并不特别难,但我们的目标是使 React Native 极其易学。将基础按钮加入核心组件后,初学者可以在第一天就开发出很棒的东西,而不必花时间格式化按钮或学习 Touchable 的细节。

Button 组件旨在在各个平台上都表现良好且原生化,但不会支持所有自定义按钮的复杂功能。这是一个很好的起点,但并不意味着要替代你已有的所有按钮。想了解更多,请查看带有可运行示例的 Button 新文档

使用 Yarn 加快 react-native init

你现在可以使用新 JavaScript 包管理器 Yarn 来显著加快 react-native init 的速度。想体验提升,请 安装 yarn 并将 react-native-cli 升级到 1.2.0:

$ npm install -g react-native-cli

设置新应用时,你应该可以看到 “Using yarn” :

使用 yarn

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

感谢!

感谢所有为本次发布做出贡献的人。完整的 发布说明 已在 GitHub 上公开。拥有 24 个以上的 bug 修复和新功能,React Native 在大家的共同努力下持续变得更好。

0.36:无界面 JS、键盘 API 及更多

· 阅读需 3 分钟
Héctor Ramos
Héctor Ramos
Former Developer Advocate @ Facebook

今天我们发布了 React Native 0.36。请继续阅读,了解更多新内容。

无界面 JS(Headless JS)

无界面 JS 是一种在应用处于后台时运行 JavaScript 任务的方式。例如,它可以用于同步最新数据、处理推送通知或播放音乐。目前仅在 Android 上可用。

开始使用时,在一个专用文件中定义你的异步任务(例如 SomeTaskName.js):

module.exports = async taskData => {
// 在这里执行你的任务。
};

接下来,在 AppRegistry 中注册你的任务:

AppRegistry.registerHeadlessTask('SomeTaskName', () =>
require('SomeTaskName'),
);

使用无界面 JS 需要编写一些本地 Java 代码,以便在需要时启动服务。查看我们新的 无界面 JS 文档 了解更多内容!

键盘 API

使用 Keyboard 操作屏幕键盘现在变得更简单。你可以监听原生键盘事件并做出响应。例如,要关闭当前活动的键盘,只需调用 Keyboard.dismiss()

import {Keyboard} from 'react-native';

// 关闭键盘!
Keyboard.dismiss();

动画除法

React Native 已经支持通过加法、乘法和取模来组合两个动画值。到了 0.36 版本,现已支持通过 除法 来组合两个动画值。有些情况下需要一个动画值反转另一个动画值进行计算,比如反转缩放比例(2 倍 --> 0.5 倍):

const a = Animated.Value(1);
const b = Animated.divide(1, a);

Animated.spring(a, {
toValue: 2,
}).start();

b 将随 a 的弹簧动画变化,生成值 1 / a

基本用法如下:

<Animated.View style={{transform: [{scale: a}]}}>
<Animated.Image style={{transform: [{scale: b}]}} />
<Animated.View>

在此示例中,内部图片不会被拉伸,因为父视图的缩放被抵消了。如果你想了解更多,可以查看动画指南

深色状态栏

StatusBar 新增了一个 barStyle 值:dark-content。有了这个新增值,你现在可以在 Android 和 iOS 上都使用 barStyle。行为如下:

  • default:使用平台默认(iOS 上为浅色,Android 上为深色)。
  • light-content:使用浅色状态栏,文字和图标为黑色。
  • dark-content:使用深色状态栏,文字和图标为白色。

...以及更多

以上只是 0.36 版本更新内容的部分示例。请查看 GitHub 上的发行说明 获取完整新特性、bug 修复和破坏性变更列表。

你可以通过在终端运行以下命令升级到 0.36:

$ npm install --save react-native@0.36
$ react-native upgrade