跳到主要内容

26 篇博文 含有标签「engineering」

查看所有标签

介绍 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这场讲座

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 支持能助你让应用走向国际,更好地成长!

深入探讨 React Native 性能

· 阅读需 2 分钟
Pieter De Baets
Facebook 软件工程师

React Native 允许你使用 JavaScript 结合 React 和 Relay 的声明式编程模型构建 Android 和 iOS 应用。这带来了代码更简洁、更易理解;无需编译周期即可快速迭代;以及跨多个平台轻松共享代码。你可以更快发布,专注于真正重要的细节,让你的应用看起来和使用起来都非常出色。优化性能是其中的重要环节。这里讲述了我们如何让 React Native 应用启动速度提升一倍的故事。

为什么要加快速度?

应用运行更快,内容加载更快,意味着用户有更多时间与应用互动,流畅的动画让应用使用体验更加愉悦。在新兴市场,比如绝大多数用户使用的是2011年款手机并连结于2G网络,性能优化往往决定了应用是能用还是几乎无法使用。

自从 React Native 在iOSAndroid 平台发布以来,我们一直在提升列表视图滚动性能、内存效率、界面响应速度和应用启动时间。启动时间决定了应用的第一印象,并且考验框架的各个部分,因此是最有价值且最具挑战性的问题。

以上为节选内容。请在Facebook Code阅读完整文章。

介绍热重载

· 阅读需 8 分钟
Martín Bigio
Instagram 软件工程师

React Native 的目标是为你提供最佳的开发者体验。重要的一部分是从你保存文件到能够看到更改之间所花费的时间。我们的目标是把这个反馈循环控制在 1 秒以内,即使你的应用日益庞大。

我们通过三个主要特性接近了这个理想:

  • 使用 JavaScript 作为语言,它没有漫长的编译周期。
  • 实现了一个名为 Packager 的工具,将 es6/flow/jsx 文件转换成虚拟机可以理解的普通 JavaScript。它设计成一个服务器,在内存中保持中间状态以实现快速的增量更改,并利用多核 CPU。
  • 构建了一个叫做 Live Reload 的功能,在保存时重新加载应用。

此时,开发者的瓶颈不再是应用加载时间,而是丢失应用状态。一个常见场景是你正在开发一个多屏幕外的功能。每次重载,你都必须反复点击同一路径返回功能界面,使得循环花费数秒。

热重载

热重载的理念是在保持应用运行的同时,在运行时注入你编辑的文件的新版本。这样你就不会丢失任何状态,特别适合调整 UI 时使用。

一张图胜千言。看看 Live Reload(当前)和 Hot Reload(新)的区别。

仔细看,你会发现它可以从红色错误界面恢复,也可以开始导入之前不存在的模块,无需完整重载。

警告: 由于 JavaScript 是一个高度状态化的语言,热重载无法完美实现。实际上,我们发现当前的方案已能较好地应对大多数常用场景,且如若发生错误总可以进行完整重载。

热重载自 0.22 版本起可用,你可以这样开启:

  • 打开开发者菜单
  • 点击“Enable Hot Reloading”

实现简述

了解了为什么要用以及如何使用后,趣味部分来了:它实际上是如何工作的。

热重载基于一种名为 Hot Module Replacement(简称 HMR)的功能。这最初由 webpack 引入,我们在 React Native Packager 中实现了它。HMR 使 Packager 监听文件变更,并向应用中包含的轻量 HMR 运行时发送更新。

简言之,HMR 更新包含了修改后 JS 模块的新代码。运行时收到后,会用新代码替换旧代码:

HMR 更新不仅仅包含要更改模块的代码,因为光替换代码还不足以让运行时认知变更。问题在于模块系统可能已缓存了想要更新模块的 exports。举例说明,假设应用由以下两个模块组成:

// 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 函数注册每个模块到模块系统。对于该应用,log 模块会有如下定义:

__d('log', function() {
... // 模块代码
});

该调用将每个模块代码包裹在匿名函数中,通常称之为工厂函数。模块系统运行时跟踪每个模块的工厂函数,是否已执行过,以及执行结果(exports)。当某模块被引用,模块系统会返回缓存的 exports,或者首次执行工厂函数并缓存结果。

假设启动应用并引用 log 模块,此时 logtime 的工厂函数都未执行,exports 还未缓存。之后用户修改 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 模块中的 require('time') 是顶级调用:

const time = require('./time'); // 顶级 require

// log.js
function log(message) {
console.log(`[${time()}] ${message}`);
}

module.exports = log;

log 被引用时,运行时会缓存它和 time 的 exports(步骤 1)。之后 time 变更时,HMR 进程不能只替换 time 的代码直接结束,否则 log 执行时仍会使用缓存的旧 time 代码。

为了让 log 拥抱 time 的改变,必须清除 log 的缓存 exports,因为它依赖的模块被热替换了(步骤 3)。最后,下一次 log 被引用时,工厂函数会被重新执行,进而使用新 time 代码。

HMR API

React Native 中的 HMR 扩展了模块系统,新增了 hot 对象。该 API 基于 webpack 的实现。hot 对象暴露了 accept 函数,允许你定义模块被热替换时调用的回调。例如,如果我们修改 time,每次保存时,控制台都会输出 “time changed”:

// time.js
function time() {
... // 新代码
}

module.hot.accept(() => {
console.log('time changed');
});

module.exports = time;

注意手动使用该 API 的情况很少见。热重载默认对大多数场景开箱即用。

HMR 运行时

如前所述,仅仅接受 HMR 更新有时不够,因为使用该模块的上层模块可能已执行且导入被缓存。举例来说,假设电影示例应用的依赖树顶层有个 MovieRouter,其依赖 MovieSearchMovieScreen 视图,这些视图又依赖前面示例中的 logtime 模块:

用户访问了电影搜索视图,但未访问另一个视图,除 MovieScreen 外,其他模块都缓存了 exports。如果修改了 time,运行时会清除 log 的缓存 exports 以反映 time 的变更。这个过程还会递归上溯所有父模块并尝试接受它们的更新。对于未被引用的 MovieScreen,会跳过;对 MovieSearch,清除缓存后递归处理其父级;最终对 MovieRouter 采用相同处理,之后结束,因为没有父模块依赖它。

运行时为进行依赖树递归,会从 Packager 那里在 HMR 更新中获得逆向依赖树。对于此例,运行时会收到了如下类型的 JSON 对象:

{
modules: [
{
name: 'time',
code: /* time 的新代码 */
}
],

inverseDependencies: {
MovieRouter: [],
MovieScreen: ['MovieRouter'],
MovieSearch: ['MovieRouter'],
log: ['MovieScreen', 'MovieSearch'],
time: ['log'],
}
}

React 组件

React 组件更难支持热重载,因为不能简单替换旧代码否则会丢失组件状态。对于 React Web 应用,Dan Abramov 实现了 babel transform,结合 webpack 的 HMR API 解决了这个问题。简而言之,他的方案是在 transform 阶段 为每个 React 组件创建一个代理。这些代理持有组件状态,并将生命周期方法委托给实际组件,后者是被热重载的目标:

除了创建代理组件,该 transform 还定义了 accept 函数,代码强制 React 重新渲染组件。这样,我们就能在不丢失任何应用状态的前提下热重载渲染代码。

React Native 默认的 transformer 使用 babel-preset-react-native,其中 配置react-transform,用法与 React Web + webpack 项目相同。

Redux Store

要为 Redux store 启用热重载,只需像在 webpack Web 项目中一样使用 HMR API:

// 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 给了我们机会重新思考构建应用的方式,以打造极佳的开发体验。热重载只是拼图中的一片,还有哪些疯狂的黑科技,我们能让开发体验更好呢?

让 React Native 应用支持无障碍功能

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

随着 React 在网页端和 React Native 在移动端的推出,我们为开发者提供了一个全新的前端框架以构建产品。构建一个强健产品的关键之一是确保任何人都能使用它,包括视力障碍或其他残疾人士。React 和 React Native 的无障碍 API 使您能够让任何基于 React 的体验为可能使用辅助技术的人所用,比如盲人和视障者使用的屏幕阅读器。

在本文中,我们将重点介绍 React Native 应用。我们设计的 React 无障碍 API 与 Android 和 iOS 的 API 看起来和感觉上都相似。如果你之前为 Android、iOS 或网页开发过无障碍应用,你应该会对 React AX API 的框架和命名感到熟悉。例如,你可以让一个 UI 元素 accessible(因此它能被辅助技术访问),并使用 accessibilityLabel 来为该元素提供一个字符串描述:

<View accessible={true} accessibilityLabel=”这是一个简单视图”>

让我们通过看看 Facebook 自家的一个基于 React 的产品 —— 广告管理器应用 来演示 React AX API 更加复杂的应用场景。

这是节选内容。阅读全文请访问 Facebook Code