跳到主要内容

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 阅读完整文章。

深入探讨 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

Android 版 React Native:我们如何构建第一个跨平台的 React Native 应用

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

今年早些时候,我们推出了iOS 版 React Native。React Native 将开发者在网页端使用 React 时所熟悉的声明式自包含 UI 组件和快速开发周期带到了移动平台,同时保持了原生应用的速度、逼真度和使用感受。今天,我们很高兴地发布了 Android 版 React Native。

在 Facebook,我们已经在生产环境中使用 React Native 超过一年。几乎正好一年前,我们团队开始开发广告管理器应用。我们的目标是创建一款新应用,让数百万在 Facebook 上投放广告的用户能够随时管理账户并创建新广告。最终,这不仅是 Facebook 第一个完全基于 React Native 的应用,也是第一个跨平台应用。在这篇文章中,我们想与大家分享我们如何构建这款应用,React Native 如何帮助我们提速,以及我们所学到的经验教训。

以上为节选内容。请在Facebook Code阅读全文。

React Native:将现代网页技术引入移动端

· 阅读需 2 分钟
Tom Occhino
Facebook 工程经理

我们在两年前向世界介绍了React,此后它在 Facebook 内外都取得了令人瞩目的增长。如今,虽然没人被强制使用它,但 Facebook 上的新网页项目通常会以某种形式使用 React,并且它正在业界被广泛采用。工程师们每天选择使用 React,是因为它让他们能更多地专注于产品,而不是花费大量时间与框架“斗争”。不过,直到我们使用 React 开发了一段时间之后,才真正理解它为何如此强大。

React 强制我们将应用拆分为离散的组件,每个组件表示一个单独的视图。这些组件让我们更容易迭代产品,因为修改某一部分时无需在脑中保持整个系统的全貌。但更重要的是,React 用声明式的方式包装了 DOM 的变异式、命令式 API,这提升了抽象层次,简化了编程模型。我们的发现是,使用 React 开发时,代码更加可预测。这种可预测性让我们能更有信心快速迭代,也让应用变得更加可靠。此外,使用 React 构建应用不仅更易于扩展,我们还发现团队规模的扩展也更为便捷。

结合网页的快速迭代周期,我们用 React 构建了一些很棒的产品,包括 Facebook.com 的许多组件。此外,我们基于 React 构建了强大的 JavaScript 框架,比如 Relay,它能够大幅简化大规模数据获取。当然,网页只是故事的一部分。Facebook 还有广泛使用的 Android 和 iOS 应用,它们基于彼此独立且封闭的技术栈构建。在多个平台之上进行应用开发,使我们的工程组织分裂开来,但这只是移动原生应用开发难点之一。

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