跳到主要内容

使用原生驱动实现动画

· 阅读需 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 [email protected]
$ react-native upgrade

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

· 阅读需 8 分钟
王梦珏 (Mandy)
Facebook 软件工程实习生

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

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

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

为了在生产环境中测试 RTL 支持,最新版的 Facebook Ads Manager 应用(首个 100% 跨平台 RN 应用)现已支持阿拉伯语和希伯来语的 RTL 布局,分别适用于 iOSAndroid。下面是这些 RTL 语言下的界面展示:

RN 支持 RTL 的改动概述

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

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

下面的图表描述了高层次的改动:

改动包括:

有了这个更新,当你允许你的应用使用 RTL 布局时:

  • 每个组件的布局会水平翻转
  • 如果你使用支持 RTL 的 OSS 组件,部分手势和动画会自动适配 RTL 布局
  • 你只需做最小的额外工作,就能使应用完全支持 RTL

让应用支持 RTL

  1. 要支持 RTL,首先应将 RTL 语言包加入你的应用。

  2. 通过在原生代码开头调用 allowRTL() 函数,允许你的应用使用 RTL 布局。这个工具仅会启用 RTL 布局,当你的应用已准备好。示例如下:

    iOS:

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

    Android:

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

现在,重新编译应用并将设备语言切换为 RTL 语言(如阿拉伯语或希伯来语),应用布局应自动变成 RTL。

编写支持 RTL 的组件

一般而言,大部分组件已经支持 RTL,例如:

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

不过,有些情况需要留意,你可能需要用到 I18nManagerI18nManager 中有一个常量 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 中,手势和动画不在核心代码层支持,而是在组件层。好消息是,部分组件今天已支持 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={'快速测试 RTL 布局'}>
<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(
'重新加载页面',
'请重新加载此页面以更改界面方向!' +
'本应用内所有示例都会受到影响,' +
'可以查看它们在 RTL 布局下的表现。',
);
};

开发新功能时,你可以轻松切换这个按钮并重启应用来查看 RTL 布局。好处是,无需更改语言设置来测试,但部分文本对齐不会随之变化(参见下一节说明)。因此,发布前始终推荐在 RTL 语言环境下完整测试应用。

限制与未来计划

RTL 支持应该涵盖大部分应用用户体验,但目前仍有一些限制:

  • Android 与 iOS 中文本对齐行为不同
    • iOS 中,默认文本对齐依赖于所用语言包,始终保持在同一侧。Android 中,默认文本对齐依赖文本内容语言,即英文左对齐,阿拉伯文右对齐。
    • 理论上,应该统一平台间的行为,但不同用户可能对不同行为有偏好。未来或需更多用户体验调研以确定最佳实践。
  • 没有“真实”的左/右

    如前所述,我们将 JS 端的 left/right 样式映射为 start/end,代码中写的 left 在 RTL 时显示为屏幕右侧,right 则显示为屏幕左侧。这方便产品代码改动最小,但目前没办法指定“真实的左边”或“真实的右边”。未来可能需要允许组件无视语言设置,自行控制方向。

  • 让手势与动画的 RTL 支持更易用

    目前,让手势和动画支持 RTL 仍需一定编码工作。未来有望找到更简便的方式帮助开发者支持 RTL 的手势和动画。

尝试一下吧!

查看 RNTester 中的 RTLExample,深入理解 RTL 支持,并告诉我们你的使用体验!

最后,感谢阅读!希望 React Native 的 RTL 支持能助你让应用走向国际,更好地成长!

旧金山聚会回顾

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

上周我有机会参加了在 Zynga 旧金山办公室举办的 React Native Meetup。大约有200人出席,这里是个结识我附近同样对 React Native 感兴趣的开发者的绝佳场所。

我尤其感兴趣的是了解像 Zynga、Netflix 和 Airbnb 这样的公司是如何使用 React 和 React Native 的。当晚的议程如下:

  • React 中的快速原型开发
  • 为 React Native 设计 API
  • 弥合鸿沟:在现有代码库中使用 React Native

但首先,活动以快速介绍和近期新闻回顾开始:

如果你附近有这样的聚会,我强烈推荐你参加!

Zynga 的 React 快速原型开发

第一轮新闻之后,Zynga——我们当晚的主办方——做了简短介绍。Abhishek Chadha 讲述了他们如何使用 React 快速原型开发移动新体验,演示了类似 Draw Something 的快速原型应用。他们采用了与 React Native 类似的方式,通过桥接访问原生 API。当 Abhishek 用设备摄像头拍摄了观众照片后,还在一个人的头上画了顶帽子,演示效果十分生动。

Netflix 的 React Native API 设计

接下来,是当晚的第一个主题演讲。Netflix 的高级软件工程师 Clarence Leung 带来关于为 React Native 设计 API 的分享。首先他指出可能开发的两类库:一类是组件,如标签栏和日期选择器,另一类是提供访问原生服务的库,如相册或应用内支付。构建 React Native 库时可以有两种设计思路:

  • 提供平台特定的组件
  • 提供跨平台、为 Android 和 iOS 设计相似 API 的库

每种方法都有其考虑点,需按需选择最合适的方案。

方案一

以平台特定组件为例,Clarence 提到 React Native 核心的 DatePickerIOS 和 DatePickerAndroid。iOS 上日期选择器作为 UI 一部分渲染,可轻松嵌入现有视图;而 Android 上是以模态呈现,因此分别提供组件是合理的。

方案二

相反,相册选择器在 Android 和 iOS 上的表现较为相似。虽然有些小差异——比如 Android 不像 iOS 那样将照片分组(如自拍)——这些均可用 if 语句和 Platform 组件轻松处理。

无论选择哪种方式,建议尽量缩减 API 设计范围,构建针对应用的专用库。例如,iOS 的应用内购买支持一次性消耗和可续订订阅。如果应用仅需支持消耗型购买,可以剔除跨平台库中的订阅支持。

Clarence 演讲结束后有简短问答环节。一个有趣的点是,Netflix 这些库中写的 React Native 代码约有80%能在 Android 和 iOS 上共享。

弥合鸿沟:在现有代码库中使用 React Native

当晚最后一场演讲由 Airbnb 的 Leland Richardson 带来,主题聚焦在如何在已有代码库中使用 React Native。我很熟悉从零开始用 React Native 编写新应用的便利,非常期待听听 Airbnb 在现有原生应用中采用 React Native 的经验。

Leland 先介绍了“绿地”(Greenfield)与“棕地”(Brownfield)应用。绿地指不考虑任何既有工作的新项目,而棕地则要考虑已有项目的需求、开发流程和团队各种要求。

对于绿地应用,React Native CLI 会为 Android 和 iOS 建立单一仓库,一切运作顺畅。但 Airbnb 面临的首要挑战是 Android 和 iOS 各自拥有独立仓库。多仓库结构的公司在采用 React Native 之前需克服一定障碍。

为此,Airbnb 首先为 React Native 代码库建立了新仓库。他们用 CI 服务器将 Android 和 iOS 仓库镜像到这个新仓库。测试运行并打包后,构建产物同步回原仓库。这样移动端工程师可以在不改变开发环境的情况下开发原生代码,无需安装 npm、运行打包器或记得构建 JavaScript 包。负责编写 React Native 代码的工程师直接在 React Native 仓库工作,无需担心代码同步问题。

不过,这也带来一些弊端,主要是无法发布原子更新。需要同时修改原生和 JavaScript 代码的改动需提交三份独立 PR,且必须小心合并。为避免冲突,若主分支自构建开始后有变动,CI 会阻止变动同步回 Android 和 iOS 仓库,导致高频提交时延迟增加(如新版本发布期间)。

Airbnb 后来转向了单体仓库(mono repo)方式。幸运的是,这已有所考虑,且 Android 和 iOS 团队适应 React Native 后乐于加快合并进程。

这解决了采用多仓库方案时的大多数问题。Leland 提到,这会增加版本控制服务器负担,可能对小型公司造成困扰。

导航问题

演讲下半场集中讨论一个我非常关心的话题:React Native 中的导航问题。Leland 介绍了丰富的导航库资源,包括官方和第三方。NavigationExperimental 曾被认为是有潜力的,但实际用起来不适合他们的场景。

事实上,现有导航库很难满足棕地应用需求。棕地应用要求导航状态完全由原生应用掌控。例如用户会话过期时,在呈现 React Native 视图期间,原生应用应能接管并根据需要展示登录界面。

Airbnb 也希望避免在过渡期间用 JavaScript 版本替换原生导航栏,因为会带来突兀感。最初他们限制为模态视图,这显然限制了 React Native 在其应用中的广泛采用。

因此他们决定开发自有导航库:airbnb-navigation。目前尚未开源,因为紧耦合 Airbnb 代码库,但计划年底前发布。

我不会详细介绍其 API,以下是主要要点:

  • 必须预先注册场景
  • 每个场景都在独立的 RCTRootView 中显示,在各平台以原生方式呈现(如 iOS 通过 UINavigationController
  • 场景中的主 ScrollView 应包装在 ScrollScene 组件内,这样可以利用原生行为,如 iOS 点击状态栏自动滚动到顶部
  • 场景切换由原生处理,无需担心性能
  • Android 返回键自动支持
  • 可以通过 Navigator.Config UI-less 组件利用基于视图控制器的导航栏样式

同时也需注意:

  • 导航栏为原生组件,不易在 JavaScript 端自定义,这是设计所需,因该库硬性规定必须使用原生导航栏
  • 传递给场景的 ScreenProps 需进行序列化/反序列化,传输大量数据时需谨慎
  • 导航状态由原生应用持有(库的硬性需求),因此 Redux 等不能直接操作导航状态

Leland 演讲后也进行了问答环节。总的来说,Airbnb 对 React Native 很满意。他们对使用 Code Push 来修复问题、绕过 App Store 流程感兴趣,且工程师非常喜欢 Live Reload,因为不必每次小改都等待原生应用重建。

结语

活动最后分享了更多 React Native 新闻:

聚会是与社区其他开发者交流学习的好机会。我期待未来参加更多 React Native 聚会。如果你也参加了,请认出我,并告诉我怎样才能让 React Native 更好地为你服务!

致更好的文档

· 阅读需 5 分钟
Kevin Lacker
Facebook 工程经理

打造优秀的开发者体验,其中一部分是拥有优秀的文档。创建好的文档需要很多工作——理想的文档应当是简明、有用、准确、完整且令人愉悦的。最近我们根据大家的反馈努力提升文档质量,想和大家分享一些我们做出的改进。

行内示例

当你学习一个新的库、新的编程语言或新框架时,会有一个美妙的时刻,你第一次写出一段代码,尝试运行,看它是否能工作……而且它 确实 能工作。你创造了一些真实的东西。我们希望把这种直接的体验融入到文档里。比如这样:

import React, { Component } from 'react';
import { AppRegistry, Text, View } from 'react-native';

class ScratchPad extends Component {
render() {
return (
<View style={{flex: 1}}>
<Text style={{fontSize: 30, flex: 1, textAlign: 'center'}}>
这是不是很酷?
</Text>
<Text style={{fontSize: 100, flex: 1, textAlign: 'center'}}>
👍
</Text>
</View>
);
}
}

AppRegistry.registerComponent('ScratchPad', () => ScratchPad);

我们认为这些行内示例,借助 react-native-web-player 模块,在 Devin Abbott 的帮助下实现,是学习 React Native 基础的绝佳方式,我们也已更新了面向新手的 React Native 入门教程,尽可能使用这些示例。去看看吧——如果你曾经好奇只修改一小段示例代码会发生什么,这是一种很棒的试验方式。另外,如果你开发的是面向开发者的工具,想在自己的网站上展示一个实时的 React Native 示例,react-native-web-player 能让这件事轻松实现。

核心的模拟引擎由 Nicolas Gallagherreact-native-web 项目提供,它让 React Native 组件如 TextView 能在网页上显示。如果你对创建同时支持移动端与网页的体验感兴趣,代码共用大量代码库,可以查看 react-native-web

更好的指南

在 React Native 的某些部分,存在多种实现方式,我们收到反馈说可以给出更明确的指导。

我们新出了一个 导航指南,对比了不同方案,并给出使用建议——NavigatorNavigatorIOSNavigationExperimental。中期来看,我们正在努力改进和整合这些接口。短期内,我们希望更好的指南能够让你更容易应对。

我们还新增了一个关于 处理触摸 的指南,解释了制作按钮类界面的基本知识,并简述了多种处理触摸事件的方式。

我们着力改进的另一个领域是 Flexbox。包括如何使用 Flexbox 处理布局 和如何控制 组件尺寸 的教程,还包括一个虽然不够炫酷但希望对你有用的 React Native 布局控制属性列表

快速入门

当你开始在机器上搭建 React Native 开发环境时,确实需要安装和配置许多东西。要把安装过程变得非常有趣和令人激动很难,但我们至少可以让它尽可能快速且无痛。

我们构建了一个 全新的快速入门流程,让你先选择开发操作系统和移动操作系统,集中提供所有的设置说明。我们还亲自体验了安装流程,确保一切顺利,且每个决策点都有明确推荐。经过对无辜的同事的测试,我们相信这是一次改进。

我们还完善了 将 React Native 集成到已有应用 的指南。很多大型应用,比如 Facebook 自身的应用,实际上部分使用 React Native 构建,部分使用传统开发工具。我们希望这份指南能帮助更多人采用这种应用开发方式。

我们需要你的帮助

你的反馈让我们知道应当优先处理什么。我知道有些人看完这篇博客可能会想“更好的文档?哼,X 库的文档还是一团糟!”这非常好——我们需要这种热情。给我们反馈的最佳方式取决于反馈的类型。

如果你发现文档中有错误,比如描述不准确或代码无法运行,提交 Issue 是最直接的方式。标注“Documentation”标签,方便我们分配给对应人员。

如果不是具体的错误,而是文档内容本质上令人困惑,这就不太适合 GitHub Issue。相反,可以在 Canny 上发表你觉得哪部分文档需要改进。这有助于我们在写指南等更广泛工作中进行优先排序。

感谢你读到这里,也感谢你使用 React Native!

React Native:一年的回顾

· 阅读需 2 分钟
Martin Konicek
Facebook 软件工程师

自从我们开源 React Native 已经一年了。一开始,这只是几个工程师的一个想法,如今已经成为一个被 Facebook 及其他产品团队广泛使用的框架。今天在 F8 大会上,我们宣布微软将 React Native 引入 Windows 生态系统,使开发者能够在 Windows PC、手机和 Xbox 上构建 React Native 应用。该项目还将提供开源工具和服务,比如 Visual Studio Code 的 React Native 扩展和 CodePush,帮助开发者在 Windows 平台上创建 React Native 应用。此外,三星 正在为其混合平台构建 React Native,这将赋能开发者为数以百万计的智能电视、移动设备和可穿戴设备开发应用。我们还发布了 Facebook SDK for React Native,让开发者更容易地将 Facebook 的社交功能(如登录、分享、应用分析和 Graph API)集成到他们的应用中。一年之内,React Native 改变了开发者在各大主流平台上的开发方式。

这一年是史诗般的旅程——但我们才刚刚开始。下面回顾一下自从我们开源以来,React Native 如何成长和演进,我们在此过程中遇到的一些挑战,以及展望未来的期望。

以上为摘录。请在 Facebook Code 阅读完整文章。