跳到主要内容

idx: 存在函数

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

在 Facebook,我们经常需要访问通过 GraphQL 获取的数据结构中深层嵌套的值。在访问这些深层嵌套值时,一个或多个中间字段可为空的情况很常见。这些中间字段可能为空的原因有很多,从隐私检查失败到 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 中查看其实现方式。)

在实践中,每次嵌套属性访问都进行 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 包,并将“idx”添加到 .babelrc 文件中的插件列表中即可使用它们。

介绍 Create React Native App

·3 分钟阅读
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 也支持“eject”(弹出)。

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

问题?反馈?

Create React Native App 现在已经足够稳定,可以普遍使用了,这意味着我们非常渴望听到您使用它的体验!您可以在 Twitter 上找到我,或者在 GitHub 仓库上提交问题。非常欢迎拉取请求!

为 Animated 使用原生驱动器

·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 的支持,以及修复我们在生产应用中使用时发现的错误。这确实是一项社区努力,我要感谢所有参与者以及 Expo 为大部分开发提供赞助。它现在被 React Native 中的 Touchable 组件以及新发布的 React Navigation 库中的导航动画所使用。

它是如何工作的?

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

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

  • 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',
});

创建原生属性节点,这告诉原生驱动器它所连接的视图上的哪个属性

NativeAnimatedModule.createNode({
id: 3,
type: 'props',
properties: ['style.opacity'],
});

将节点连接起来

NativeAnimatedModule.connectNodes(1, 2);
NativeAnimatedModule.connectNodes(2, 3);

将属性节点连接到视图

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 的这次演讲

每月发布节奏:发布十二月和一月 RC 版本

·3 分钟阅读
Eric Vicenti
Facebook 工程师

React Native 推出后不久,我们开始每两周发布一次,以帮助社区采用新功能,同时保持版本稳定以供生产使用。在 Facebook,我们每两周必须稳定代码库以发布我们的生产 iOS 应用,因此我们决定以相同的速度发布开源版本。现在,许多 Facebook 应用每周发布一次,尤其是在 Android 上。因为我们每周从主分支发布,所以需要保持相当稳定。因此,双周发布节奏甚至不再有利于内部贡献者。

我们经常从社区听到反馈,称发布速度很难跟上。像 Expo 这样的工具不得不跳过每次发布以管理快速的版本变化。因此,很明显,双周发布并未很好地服务于社区。

现在每月发布

我们很高兴地宣布新的每月发布节奏,以及 2016 年 12 月发布的 v0.40,该版本已在上个月完成稳定化并准备好被采用。(请确保 更新您在 iOS 上的原生模块头文件)。

尽管可能会有几天偏差以避免周末或处理意外问题,但您现在可以预期一个版本在月初提供,并在月末发布。

使用当月版本以获得最佳支持

一月发布候选版已可试用,您可以在 此处查看新内容

为了查看即将到来的变化并向 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,升级变得更容易

·5 分钟阅读
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 能够使用其三方合并算法自动合并大部分更改,最终留下我们熟悉的冲突分隔符:

    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 清除。我们不想将这些 Yeoman 依赖项重新引入 react-native 包中,以便能够评估旧模板以创建补丁。

试用并提供反馈

总之,我会说,享受这个功能吧,并且随时 提出改进建议、报告问题,尤其是 发送拉取请求。每个环境都略有不同,每个 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 版本引入了一个新的核心组件,使得在任何应用中添加可触摸的 Button 变得非常容易。我们还引入了对新包管理器 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 开发者知道如何制作按钮:iOS 上使用 TouchableOpacity 实现默认外观,Android 上使用 TouchableNativeFeedback 实现波纹效果,然后应用一些样式。自定义按钮的构建或安装并不特别困难,但我们的目标是让 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 上提供。凭借二十多项错误修复和新功能,React Native 在您的帮助下不断变得更好。

0.36: 无头 JS、键盘 API 及更多

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

今天我们发布了 React Native 0.36。继续阅读以了解更多新功能。

无头 JS

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

首先,在专用文件(例如 SomeTaskName.js)中定义您的异步任务:

module.exports = async taskData => {
// Perform your task here.
};

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

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

使用无头 JS 需要编写一些原生 Java 代码,以便您在需要时启动服务。查看我们的新 无头 JS 文档 以了解更多信息!

键盘 API

现在,使用 Keyboard 处理屏幕键盘变得更容易了。您现在可以监听原生键盘事件并对其做出反应。例如,要关闭活动键盘,只需调用 Keyboard.dismiss()

import {Keyboard} from 'react-native';

// Hide that keyboard!
Keyboard.dismiss();

动画除法

React Native 已支持通过加法、乘法和模运算组合两个动画值。在 0.36 版本中,现在可以通过 除法组合两个动画值。在某些情况下,动画值需要反转另一个动画值进行计算。一个例子是反转比例 (2x --> 0.5x)

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 上的发布说明 以查看新功能、错误修复和重大更改的完整列表。

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

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

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

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

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

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

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

为了在生产环境中测试 RTL 支持,最新版本的 Facebook Ads Manager 应用(第一个 100% RN 跨平台应用)现已支持阿拉伯语和希伯来语的 RTL 布局,适用于 iOSAndroid。以下是它在这些 RTL 语言中的显示效果:

RN 中 RTL 支持的概览变更

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

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

下图描述了高层面的变化:

这些包括:

通过此更新,当您为应用启用 RTL 布局时:

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

现在,当您重新编译应用并将设备语言更改为 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 支持应涵盖您应用中的大部分用户体验;但是,目前仍有一些限制:

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

    如前所述,我们将 JS 侧的 left/right 样式映射到 start/end,RTL 布局中代码里的所有 left 在屏幕上都变成“右”,而代码中的 right 在屏幕上变成“左”。这很方便,因为您无需过多更改产品代码,但这意味着无法在代码中指定“真正的左”或“真正的右”。将来,可能需要允许组件独立于语言控制其方向。

  • 使手势和动画的 RTL 支持更对开发者友好

    目前,要使手势和动画兼容 RTL 仍然需要一些编程工作。未来,理想的做法是找到一种方法,使手势和动画的 RTL 支持对开发者更友好。

试用一下!

查看 RNTester 中的 RTLExample 以了解更多关于 RTL 支持的信息,并告诉我们它的效果如何!

最后,感谢您的阅读!我们希望 React Native 的 RTL 支持能帮助您的应用拓展国际受众!

旧金山 Meetup 回顾

·9 分钟阅读
Héctor Ramos
Héctor Ramos
Facebook 前开发者布道师

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

我特别想了解 React 和 React Native 在 Zynga、Netflix 和 Airbnb 等公司中的使用情况。当晚的议程如下:

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

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

  • 您知道 React Native 现在是 GitHub 上最热门的 Java 仓库吗?
  • rnpm 现在是 React Native 核心的一部分!您现在可以使用 react-native link 代替 rnpm link安装具有原生依赖的库
  • React Native Meetup 社区正在快速发展!目前全球各地有超过 4,800 名开发者加入了各种 React Native 聚会小组。

如果 其中一个聚会在您附近举行,我强烈建议您参加!

Zynga 使用 React 进行快速原型开发

第一轮新闻之后是当晚的主办方 Zynga 的简短介绍。Abhishek Chadha 谈到了他们如何使用 React 快速在移动设备上原型化新体验,并演示了一个类似“你画我猜”应用的快速原型。他们使用与 React Native 类似的方法,通过桥接器提供对原生 API 的访问。当 Abhishek 使用设备的相机拍摄观众照片,然后在他人的头上画了一顶帽子时,这一点得到了展示。

Netflix 为 React Native 设计 API

接下来是当晚的第一个专题演讲。Clarence Leung,Netflix 的高级软件工程师,发表了关于为 React Native 设计 API 的演讲。他首先指出了两种主要的库类型:组件(如标签栏和日期选择器)以及提供原生服务(如相机胶卷或应用内支付)访问的库。在构建用于 React Native 的库时,有两种方法可以考虑:

  • 提供平台特定组件
  • 一个跨平台库,为 Android 和 iOS 提供相似的 API

每种方法都有其自身的考量,由您决定哪种最适合您的需求。

方法 #1

Clarence 以核心 React Native 中的 DatePickerIOS 和 DatePickerAndroid 为例,谈到了平台特定组件。在 iOS 上,日期选择器作为 UI 的一部分渲染,可以轻松嵌入到现有视图中;而在 Android 上,日期选择器以模态形式呈现。在这种情况下,提供单独的组件是有意义的。

方法 #2

另一方面,图片选择器在 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 应用。Greenfield 意味着开始一个项目时无需考虑任何现有工作。这与 Brownfield 项目形成对比,后者需要考虑现有项目的需求、开发流程以及所有团队的各种需求。

当您开发 Greenfield 应用时,React Native CLI 会为 Android 和 iOS 设置一个单一的代码仓库,一切都正常运行。Airbnb 在使用 React Native 时面临的第一个挑战是,Android 和 iOS 应用各自拥有独立的仓库。多仓库公司在采用 React Native 之前会遇到一些障碍。

为了解决这个问题,Airbnb 首先为 React Native 代码库设置了一个新仓库。他们使用持续集成服务器将 Android 和 iOS 仓库镜像到这个新仓库中。测试运行并构建捆绑包后,构建产物会同步回 Android 和 iOS 仓库。这使得移动工程师可以在不改变其开发环境的情况下处理原生代码。移动工程师无需安装 npm、运行打包器,或记住构建 JavaScript 捆绑包。编写实际 React Native 代码的工程师不必担心在 Android 和 iOS 之间同步代码,因为他们直接在 React Native 仓库上工作。

这确实带来了一些缺点,主要是他们无法发布原子更新。需要结合原生和 JavaScript 代码的更改将需要三个独立的拉取请求,所有这些都必须仔细合并。为了避免冲突,如果主分支在构建开始后发生更改,CI 将无法将更改返回到 Android 和 iOS 仓库。这会在提交频率高的日子(例如发布新版本时)造成长时间延迟。

此后,Airbnb 转向了单一仓库的方法。幸运的是,这已在考虑之中,一旦 Android 和 iOS 团队对使用 React Native 感到满意,他们便乐于加速向单一仓库的转变。

这解决了他们在使用分离仓库方法时遇到的大部分问题。Leland 确实指出,这确实会给版本控制服务器带来更大的压力,这对于小型公司来说可能是一个问题。

导航问题

Leland 演讲的后半部分聚焦于一个我非常关心的话题:React Native 中的导航问题。他谈到了 React Native 中众多的导航库,包括第一方和第三方库。NavigationExperimental 被提及为看起来很有前途的东西,但最终并不适合他们的用例。

事实上,现有的导航库似乎都不太适用于 Brownfield 应用。Brownfield 应用要求导航状态完全由原生应用拥有。例如,如果用户会话在 React Native 视图显示期间过期,原生应用应该能够接管并根据需要显示登录屏幕。

Airbnb 还希望避免在过渡过程中用 JavaScript 版本替换原生导航栏,因为效果可能会令人不适。最初,他们只限制使用模态呈现的视图,但这显然在更广泛地在他们的应用中采用 React Native 时带来了问题。

他们决定需要自己的库。这个库叫做 airbnb-navigation。该库尚未开源,因为它与 Airbnb 的代码库紧密绑定,但他们希望在年底前发布它。

我不会详细介绍该库的 API,但以下是一些主要收获:

  • 必须提前预注册场景
  • 每个场景都在其自己的 RCTRootView 中显示。它们在每个平台上以原生方式呈现(例如,iOS 上使用 UINavigationController)。
  • 场景中的主要 ScrollView 应该被包裹在一个 ScrollScene 组件中。这样做可以利用原生行为,例如在 iOS 上点击状态栏滚动到顶部。
  • 场景之间的过渡由原生处理,无需担心性能问题。
  • Android 返回按钮自动支持。
  • 他们可以通过 Navigator.Config 无 UI 组件利用基于视图控制器的导航栏样式。

还需要考虑一些事项:

  • 导航栏在 JavaScript 中不容易自定义,因为它是一个原生组件。这是有意为之,因为使用原生导航栏是此类库的硬性要求。
  • ScreenProps 在通过桥接器发送时必须进行序列化/反序列化,因此如果在此处发送过多数据,则必须小心。
  • 导航状态由原生应用拥有(这也是该库的硬性要求),因此 Redux 等工具无法操纵导航状态。

Leland 的演讲之后也进行了一次问答环节。总的来说,Airbnb 对 React Native 很满意。他们对使用 Code Push 无需通过 App Store 即可修复任何问题很感兴趣,他们的工程师喜欢 Live Reload,因为他们不必在每次细微更改后等待原生应用重新构建。

结束语

活动以一些额外的 React Native 新闻结束:

开发者聚会提供了一个很好的机会,可以结识社区中的其他开发者并向他们学习。我期待将来参加更多的 React Native 聚会。如果您参加其中一个,请留意我,并告诉我我们如何才能让 React Native 更好地为您服务!