跳到主要内容

29 篇帖子,已标记“engineering”

查看所有标签

React Native Monthly #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 测试来改进发布流程。拉取请求应该很快就会提交。
  • 他们一直在处理的 Blob 拉取请求已合并,后续拉取请求即将到来。
  • 正在内部项目中增加 Haul 的采用率,以了解它与 Metro Bundler 相比的性能。正在与 webpack 团队合作,以提高多线程性能。
  • 在内部,他们已经实施了更好的基础设施来管理开源项目。计划在未来几周内推出更多内容。
  • React Native Europe 大会即将到来,目前还没有什么有趣的消息,但我们邀请大家参加!
  • 暂时退出了 react-navigation 一段时间,以研究替代方案(尤其是原生导航)。

Expo

Facebook

  • React Native 的打包器现在是 Metro Bundler,位于一个独立的仓库中。伦敦的 Metro Bundler 团队很高兴能够满足社区的需求,提高模块化以适应 React Native 之外的其他用例,并提高对问题和 PR 的响应速度。
  • 在接下来的几个月中,React Native 团队将致力于改进原始组件的 API。预计在布局怪癖、无障碍功能和 Flow 类型方面会有所改进。
  • React Native 团队还计划在今年通过重构来完全支持 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 在调试方面存在一个错误,他们正在努力修复该错误,并将发布一个新版本。
  • 正在研究 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 的新仓库。您可以查看一下,它正在开源开发中。
  • 正在与来自 KPN 的贡献者合作,开发用于 Android 的 Detox 并支持真机。
  • 正在考虑将“Detox 作为一个平台”,以允许构建其他需要自动化模拟器/设备的工具。一个例子是用于 React Native 的 Storybook 或 Ram 的集成测试想法。

下一次会议

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

React Native 中更好的列表视图

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

在我们在社区群组中发布 预告公告 后,你们中的许多人已经开始试用我们的一些新的列表组件,但我们今天正式宣布它们!不再有 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。如果您的数据不在纯数组中(例如,不可变列表),则尤其方便。

功能

列表在许多上下文中使用,因此我们在新组件中 packed 了大量功能,以开箱即用地处理大多数用例

  • 滚动加载 (onEndReached)。
  • 下拉刷新 (onRefresh / refreshing)。
  • 可配置的 可见性 (VPV) 回调 (onViewableItemsChanged / viewabilityConfig)。
  • 水平模式 (horizontal)。
  • 智能项目和节分隔符。
  • 多列支持 (numColumns)
  • scrollToEndscrollToIndexscrollToItem
  • 更好的 Flow 类型。

一些注意事项

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

  • 这些组件基于 PureComponent,这意味着如果 props 保持浅相等,它们将不会重新渲染。确保您的 renderItem 函数直接依赖的所有内容都作为 props 传递,这些 props 在更新后不是 ===,否则您的 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 键。或者,您可以提供自定义 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)。
  • 更多功能,因为我们看到/听到需求(请告诉我们!)。
  • 粘性节标题支持。
  • 更多性能优化。
  • 支持带有状态的功能项组件。

idx:存在性函数

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

在 Facebook,我们经常需要访问使用 GraphQL 获取的数据结构中深层嵌套的值。在访问这些深层嵌套的值的过程中,一个或多个中间字段通常是可为空的。这些中间字段可能为空的原因有很多,从隐私检查失败到仅仅是空恰好是表示非致命错误的最灵活方式。

不幸的是,访问这些深层嵌套的值目前既繁琐又冗长。

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 每个嵌套属性访问都很慢,并且区分特定类型的 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 打包器并打印一个 QR 码。在 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 支持“弹出”。

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

问题?反馈?

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

将 Native Driver 用于 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 的支持,以及修复我们在生产应用中使用它时发现的错误。这真的是社区的共同努力,我要感谢所有参与者以及 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 到 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 节点,这告诉原生驱动程序它附加到的视图上的哪个 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,
});

现在这是动画运行时发生情况的分解

  • 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, // <-- 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 和 position 属性将不起作用。另一个是 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 广告管理工具应用(第一个跨平台 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 文件中的 <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 支持应涵盖您应用中的大多数 UX;但是,目前存在一些限制

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

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

  • 使手势和动画的 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 上阅读帖子的其余部分。

介绍 Hot Reloading

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

React Native 的目标是为您提供最佳的开发者体验。其中很大一部分是您保存文件到能够看到更改之间所花费的时间。我们的目标是使此反馈循环保持在 1 秒以下,即使您的应用不断增长也是如此。

我们通过三个主要功能接近了这个理想

  • 使用 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 组件创建一个代理。代理保存组件的状态并将生命周期方法委托给实际组件,这些组件是我们热重载的组件

除了创建代理组件之外,转换还定义了 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 驱动的产品之一:广告管理工具应用,来了解 React AX API 的一个稍微复杂的应用。

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