跳到主要内容

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

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

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

我们很高兴地宣布,React Native 已经改进以支持 RTL 布局。这现在可在 react-native 的 master 分支中获取,并将在下一个 RC 版本中提供:v0.33.0-rc

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

为了在生产环境中对 RTL 支持进行实战测试,最新版本的 Facebook Ads Manager 应用(第一个 100% RN 跨平台应用)现在已推出阿拉伯语和希伯来语版本,并支持 iOSAndroid 上的 RTL 布局。以下是它在这些 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”,代码中的 right 在屏幕上变为“left”。这很方便,因为你不需要过多地更改你的产品代码,但这意味着无法在代码中指定“真正的左”或“真正的右”。将来,允许组件独立于语言控制其方向可能是必要的。

  • 使手势和动画的 RTL 支持更具开发人员友好性

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

试一试!

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

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

旧金山见面会回顾

·阅读时长 9 分钟
Héctor Ramos
Héctor Ramos
前 Facebook 开发者倡导者

上周我有幸参加了在Zynga旧金山办公室举办的React Native Meetup。约有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 社区正在快速发展!全球各地的 React Native meetup 小组现在共有超过 4,800 名开发者。

如果这些聚会之一在您附近举行,我强烈建议您参加!

在 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

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

方法一

作为平台特定组件的例子,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 首先谈到了绿地应用和棕地应用。绿地项目意味着无需考虑任何先前的工作而开始一个项目。这与棕地项目形成对比,在棕地项目中,您需要考虑现有项目的需求、开发流程以及所有团队的各种需求。

当您在一个绿地应用上工作时,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 代码组合的更改将需要三个独立的拉取请求,所有这些都必须仔细合并。为了避免冲突,如果在构建开始后 master 分支发生更改,CI 将无法将更改同步回 Android 和 iOS 仓库。这在高提交频率的日子(例如发布新版本时)会导致长时间的延迟。

Airbnb 从那时起已转向单一代码库方法。幸运的是,这已经考虑在内,一旦 Android 和 iOS 团队对使用 React Native 感到满意,他们便乐于加速向单一代码库的迁移。

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

导航问题

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

事实上,现有的导航库似乎都不适用于棕地应用。棕地应用要求导航状态完全由原生应用拥有。例如,如果用户会话在 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 来修复任何问题而无需通过应用商店感兴趣,他们的工程师喜欢 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'}}>
Isn't this cool?
</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 项目提供,该项目提供了一种在网络上显示 TextView 等 React Native 组件的方式。如果您有兴趣构建共享大部分代码库的移动和网络体验,请查看 react-native-web

更好的指南

在 React Native 的某些部分,有多种方法可以完成事情,我们收到了反馈,认为我们可以提供更好的指导。

我们有一个新的导航指南,它比较了不同的方法并就您应该使用什么提供了建议——NavigatorNavigatorIOSNavigationExperimental。在中期,我们正在努力改进和整合这些接口。在短期内,我们希望更好的指南能让您的生活更轻松。

我们还有一个新的处理触摸指南,它解释了制作类似按钮界面的基础知识,以及处理触摸事件的不同方法的简要总结。

我们努力的另一个领域是 Flexbox。这包括关于如何使用 Flexbox 处理布局 以及如何控制 组件大小 的教程。它还包括一个不起眼但希望能有用的 React Native 中控制布局的所有 props 列表

入门

当您开始在您的机器上设置 React Native 开发环境时,确实需要进行大量的安装和配置。让安装成为一个真正有趣和令人兴奋的体验是很困难的,但我们至少可以使其尽可能快速和无痛。

我们构建了一个 新的入门工作流程,让您预先选择您的开发操作系统和移动操作系统,以便在一个简洁的地方提供所有设置说明。我们还通过了安装过程,以确保一切正常,并确保每个决策点都有明确的建议。在对我们无辜的同事进行测试后,我们非常确定这是一个改进。

我们还改进了 将 React Native 集成到现有应用程序的指南。许多使用 React Native 的最大应用程序,例如 Facebook 应用程序本身,实际上是部分使用 React Native 构建,部分使用常规开发工具构建。我们希望本指南能让更多人更容易以这种方式构建应用程序。

我们需要您的帮助

您的反馈让我们知道我们应该优先考虑什么。我知道有些人会读这篇博客文章并想“更好的文档?哼。X 的文档仍然是垃圾!”。这很好——我们需要这种动力。提供反馈的最佳方式取决于反馈的类型。

如果您在文档中发现错误,例如不准确的描述或不起作用的代码,请提交问题。将其标记为“文档”,以便更容易将其路由给合适的人。

如果不是具体的错误,但文档中的某些内容从根本上令人困惑,那么它不适合作为 GitHub 问题。相反,请在 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,这将使开发者能够为数百万智能电视以及移动和可穿戴设备构建应用程序。我们还发布了 React Native 的 Facebook SDK,这让开发者可以更轻松地将 Facebook 社交功能(如登录、分享、应用分析和 Graph API)集成到他们的应用程序中。一年之内,React Native 改变了开发者在每个主要平台上构建应用的方式。

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

这是一篇节选。阅读完整文章请访问 Facebook Code

深入了解 React Native 性能

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

React Native 允许你使用 React 和 Relay 的声明式编程模型,用 JavaScript 构建 Android 和 iOS 应用。这使得代码更简洁、易于理解;无需编译周期即可快速迭代;并且易于在多个平台之间共享代码。你可以更快地发布产品,并专注于真正重要的细节,让你的应用看起来和感觉都棒极了。优化性能是其中的重要部分。以下是我们如何让 React Native 应用启动速度加快一倍的故事。

为什么要赶时间?

应用运行更快,内容加载迅速,这意味着人们有更多时间与内容互动,流畅的动画让应用使用起来令人愉悦。在新兴市场,2011 年的手机2G 网络上是主流,对性能的关注可以决定一个应用是可用还是不可用。

自 React Native 在 iOSAndroid 上发布以来,我们一直在改进列表视图的滚动性能、内存效率、UI 响应能力和应用启动时间。启动决定了应用的第一印象,并考验了框架的所有部分,因此它是最值得解决也最具挑战性的问题。

这是摘录。阅读其余帖子请访问 Facebook Code。

引入热重载

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

React Native 的目标是为您提供最佳的开发者体验。其中很重要的一部分是您保存文件到看到更改所需的时间。我们的目标是使这个反馈循环在应用程序增长时也能保持在1秒以内。

我们通过以下三个主要功能接近这个理想状态:

  • 使用 JavaScript 作为语言,因为它没有漫长的编译周期。
  • 实现一个名为 Packager 的工具,将 ES6/Flow/JSX 文件转换为虚拟机可理解的普通 JavaScript。它被设计为一个服务器,在内存中保存中间状态以实现快速增量更改,并使用多核。
  • 构建一个名为“实时重载”的功能,在保存时重新加载应用程序。

此时,开发人员的瓶颈不再是重新加载应用所需的时间,而是丢失了应用的状态。一个常见场景是处理一个离启动屏幕有多个屏幕距离的功能。每次重新加载时,你都必须一遍又一遍地点击相同的路径才能回到你的功能,这使得周期长达数秒。

热重载

热重载的理念是保持应用程序运行,并在运行时注入您编辑的文件的最新版本。这样,您不会丢失任何状态,这在调整 UI 时尤其有用。

一图胜千言。看看实时重载(当前)和热重载(新)之间的区别。

如果您仔细观察,您会发现可以从红框中恢复,并且还可以开始导入以前不存在的模块,而无需完全重新加载。

警告:由于 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`。此时,`log` 和 `time` 的工厂函数都还没有执行,所以没有导出被缓存。然后,用户修改 `time` 以便返回 `MM/DD` 格式的日期

// time.js
function bar() {
const date = new Date();
return `${date.getMonth() + 1}/${date.getDate()}`;
}

module.exports = bar;

Packager 会将 time 的新代码发送到运行时(步骤 1),当 `log` 最终被 require 时,导出的函数会与 `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`,它依赖于 `MovieSearch` 和 `MovieScreen` 视图,而这些视图又依赖于前面示例中的 `log` 和 `time` 模块

如果用户访问电影搜索视图而不是另一个视图,除了 `MovieScreen` 之外的所有模块都将缓存导出。如果对模块 `time` 进行了更改,运行时将不得不清除 `log` 的导出,以便它能接收 `time` 的更改。这个过程不会就此结束:运行时将递归地重复这个过程,直到所有父级都被接受。因此,它将获取依赖于 `log` 的模块并尝试接受它们。对于 `MovieScreen`,它可以跳过,因为它还没有被请求。对于 `MovieSearch`,它将不得不清除其导出并递归处理其父级。最后,它将对 `MovieRouter` 执行相同的操作并在那里结束,因为没有模块依赖于它。

为了遍历依赖树,运行时从 Packager 接收 HMR 更新中的反向依赖树。对于这个例子,运行时将接收一个像这样的 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 转换,它使用 webpack 的 HMR API 来解决这个问题。简而言之,他的解决方案通过在转换时为每个 React 组件创建一个代理来实现。这些代理保存组件的状态并将生命周期方法委托给实际的组件,而这些组件正是我们热重载的组件

除了创建代理组件外,转换还定义了 `accept` 函数,其中包含一段强制 React 重新渲染组件的代码。通过这种方式,我们可以在不丢失任何应用程序状态的情况下热重载渲染代码。

React Native 附带的默认转换器使用 `babel-preset-react-native`,它被配置为以与在 React web 项目中使用 webpack 相同的方式使用 `react-transform`。

Redux Stores

要启用 Redux 存储的热重载,您只需像在使用 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 在 Web 和 React Native 在移动端的推出,我们为开发者提供了一个新的前端框架来构建产品。构建健壮产品的一个关键方面是确保任何人都可以使用它,包括视力受损或其他残疾人士。React 和 React Native 的无障碍 API 使你能够让任何基于 React 的体验可供使用辅助技术的人使用,例如盲人和视障人士的屏幕阅读器。

在这篇文章中,我们将重点关注 React Native 应用程序。我们设计的 React 无障碍 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 上阅读其余内容。

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

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

今年早些时候,我们推出了用于 iOS 的 React Native。React Native 将开发者在 Web 版 React 中习惯的——声明式自包含 UI 组件和快速开发周期——带到了移动平台,同时保留了原生应用程序的速度、保真度和体验。今天,我们很高兴发布用于 Android 的 React Native。

在 Facebook,我们使用 React Native 进行生产已有一年多。几乎就在一年前,我们的团队着手开发广告管理工具应用。我们的目标是创建一个新的应用程序,让数百万在 Facebook 上投放广告的人能够随时随地管理他们的账户和创建新的广告。它最终不仅成为 Facebook 第一个完全采用 React Native 的应用程序,也是第一个跨平台应用程序。在这篇文章中,我们想与您分享我们如何构建这个应用程序,React Native 如何使我们能够更快地行动,以及我们学到的经验教训。

这是一个摘录。阅读这篇博文的其余部分,请访问 Facebook Code

React Native:将现代网络技术带到移动端

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

两年前,我们向世界介绍了 React,自那以后,它在 Facebook 内部和外部都取得了令人瞩目的增长。如今,即使没有人被迫使用它,Facebook 的新 Web 项目也普遍以某种形式使用 React 构建,并且它在整个行业中得到了广泛采用。工程师们每天选择使用 React,因为它能让他们将更多时间专注于产品,更少时间与框架搏斗。然而,直到我们用 React 构建了一段时间后,我们才开始理解它如此强大的原因。

React 强制我们将应用程序分解成离散的组件,每个组件代表一个单一视图。这些组件使得产品迭代更加容易,因为我们无需在更改其中一部分时将整个系统都记在脑中。但更重要的是,React 用声明式 API 封装了 DOM 的命令式、可变 API,这提高了抽象级别并简化了编程模型。我们发现,使用 React 构建时,我们的代码更具可预测性。这种可预测性使我们能够自信地更快迭代,因此我们的应用程序也更加可靠。此外,使用 React 构建的应用程序不仅更容易扩展,我们还发现团队本身的规模也更容易扩展。

结合 Web 的快速迭代周期,我们已经能够使用 React 构建一些出色的产品,包括 Facebook.com 的许多组件。此外,我们还在 React 之上构建了令人惊叹的 JavaScript 框架,例如 Relay,它使我们能够大规模地极大地简化数据获取。当然,Web 只是故事的一部分。Facebook 还有广泛使用的 Android 和 iOS 应用程序,它们是在不相干的专有技术栈之上构建的。不得不在多个平台上构建我们的应用程序使我们的工程组织分化,但这只是使原生移动应用程序开发困难的其中一个原因。

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