跳到主要内容

在 React Native 中使用 TypeScript

· 阅读需 8 分钟
Ash Furrow
Artsy 软件工程师

JavaScript!我们都喜欢它。但我们中的一些人也喜欢类型。幸运的是,有办法为 JavaScript 增加更强的类型。我最喜欢的是TypeScript,但 React Native 开箱即用地支持Flow。你喜欢哪个完全是个人偏好,它们各自有自己将类型魔力赋予 JavaScript 的方法。今天,我们来看看如何在 React Native 应用中使用 TypeScript。

本文借助微软的TypeScript-React-Native-Starter 仓库作为指南。

更新:自从这篇博客文章写成以来,事情变得更简单了。你只需运行一条命令即可替代本博客中所有的设置步骤:

npx react-native init MyAwesomeProject --template react-native-template-typescript

不过,Babel 对 TypeScript 的支持仍存在一些限制,上述博客文章对此有详细说明。本文中介绍的步骤依然有效,Artsy 仍在生产中使用 react-native-typescript-transformer,但最快启动 React Native 和 TypeScript 的方式是使用上述命令。如果需要,之后你也可以随时切换。

无论如何,玩得开心!原始博客内容如下。

前提条件

因为你可能在多种不同平台开发,针对多类型设备,基本设置可能比较繁琐。你应首先确保能运行一个不带 TypeScript 的普通 React Native 应用。请按照React Native 官网的指南开始。当你成功部署到设备或模拟器后,就可以开始开发 TypeScript React Native 应用了。

你还需要安装 Node.jsnpmYarn

初始化

当你尝试搭建了一个普通的 React Native 项目后,便可以开始添加 TypeScript。我们现在动手操作。

react-native init MyAwesomeProject
cd MyAwesomeProject

添加 TypeScript

下一步为项目添加 TypeScript。以下命令将会:

  • 向项目添加 TypeScript
  • 添加 React Native TypeScript Transformer
  • 初始化一个空的 TypeScript 配置文件,我们接下来会配置它
  • 添加一个空的 React Native TypeScript Transformer 配置文件,我们也会配置它
  • 添加 React 和 React Native 的 类型定义

好,开始运行它们。

yarn add --dev typescript
yarn add --dev react-native-typescript-transformer
yarn tsc --init --pretty --jsx react
touch rn-cli.config.js
yarn add --dev @types/react @types/react-native

tsconfig.json 文件包含所有 TypeScript 编译器的设置。上述命令创建的默认配置大多合适,但打开文件并取消注释以下行:

{
/* 找到配置文件中的以下行并取消注释。 */
// "allowSyntheticDefaultImports": true, /* 允许从无默认导出的模块中默认导入。不会影响代码生成,仅用于类型检查。 */
}

rn-cli.config.js 包含 React Native TypeScript Transformer 的设置。打开它并添加以下内容:

module.exports = {
getTransformModulePath() {
return require.resolve('react-native-typescript-transformer');
},
getSourceExts() {
return ['ts', 'tsx'];
},
};

迁移到 TypeScript

将生成的 App.js__tests__/App.js 文件重命名为 App.tsxindex.js 仍须使用 .js 扩展名。所有新文件应使用 .tsx (如果文件含 JSX)或 .ts (不含 JSX)。

如果现在尝试运行应用,会出现类似 object prototype may only be an object or null 的错误。这是因为在同一行既试图导入 React 的默认导出也导入具名导出失败。打开 App.tsx,修改文件顶部的导入:

-import React, { Component } from 'react';
+import React from 'react'
+import { Component } from 'react';

部分原因是 Babel 和 TypeScript 在处理 CommonJS 模块时的互操作差异。未来两者将逐渐统一行为。

此时,你应该能运行 React Native 应用了。

添加 TypeScript 测试基础设施

React Native 默认附带 Jest,所以要用 TypeScript 测试 React Native 应用,我们需要向 devDependencies 添加 ts-jest

yarn add --dev ts-jest

接着打开项目的 package.json,替换 jest 字段为:

{
"jest": {
"preset": "react-native",
"moduleFileExtensions": [
"ts",
"tsx",
"js"
],
"transform": {
"^.+\\.(js)$": "<rootDir>/node_modules/babel-jest",
"\\.(ts|tsx)$": "<rootDir>/node_modules/ts-jest/preprocessor.js"
},
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
],
"cacheDirectory": ".jest/cache"
}
}

这样 Jest 就配置为用 ts-jest 运行 .ts.tsx 文件。

安装依赖的类型声明

为了在 TypeScript 中获得最佳体验,希望类型检查器能够理解我们依赖库的结构和 API。有些库会自带 .d.ts 类型声明文件,能够描述其 JavaScript 形态。其他库则需要我们显式安装 npm @types/ 范围内的对应包。

例如,这里我们需要为 Jest、React、React Native 和 React Test Renderer 安装类型:

yarn add --dev @types/jest @types/react @types/react-native @types/react-test-renderer

我们把这些声明文件包放在了 开发依赖 中,因为这是一个 React Native 应用,这些依赖只在开发阶段使用,而非运行时。如果你发布一个库到 NPM,可能需要将某些类型依赖放入正常依赖。

你可以在这里了解更多关于 .d.ts 文件的信息

忽略更多文件

为了你的源代码管理,你会想开始忽略 .jest 文件夹。如果你使用 git,直接向 .gitignore 添加条目即可。

# Jest
#
.jest/

作为一个检查点,考虑将这些文件提交到版本控制。

git init
git add .gitignore # 先添加这个很重要,这样才能忽略其他文件
git add .
git commit -am "Initial commit."

添加一个组件

我们来给应用添加一个组件。创建一个 Hello.tsx 组件。它是一个教学用途的组件,不是你在生产中必写的东西,但能展示如何在 React Native 中使用 TypeScript。

创建一个 components 目录,添加以下示例:

// components/Hello.tsx
import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';

export interface Props {
name: string;
enthusiasmLevel?: number;
}

interface State {
enthusiasmLevel: number;
}

export class Hello extends React.Component<Props, State> {
constructor(props: Props) {
super(props);

if ((props.enthusiasmLevel || 0) <= 0) {
throw new Error(
'You could be a little more enthusiastic. :D',
);
}

this.state = {
enthusiasmLevel: props.enthusiasmLevel || 1,
};
}

onIncrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel + 1,
});
onDecrement = () =>
this.setState({
enthusiasmLevel: this.state.enthusiasmLevel - 1,
});
getExclamationMarks = (numChars: number) =>
Array(numChars + 1).join('!');

render() {
return (
<View style={styles.root}>
<Text style={styles.greeting}>
Hello{' '}
{this.props.name +
this.getExclamationMarks(this.state.enthusiasmLevel)}
</Text>

<View style={styles.buttons}>
<View style={styles.button}>
<Button
title="-"
onPress={this.onDecrement}
accessibilityLabel="decrement"
color="red"
/>
</View>

<View style={styles.button}>
<Button
title="+"
onPress={this.onIncrement}
accessibilityLabel="increment"
color="blue"
/>
</View>
</View>
</View>
);
}
}

// 样式
const styles = StyleSheet.create({
root: {
alignItems: 'center',
alignSelf: 'center',
},
buttons: {
flexDirection: 'row',
minHeight: 70,
alignItems: 'stretch',
alignSelf: 'center',
borderWidth: 5,
},
button: {
flex: 1,
paddingVertical: 0,
},
greeting: {
color: '#999',
fontWeight: 'bold',
},
});

哇!代码挺多,但我们来拆解一下:

  • 我们不是渲染 HTML 元素比如 divspanh1,而是渲染比如 ViewButton 这样的组件。这些是跨平台的原生组件。
  • 样式通过 React Native 提供的 StyleSheet.create 函数指定。React 的样式表允许我们用 Flexbox 控制布局,使用类似 CSS 的构造进行样式设计。

添加组件测试

既然有了组件,我们就来尝试测试它。

我们已经安装了 Jest 作为测试运行器。要对组件编写快照测试,还需要安装用于快照测试的附加库:

yarn add --dev react-addons-test-utils

接着在 components 目录下创建 __tests__ 文件夹,并添加 Hello.tsx 的测试:

// components/__tests__/Hello.tsx
import React from 'react';
import renderer from 'react-test-renderer';

import {Hello} from '../Hello';

it('renders correctly with defaults', () => {
const button = renderer
.create(<Hello name="World" enthusiasmLevel={1} />)
.toJSON();
expect(button).toMatchSnapshot();
});

首次运行测试时,会生成组件渲染的快照并存储于 components/__tests__/__snapshots__/Hello.tsx.snap 文件。当你修改组件时,需要更新快照并审查改动,避免不经意的变更。你可以在这里了解更多关于测试 React Native 组件的信息

下一步

查看官方的React 教程 和状态管理库 Redux。这些资源在编写 React Native 应用时很有帮助。另外,你还可以看看 ReactXP,它是一个完全用 TypeScript 编写的组件库,同时支持网页上的 React 和 React Native。

祝你在更类型安全的 React Native 开发环境中玩得愉快!

使用 React Native 构建 - Build.com 应用

· 阅读需 5 分钟
Garrett McCullough
高级移动工程师

Build.com,总部位于加利福尼亚州奇科,是最大的家居改进商品在线零售商之一。团队已经拥有 18 年以网络为中心的业务,并于 2015 年开始考虑开发移动应用。由于团队规模不大且原生开发经验有限,构建独特的 Android 和 iOS 应用并不实际。相反,我们决定冒险采用全新的 React Native 框架。我们的首次提交是在2015年8月12日,使用 React Native v0.8.0!我们于2016年10月15日在两个应用商店上线。在过去两年中,我们持续升级和扩展应用程序。目前使用的 React Native 版本是 0.53.0。

您可以访问 https://www.build.com/app 来查看该应用。

功能

我们的应用功能完善,包含您对电商应用的所有期望:产品列表、搜索与排序、复杂产品配置功能、收藏等。我们支持标准信用卡付款方式,同时也支持 PayPal,以及为 iOS 用户提供 Apple Pay。

一些您可能没想到的特色功能包括:

  1. 大约 40 个产品配备了 90 种饰面效果的 3D 模型
  2. 增强现实 (AR) 功能,允许用户以98%的尺寸精度预览灯具和水龙头在家中的效果。Build.com React Native 应用被苹果 App Store 评为 AR 购物特色应用!AR 现已支持 Android 和 iOS!
  3. 协作项目管理功能,允许多人为不同阶段的项目制定购物清单并协作选择

我们正在开发许多新颖且令人兴奋的功能,持续提升应用体验,包括下一阶段的沉浸式 AR 购物。

我们的开发流程

Build.com 允许每位开发者选择最适合自己的工具。

  • IDE 包括 Atom、IntelliJ、VS Code、Sublime、Eclipse 等。
  • 单元测试方面,开发者负责为新组件创建 Jest 单元测试,并利用 jest-coverage-ratchet 逐步提升旧组件的测试覆盖率。
  • 我们使用 Jenkins 构建 beta 版本和发布候选版本。该流程运作良好,但仍需大量工作来创建发布说明和其他相关文档。
  • 集成测试包括一个跨桌面、移动和网页的共享测试人员池。我们的自动化工程师利用 Java 和 Appium 构建自动化集成测试套件。
  • 工作流程中还包含详细的 eslint 配置、自定义规则以强制执行测试所需属性,以及阻止有问题代码提交的 pre-push 钩子。

应用中使用的库

Build.com 应用依赖多个常用开源库,包括:Redux、Moment、Numeral、Enzyme 及大量 React Native 桥接模块。我们还使用了一些分叉的开源库,这些库或因被弃用,或因我们需要定制功能而分叉。简单统计约有 115 个 JavaScript 和原生依赖。我们希望探索能移除未使用库的工具。

我们正着手通过 TypeScript 添加静态类型支持,并研究可选链功能。这些功能能帮助我们解决几类仍存在的错误:

  • 类型错误的数据
  • 由于对象不包含预期内容导致数据未定义

开源贡献

由于我们严重依赖开源,团队致力于回馈社区。Build.com 允许团队将自建库开源,并鼓励我们为所用库做贡献。

我们已经发布并维护了多款 React Native 库:

  • react-native-polyfill
  • react-native-simple-store
  • react-native-contact-picker

我们也向许多库做出了贡献,包括:React 和 React Native、react-native-schemes-managerreact-native-swipeablereact-native-galleryreact-native-view-transformerreact-native-navigation

我们的旅程

过去几年,我们见证了 React Native 及其生态的巨大成长。早期,每个 React Native 版本似乎都修复了一些 bug,却引入了更多问题。例如,Android 上的远程 JS 调试曾断裂数月。幸运的是,2017 年开始稳定性大为提升。

导航库

导航库一直是我们面临的大难题之一。很长一段时间内,我们使用 Expo 的 ex-nav 库。它运行良好,但最终被弃用。然而,当时我们正处于功能密集开发期,无暇替换导航库,因此不得不分叉该库并为支持 React 16 和 iPhone X 自行打补丁。最终,我们迁移到了 react-native-navigation,希望它能持续获得支持。

桥接模块

桥接模块同样存在不小挑战。初期,很多关键桥接缺失。我的一位队友开发了 react-native-contact-picker,以便应用访问 Android 通讯录选择器。我们也遇见了许多因 React Native 变动而失效的桥接。例如 React Native v40 版本引入了破坏性变更,升级时我不得不提交 PR 修复 3 至 4 个尚未更新的库。

展望未来

随着 React Native 继续发展,我们对社区的期望包括:

  • 稳定并改进导航库
  • 维护 React Native 生态中的库支持
  • 优化添加原生库及桥接模块的体验

React Native 社区中的公司及个人在改善我们共同使用的工具上做出了巨大努力。如果您还未参与开源,真诚邀请您考虑为您使用的库贡献代码或文档。网上有许多入门文章,过程可能比想象的简单许多!

为 React Native 构建 <InputAccessoryView>

· 阅读需 7 分钟
Peter Argany
Facebook 软件工程师

动机

三年前,有人在 GitHub 上提出了一个 issue,要求 React Native 支持输入辅助视图(input accessory view)。

在随后的几年中,这个 issue 收到了无数的“+1”、各种解决方案,但 React Native 本身却没有做出任何实质性的改变——直到今天。我们从 iOS 开始,新提供了一个用于访问原生输入辅助视图的 API(详细文档),并且很兴奋与大家分享我们的构建过程。

背景

输入辅助视图到底是什么?根据 Apple 的开发文档,它是一个自定义视图,当一个响应者(receiver)成为第一响应者时,可以锚定在系统键盘的顶部。任何继承自 UIResponder 的对象都可以重新声明 .inputAccessoryView 属性为可读写,并管理这里的自定义视图。响应者架构负责挂载该视图,并保持其与系统键盘的同步。像拖拽或点击这些会关闭键盘的手势,会在框架层级应用到输入辅助视图上。这使我们可以构建具有交互式键盘关闭功能的内容,这是顶级消息应用(如 iMessage 和 WhatsApp)中的一个重要特性。

在键盘顶部锚定视图有两个常见应用场景。第一个是创建键盘工具栏,比如 Facebook 创作器中的背景选择器。

在此场景中,键盘聚焦于一个文本输入框,输入辅助视图用于提供额外的键盘功能。该功能与输入框的类型相关。在地图应用中,可能是地址建议;在文本编辑器中,可能是富文本格式化工具。


拥有 <InputAccessoryView> 的 Objective-C UIResponder 应该很明确。<TextInput> 变成了第一响应者,其底层实际上是一个 UITextViewUITextField 实例。

第二个常见场景是“粘性”文本输入框:

这里,文本输入框实际上是输入辅助视图本身的子视图。这在消息应用中很常见,用户可以在滚动浏览之前的消息线程时撰写消息。


在这个例子中,谁拥有 <InputAccessoryView>?还能是 UITextViewUITextField 吗?文本输入框 位于 输入辅助视图内部,这听起来像是循环依赖。单独解决这个问题就足够写成一篇 博客文章。剧透一句:拥有者是一个通用的 UIView 子类,我们手动让它 becomeFirstResponder

API 设计

我们现在知道 <InputAccessoryView> 是什么,也知道它的使用方式。下一步是设计一个既符合这两个用例,又能很好地与现有 React Native 组件(如 <TextInput>)配合使用的 API。

对于键盘工具栏,我们需要考虑以下几点:

  1. 我们希望能够将任何通用的 React Native 视图层级“提升”至 <InputAccessoryView> 中。
  2. 我们希望这个通用且独立的视图层级能接受触摸事件,并能操作应用状态。
  3. 我们希望能将一个 <InputAccessoryView> 关联到特定的 <TextInput>
  4. 我们希望能在多个文本输入框之间共享一个 <InputAccessoryView>,而无需复制任何代码。

我们可以用类似 React 门户(portals) 的概念来实现第 1 点。在此设计中,我们将 React Native 视图“门户”到一个由响应者架构管理的 UIView 层级。因为 React Native 视图本质上是 UIViews,所以做起来相当简单——我们只需要重写:

- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex

将所有子视图导入一个新的 UIView 层级即可。第 2 点上,我们为 <InputAccessoryView> 设置了一个新的 RCTTouchHandler。状态更新通过常规事件回调完成。第 3 和第 4 点,我们通过 nativeID 字段,在创建 <TextInput> 组件时,通过原生代码定位辅助视图的 UIView 层级。相关函数使用了底层的原生文本输入的 .inputAccessoryView 属性,从而实现了在 ObjC 层面的 <InputAccessoryView><TextInput> 关联。

支持粘性文本输入(场景 2)则有更多约束。因为该设计中,输入辅助视图有一个文本输入作为子视图,无法通过 nativeID 关联。相反,我们将一个通用的、位于屏幕外的 UIView.inputAccessoryView 设置为我们的原生 <InputAccessoryView> 层级,通过手动让这个通用 UIView 变成第一响应者,响应者架构将会挂载该层级。这个概念在前文提到的博客文章中有详细解释。

陷阱

当然,构建这个 API 并非一帆风顺。以下是我们遇到的一些坑及其解决方案。

最初搭建这个 API 的想法是监听 NSNotificationCenter 的 UIKeyboardWill(Show/Hide/ChangeFrame) 事件。这种模式在一些开源库和 Facebook App 的部分内部代码中都有使用。不幸的是,UIKeyboardDidChangeFrame 事件在手势滑动时无法及时调用以更新 <InputAccessoryView> 的 frame。此外,键盘高度的变化也未被这些事件捕获。这就导致了如图所示的那类界面异常:

对于 iPhone X,文本键盘和表情键盘高度不同。大多数依靠键盘事件来调整文本输入框的应用都必须修复上述 bug。我们的解决方案是改用 .inputAccessoryView 属性,这样响应者架构会处理 frame 的更新。


另一个棘手的 bug 是避免遮挡 iPhone X 的 Home Pill(底部指示条)。你可能会想,“Apple 为此专门开发了 safeAreaLayoutGuide,这不简单吗?”。我们也曾天真这样想。第一个问题是,原生 <InputAccessoryView> 实现直到即将显示时才有 window 可供锚定。没关系,我们可以重写 -(BOOL)becomeFirstResponder 方法,在那里强制执行布局约束。遵循这些约束会将输入辅助视图上移,但出现了另一个 bug:

输入辅助视图成功避开了 Home Pill,但不安全区域背后的内容会显示出来。解决方案见这个 Radar。我用一个不遵守 safeAreaLayoutGuide 约束的容器包裹了原生 <InputAccessoryView> 层级。原生容器覆盖了不安全区域的内容,而 <InputAccessoryView> 保持在安全区边界以内。


使用示例

下面是一个构建键盘工具栏按钮以重置 <TextInput> 状态的示例。

class TextInputAccessoryViewExample extends React.Component<
{},
*,
> {
constructor(props) {
super(props);
this.state = {text: 'Placeholder Text'};
}

render() {
const inputAccessoryViewID = 'inputAccessoryView1';
return (
<View>
<TextInput
style={styles.default}
inputAccessoryViewID={inputAccessoryViewID}
onChangeText={text => this.setState({text})}
value={this.state.text}
/>
<InputAccessoryView nativeID={inputAccessoryViewID}>
<View style={{backgroundColor: 'white'}}>
<Button
onPress={() =>
this.setState({text: 'Placeholder Text'})
}
title="Reset Text"
/>
</View>
</InputAccessoryView>
</View>
);
}
}

另一个关于 粘性文本输入的示例可以在仓库中找到

什么时候可以使用?

该功能的完整提交记录在 这里<InputAccessoryView> 将在即将发布的 v0.55.0 版本中提供。

祝你输入顺畅 :)

在 React Native 中使用 AWS

· 阅读需 10 分钟
Richard Threlkeld
AWS Mobile 高级技术产品经理

AWS 在技术行业中以提供云服务著称。这些服务包括计算、存储和数据库技术,以及全托管的无服务器产品。AWS Mobile 团队一直与客户和 JavaScript 生态系统的成员紧密合作,致力于使云连接的移动和 Web 应用更安全、可扩展,并且更易于开发和部署。我们一开始提供了一个完整的入门套件,随后又推出了一些新的发展。

本文介绍了 React 和 React Native 开发者可能感兴趣的一些内容:

  • AWS Amplify:一个为使用云服务的 JavaScript 应用设计的声明式库
  • AWS AppSync:一个具备离线和实时功能的全托管 GraphQL 服务

AWS Amplify

使用 Create React Native App 和 Expo 等工具,启动 React Native 应用非常简单。然而,将其连接到云端时,尝试将具体用例映射到基础设施服务可能会较为复杂。例如,您的 React Native 应用可能需要上传照片。这些照片是否需要按用户保护?那很可能意味着您需要某种注册或登录流程。您是想使用自己的用户目录,还是使用社交媒体提供商?可能您的应用还需要在用户登录后调用带有自定义业务逻辑的 API。

为帮助 JavaScript 开发者解决这些问题,我们发布了名为 AWS Amplify 的库。其设计被划分为“类别”,而非 AWS 特定实现。例如,如果您希望用户注册、登录然后上传私有照片,只需将 AuthStorage 类别引入到应用中:

import { Auth } from 'aws-amplify';

Auth.signIn(username, password)
.then(user => console.log(user))
.catch(err => console.log(err));

Auth.confirmSignIn(user, code)
.then(data => console.log(data))
.catch(err => console.log(err));

上面的代码展示了 Amplify 在一些常见任务上的帮助,比如使用多因素认证 (MFA) 码(通过电子邮件或短信)。目前支持的类别包括:

  • Auth:提供凭证自动化。开箱即用实现使用 AWS 凭证进行签名,并通过 Amazon Cognito 的 OIDC JWT 令牌。支持诸如 MFA 之类的常用功能。
  • Analytics:只需一行代码,即可在 Amazon Pinpoint 跟踪已认证或未认证的用户。可根据需求扩展自定义指标或属性。
  • API:以安全方式提供与 RESTful API 的交互,利用 AWS Signature Version 4。该模块非常适合使用 Amazon API Gateway 的无服务器基础设施。
  • Storage:简化命令上传、下载和列出 Amazon S3 中的内容。还可根据用户将数据分组为公共或私有内容。
  • Caching:跨 Web 应用和 React Native 的 LRU 缓存接口,使用特定实现的持久化。
  • i18n 和 Logging:提供国际化和本地化功能,以及调试和日志功能。

Amplify 的一个优点是,它在设计中为您的特定编程环境编码了“最佳实践”。例如,我们与客户及 React Native 开发者合作时发现,开发阶段为快速实现功能所选的捷径往往会最终进入生产环境。这可能会影响扩展性或安全性,并迫使进行基础设施重构和代码重写。

帮助开发者避免此类问题的一个例子是 基于 AWS Lambda 的无服务器参考架构。它展示了结合使用 Amazon API Gateway 和 AWS Lambda 构建后端的最佳实践模式。该模式被编码进 Amplify 的 API 类别中。您可以使用这一模式与多个 REST 端点交互,并将请求头传递给 Lambda 函数以实现自定义业务逻辑。我们还发布了一个 AWS Mobile CLI,方便快速为新的或现有的 React Native 项目引入这些功能。入门只需通过 npm 安装并按照配置提示进行:

npm install --global awsmobile-cli
awsmobile configure

另一个与移动生态系统相关的最佳实践示例是密码安全。默认的 Auth 类别实现利用 Amazon Cognito 用户池进行用户注册和登录。该服务采用安全远程密码协议 来保护用户身份验证过程。如果您有兴趣阅读该协议的数学原理,您会发现计算密码验证器时必须使用一个大素数来生成一个群。在 React Native 环境中,JIT 被禁用,这使得进行类似安全操作所需的大整数计算性能较低。为此,我们发布了适用于 Android 和 iOS 的本地桥接,您可在项目中链接:

npm install --save aws-amplify-react-native
react-native link amazon-cognito-identity-js

此外,我们很高兴看到 Expo 团队已在其最新 SDK中包含了这点,您可以无需弹出(eject)即可使用 Amplify。

最后,针对 React Native(及 React)开发,Amplify 提供了 高阶组件 (HOCs),方便包裹功能,例如对应用进行注册和登录:

import Amplify, { withAuthenticator } from 'aws-amplify-react-native';
import aws_exports from './aws-exports';

Amplify.configure(aws_exports);

class App extends React.Component {
...
}

export default withAuthenticator(App);

底层组件也提供为 <Authenticator />,允许您完全自定义 UI。它还提供了用于管理用户状态(如是否已登录或等待 MFA 确认)以及状态变化时可触发回调的属性。

同样,您还会找到通用的 React 组件,可用于各种用例。您可以根据需求自定义,例如展示来自 Storage 模块中 Amazon S3 的所有私有图片:

<S3Album
level="private"
path={path}
filter={(item) => /jpg/i.test(item.path)} />

如前所示,您可通过属性控制组件的许多功能,包括公共或私有存储选项。甚至提供了用户与某些 UI 组件交互时自动收集分析数据的能力:

return <S3Album track/>

AWS Amplify 推崇约定优于配置的开发风格,支持全局初始化或按类别级别初始化。最快速的入门方式是使用一个 aws-exports 文件。不过开发者也可以独立地使用该库与已有资源协作。

想深入了解其设计理念及完整演示,请参阅 AWS re:Invent 的相关视频

AWS AppSync

在 AWS Amplify 发布不久后,我们还推出了 AWS AppSync。这是一项具备离线和实时能力的全托管 GraphQL 服务。虽然 GraphQL 可用于多种客户端语言(包括原生 Android 和 iOS),但它在 React Native 开发者中非常受欢迎,因为数据模型非常符合单向数据流和组件层次结构。

AWS AppSync 使您能够连接到自己 AWS 账户中的资源,意味着数据归您所有且您能完全控制。这通过使用数据源实现,服务支持 Amazon DynamoDBAmazon ElasticsearchAWS Lambda。这使得您可以在单一 GraphQL API 模式下结合使用多种功能(如 NoSQL 和全文搜索)。AppSync 还支持从模式自动配置资源,如果您不熟悉 AWS 服务,只需编写 GraphQL SDL,点击按钮即可自动完成构建。

AWS AppSync 的实时功能通过使用熟知的事件驱动模式的 GraphQL 订阅实现。由于 AWS AppSync 的订阅受 GraphQL 模式中的指令控制,且模式可以使用任意数据源,这意味着您可以基于 Amazon DynamoDB 和 Amazon Elasticsearch 服务的数据库操作,或基于 AWS Lambda 的其他基础设施触发通知。

类似于 AWS Amplify,您可以在 AWS AppSync 的 GraphQL API 上使用企业级安全功能。该服务允许通过 API 密钥快速启动,但生产环境下可以转而使用 AWS 身份和访问管理 (IAM) 或来自 Amazon Cognito 用户池的 OIDC 令牌。您可以通过类型策略在解析器层级控制访问,甚至可以在运行时进行逻辑检查,实现细粒度访问控制(例如检测用户是否为特定数据库资源的所有者)。此外,还支持基于组成员资格来执行解析器或访问单个数据库记录的权限控制。

为了帮助 React Native 开发者更好地了解这些技术,AWS AppSync 控制台首页内置了一个GraphQL 示例模式。该示例会自动部署 GraphQL 模式,配置数据库表,并连接查询、变更和订阅。另有一个基于此内置模式的功能齐全的AWS AppSync React Native 示例(以及React 示例),可助您在数分钟内启动客户端和云端组件。

使用 AWSAppSyncClient 启动十分简单,它集成于 Apollo ClientAWSAppSyncClient 负责 GraphQL API 的安全性和签名、离线功能,以及订阅的握手和协商过程:

import AWSAppSyncClient from "aws-appsync";
import { Rehydrated } from 'aws-appsync-react';
import { AUTH_TYPE } from "aws-appsync/lib/link/auth-link";

const client = new AWSAppSyncClient({
url: awsconfig.graphqlEndpoint,
region: awsconfig.region,
auth: {type: AUTH_TYPE.API_KEY, apiKey: awsconfig.apiKey}
});

AppSync 控制台提供可下载的配置文件,包含您的 GraphQL 端点、AWS 区域和 API 密钥。您接着可以将客户端与React Apollo一同使用:

const WithProvider = () => (
<ApolloProvider client={client}>
<Rehydrated>
<App />
</Rehydrated>
</ApolloProvider>
);

此时,您可以使用标准 GraphQL 查询:

query ListEvents {
listEvents{
items{
__typename
id
name
where
when
description
comments{
__typename
items{
__typename
eventId
commentId
content
createdAt
}
nextToken
}
}
}
}

上例展示了 AppSync 提供的示例应用模式中的查询。它不仅展示了与 DynamoDB 的交互,还包含了数据分页(包括加密令牌)以及 EventsComments 之间的类型关系。由于应用已配置 AWSAppSyncClient,数据会自动离线持久化,并在设备重新连接时同步。

您可以观看此视频,深入了解其客户端技术及 React Native 演示

反馈

这些库背后的团队期待听到您对它们的使用体验反馈。他们也希望了解还能做些什么,以便让使用云服务的 React 和 React Native 开发更简单。请通过 GitHub 联系 AWS Mobile 团队,相关项目为 AWS AmplifyAWS AppSync

在 React Native 中实现 Twitter 的应用加载动画

· 阅读需 10 分钟
Eli White
Eli White
Software Engineer @ Meta

Twitter 的 iOS 应用有一个我非常喜欢的加载动画。

当应用准备好时,Twitter 标志会令人愉快地放大,展现出应用界面。

我想弄明白如何用 React Native 重新创建这个加载动画。


为了理解_如何_去构建它,我首先必须理解加载动画的不同部分。最简单的查看细微差别的方法是将动画放慢。

这里有几个主要部分我们需要弄清楚如何构建。

  1. 对小鸟进行缩放。
  2. 随着小鸟变大,露出下面的应用。
  3. 在结束时稍微缩小应用。

我花了相当长时间才弄明白如何制作这个动画。

我一开始有一个_错误_的假设,以为蓝色背景和 Twitter 小鸟是在应用的_上层_,当小鸟放大时它变得透明,露出下面的应用。这个方法行不通,因为 Twitter 小鸟变透明会显示蓝色层,而不是下面的应用!

幸运的是,亲爱的读者,你不用经历我那样的挫折。你得到的是这个跳过废话的精彩教程!


正确的方法

在进入代码之前,了解如何拆解这个效果非常重要。为了帮助可视化这个效果,我在 CodePen 中重新创建了它(嵌入于几段文字中),这样你可以交互式地查看不同的图层。

这个效果主要有三个图层。第一层是蓝色背景层。虽然看起来它出现在应用上方,但实际上它是在后面。

然后是一个纯白色层。最后,最前面是我们的应用。


这个动画的关键是使用 Twitter 标志作为蒙版,并对应用层和白色层进行蒙版处理。我不会深入讲解蒙版的细节,网上有大量 资料 参考

在这里蒙版的基础概念是,蒙版中不透明的像素显示它所蒙版的内容,而透明的像素则隐藏它所蒙版的内容。

我们用 Twitter 标志作为蒙版,同时蒙版两个层;纯白色层和应用层。

为了显示应用,我们将蒙版放大,直到它比整个屏幕还大。

在蒙版放大的同时,我们将应用层的透明度淡入,显示应用并隐藏后面的纯白层。为了完成效果,我们让应用层开始时缩放略大于 1,然后动画结束时缩放到 1。之后隐藏非应用层,因为它们将不再被看到。

俗话说一图胜千言。一个交互式可视化值多少字?点击“下一步”按钮,逐步查看动画。显示不同图层可以让你拥有侧视角度。网格帮助可视化透明图层。

现在,讲讲 React Native 实现

好啦。现在我们知道想做什么以及动画如何工作,可以进入代码了——这才是你真正关心的。

这个谜题的核心是 MaskedViewIOS,这是 React Native 的核心组件。

import {MaskedViewIOS} from 'react-native';

<MaskedViewIOS maskElement={<Text>Basic Mask</Text>}>
<View style={{backgroundColor: 'blue'}} />
</MaskedViewIOS>;

MaskedViewIOS 接收 props maskElementchildren。children 会被 maskElement 蒙版。注意,蒙版不一定非得是图片,可以是任意视图。上面示例的效果是在“Basic Mask”的文字区域显示蓝色视图,其他地方透明。我们刚刚用蓝色文字制造了复杂的效果。

我们想做的是先渲染蓝色层,然后在其上用 Twitter 标志蒙版渲染应用和白色层。

{
fullScreenBlueLayer;
}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Image source={twitterLogo} />
</View>
}>
{fullScreenWhiteLayer}
<View style={{flex: 1}}>
<MyApp />
</View>
</MaskedViewIOS>;

这会得到我们下面看到的图层。

现在进入动画部分

我们已有制作动画所需的所有部分,接下来要动起来。为了让动画体验顺畅,我们将使用 React Native 的 Animated API。

Animated 允许我们在 JavaScript 中声明式地定义动画。默认情况下,动画在 JavaScript 中运行,每帧告诉原生层要做哪些变化。即使 JavaScript 尝试每帧更新动画,通常也追不上,可能出现掉帧(卡顿)。这可不是我们想要的!

Animated 有特殊机制避免卡顿。它有个叫 useNativeDriver 的 flag,会在动画开始时将动画定义从 JavaScript 发送到原生,让原生端自己处理动画更新,无须每帧都回传 JavaScript。useNativeDriver 的限制是只能更新特定属性,主要是 transformopacity。目前还不能用它来更新背景颜色等属性——未来会增加更多,当然你也能为这个项目提交 PR,造福社区😀。

既然想让动画顺滑,我们就在这些限制内工作。想了解useNativeDriver内部工作原理,可以看我们的宣布博客

拆解动画步骤

我们的动画包含 4 个部分:

  1. 放大小鸟,露出应用和纯白层。
  2. 让应用淡入。
  3. 缩小应用。
  4. 动画结束后隐藏白层和蓝层。

用 Animated,有两种主流定义动画的方式。第一种是用 Animated.timing,指定动画时长和缓动曲线让移动更自然。另一种是用基于物理的 API,如 Animated.spring,指定阻力和张力参数,让动画由物理模拟驱动。

这里有多段紧密相关的动画同时运行,比如我们希望应用在蒙版中途放大时开始淡入。因为它们紧密相关,我们将用单个 Animated.Value 结合 Animated.timing

Animated.Value 是 Animated 对原生值的包装器,表示动画的当前状态。通常一个完整动画只用一个。大多数 Animated 组件会把它存在 state 里。

我把动画视作不同时刻发生的步骤,让 Animated.Value 从 0 开始,表示 0% 完成度,最终到 100,表示 100% 完成度。

初始组件 state 如下:

state = {
loadingProgress: new Animated.Value(0),
};

当动画准备好开始时,调用 Animated 去动画到 100。

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true, // 这一点很重要!
}).start();

我接下来大致估算动画不同阶段应该是多少数值,以下是动画不同片段的取值预期随时间进度的表格。

Twitter 小鸟蒙版初始 scale 是 1,会先缩小一点,然后猛涨到超大,动画快结束时大约是 70。70 是个经验值,要确保它完全覆盖屏幕,60 不够😀。这里有趣的是,数字越大,看上去变大的速度越快,因为时间总量不变。这部分调试花了不少时间确保与标志搭配效果好。不同标志和设备,最终 scale 需调整以覆盖全屏。

应用保持不透明一段时间,至少在 Twitter 标志缩小时。官方动画上,我想在小鸟放大到一半时开始显示应用,到动画 30% 处完全显示。

应用初始化 scale 是 1.1,动画结束缩放回 1。

现在,代码实践。

我们在上面做的就是根据动画进度百分比映射到各个部分的数值。用 Animated 的 .interpolate 方法实现。创建三个样式对象,每个对应动画不同部分,用 this.state.loadingProgress 作插值。

const loadingProgress = this.state.loadingProgress;

const opacityClearToVisible = {
opacity: loadingProgress.interpolate({
inputRange: [0, 15, 30],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
// clamp 意味着当输入为 30-100 时,输出保持为 1
}),
};

const imageScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 10, 100],
outputRange: [1, 0.8, 70],
}),
},
],
};

const appScale = {
transform: [
{
scale: loadingProgress.interpolate({
inputRange: [0, 100],
outputRange: [1.1, 1],
}),
},
],
};

现在有了这几个样式对象,我们可以在渲染时使用它们。注意,只有 Animated.ViewAnimated.TextAnimated.Image 组件支持带有 Animated.Value 的样式。

const fullScreenBlueLayer = (
<View style={styles.fullScreenBlueLayer} />
);
const fullScreenWhiteLayer = (
<View style={styles.fullScreenWhiteLayer} />
);

return (
<View style={styles.fullScreen}>
{fullScreenBlueLayer}
<MaskedViewIOS
style={{flex: 1}}
maskElement={
<View style={styles.centeredFullScreen}>
<Animated.Image
style={[styles.maskImageStyle, imageScale]}
source={twitterLogo}
/>
</View>
}>
{fullScreenWhiteLayer}
<Animated.View
style={[opacityClearToVisible, appScale, {flex: 1}]}>
{this.props.children}
</Animated.View>
</MaskedViewIOS>
</View>
);

耶!动画部分已经符合预期。现在只需清理我们的蓝色和白色层,因为它们动画结束后都不再显示。

为了知道何时清理它们,我们需要知道动画是否完成。幸运的是,调用 Animated.timing.start 方法时,可以传入动画结束时执行的回调。

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start(() => {
this.setState({
animationDone: true,
});
});

有了 state 中的 animationDone 字段,知道动画是否结束后,我们可以根据它动态渲染蓝色和白色层。

const fullScreenBlueLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenBlueLayer]} />
);
const fullScreenWhiteLayer = this.state.animationDone ? null : (
<View style={[styles.fullScreenWhiteLayer]} />
);

就这样!动画现在可以正常工作,完成后自动清理不需要的图层。我们复刻了 Twitter 应用的加载动画!

等等,我的动画不工作!

别担心,亲爱的读者。我也讨厌那种只给你代码片段,却不提供完整源码的教程。

这个组件已经发布到 npm,且托管在 GitHub 上,地址是 react-native-mask-loader。想在手机上试试,可以在 Expo 上打开

延伸阅读 / 额外练习

  1. 这本 gitbook 是学 Animated 后非常棒的资源。
  2. Twitter 官方动画在末尾会加快蒙版的展开速度。试试修改 loader,用不同的缓动函数(或者 spring!)来更贴近官方效果。
  3. 蒙版最终 scale 目前是硬编码的,可能无法在平板等大屏设备上完整展示应用。根据屏幕尺寸和图片大小计算最终 scale 会是个超赞的 PR 贡献。

React Native 每月速递 #6

· 阅读需 4 分钟
Tomislav Tenodi
Speck 创始人

React Native 月度会议依然如火如荼地进行着!请务必查看本文底部的备注,了解下一次会议时间。

Expo

  • 祝贺 Devin AbbottHoussein Djirdeh 发布了《Full Stack React Native》一书的预发布版!这本书通过构建多个小应用来带你学习 React Native。
  • 发布了第一个(实验性)版本的 reason-react-native-scripts,帮助大家轻松尝试 ReasonML
  • Expo SDK 24 已发布!它使用了 React Native 0.51,包括许多新功能和改进:独立应用中的图片打包(首次加载无需缓存!)、图片处理 API(裁切、缩放、旋转、翻转)、人脸检测 API、全新发布频道功能(为指定频道设置活跃版本及回滚)、用于跟踪独立应用构建的网页仪表盘,以及修复了 Android OpenGL 实现与多任务管理器的长期 bug,仅举几例。
  • 从今年一月开始,我们将投入更多资源支持 React Navigation。我们坚信仅使用 React 组件及 Animated 和 react-native-gesture-handler 等基础库来构建 React Native 导航既可能又理想,我们对计划中的一些改进感到非常兴奋。如果你想为社区贡献力量,可以关注 react-native-mapsreact-native-svg,两者都非常需要帮助!

Infinite Red

Microsoft

  • 已启动一个拉取请求,旨在将 React Native Windows 桥接核心迁移到 .NET Standard,从而实现跨操作系统通用。这样许多其他 .NET Core 平台就能用自己的线程模型、JavaScript 运行时和 UIManager 扩展该桥接(例如 JavaScriptCore、Xamarin.Mac、Linux Gtk#及三星 Tizen 选项)。

Wix

  • Detox
    • 为了让端到端测试规模化并减少 CI 时间,我们正在开发 Detox 的并行支持。
    • 提交了拉取请求,以支持自定义 flavor 构建,更好地支持 E2E 测试的 mock。
  • DetoxInstruments
    • DetoxInstruments 的杀手级功能开发极具挑战性,任意时刻获取 JavaScript 调用栈需要 JSCore 的自定义实现以支持 JS 线程挂起。在 Wix 内部 app 上测试该性能分析器揭示了 JS 线程的一些有趣见解。
    • 该项目尚不够稳定供公众使用,但开发积极进行中,我们希望很快能发布。
  • React Native Navigation
    • V2 版本开发速度显著提升,之前仅有一名开发者兼职(20% 时间)开发,现在已有 3 名开发者全职投入!
  • Android 性能优化
    • 用 RN 捆绑的旧版 JSCore 替换为它的最新版本(基于 webkitGTK 项目的最新代码,带有自定义 JIT 配置)使 JS 线程性能提升了40%。接下来将编译 64 位版本。此工作基于 JSC 安卓构建脚本。可在此处查看当前状态。

下一次会议

目前有讨论考虑将本次会议重心调整为集中讨论单一具体主题(例如导航、将 React Native 模块拆分到独立仓库、文档等)。这样我们觉得能为 React Native 社区贡献更多。这可能会在下次会议中实施。欢迎通过推特告诉我们你希望被讨论的主题。

React Native 月报 #5

· 阅读需 4 分钟
Tomislav Tenodi
Speck 创始人

React Native 月度会议继续进行中!让我们看看各团队最近在忙些什么。

Callstack

  • 我们一直在完善 React Native CI。最重要的是,我们已从 Travis 迁移到 Circle,使 React Native 只保留一个统一的 CI 流程。
  • 我们组织了 Hacktoberfest - React Native 版本,与参与者一起尝试向开源项目提交大量 Pull Request。
  • 我们持续开发 Haul。上个月我们发布了两个新版本,包括 webpack 3 支持。我们计划添加对 CRNAExpo 的支持,并改进热模块替换(HMR)。我们的路线图已在 issue 跟踪器公开。如果你想建议改进或提供反馈,欢迎告诉我们!

Expo

  • 发布了 Expo SDK 22(基于 React Native 0.49),并为此更新了 CRNA
    • 包含改进的启动屏 API、基本的 ARKit 支持、“DeviceMotion” API、iOS 11 上的 SFAuthenticationSession 支持及更多内容
  • 你的 snacks 现在可以包含多个 JavaScript 文件,同时可以通过拖拽直接上传图片和其它资源到编辑器中。
  • 贡献代码到 react-navigation,以新增对 iPhone X 的支持。
  • 聚焦解决使用 Expo 构建大型应用时遇到的难点。例如:
    • 多环境部署的一级支持:包括 staging、生产以及任意频道。频道支持回滚并设置当前发布版本。如果你想成为早期测试者,请告诉我们,@expo_io
    • 我们也正在改进独立应用构建基础设施,新增在独立应用构建时打包图片和其它非代码资源的支持,同时保持通过 OTA 更新资源的能力。

Facebook

  • 更好的 RTL 支持:
    • 我们引入了多种方向感知的样式。
      • 位置:
        • (left|right) → (start|end)
      • 外边距:
        • margin(Left|Right) → margin(Start|End)
      • 内边距:
        • padding(Left|Right) → padding(Start|End)
      • 边框:
        • borderTop(Left|Right)Radius → borderTop(Start|End)Radius
        • borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius
        • border(Left|Right)Width → border(Start|End)Width
        • border(Left|Right)Color → border(Start|End)Color
    • 在 RTL 中,“left”和“right” 在 position、margin、padding 和 border 样式中的含义被交换。几个月内,我们将移除此行为,使“left”始终表示“左”,“right”始终表示“右”。此破坏性更改被隐藏在一个开关下。在 React Native 组件中使用 I18nManager.swapLeftAndRightInRTL(false) 以启用此改动。
  • 正在为我们的内部原生模块添加 Flow 类型,并利用这些类型生成 Java 中的接口和 ObjC 中的协议,原生实现必须遵循这些协议。我们希望这些代码生成工具最早能在明年开源。

Infinite Red

  • 推出一款新的开源工具,帮助 React Native 及其他项目。详情见这里
  • 正在重构 Ignite,为新版本模版做准备(代号:Bowser)。

Shoutem

  • 改善 Shoutem 的开发流程。我们希望简化从创建应用到第一个自定义界面的过程,让新手 React Native 开发者更容易上手。准备了几个工作坊测试新功能。我们还改进了 Shoutem CLI 来支持新的流程。
  • Shoutem UI 收到了若干组件改进和 bug 修复。我们也检查了其与最新 React Native 版本的兼容性。
  • Shoutem 平台收到了若干显著更新,新集成作为 开源扩展项目 的一部分发布。看到其他开发者积极为 Shoutem 扩展贡献代码,我们感到十分兴奋。我们积极联系并提供建议与指导。

下一次会议

下一次会议定于 2017 年 12 月 6 日星期三。如有任何建议,欢迎通过 Twitter 联系我,告诉我们如何改进会议成果。

React Native 月刊 #4

· 阅读需 3 分钟
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack

React Native 月度会议持续进行!以下是各团队的会议记录:

Callstack

  • React Native EU 已经结束。来自33个国家的300多名参与者访问了弗罗茨瓦夫。演讲视频可以在 YouTube 上观看。
  • 会议结束后,我们正逐步恢复开源项目的发布时间表。值得一提的是,我们正在开发react-native-opentok的下一个版本,修复了大部分现有问题。

GeekyAnts

尝试通过以下方式降低开发者使用 React Native 的门槛:

  • React Native EU 宣布了 BuilderX.io。BuilderX 是一款设计工具,可以直接操作 JavaScript 文件(目前仅支持 React Native),生成美观、易读且可编辑的代码。
  • 推出了 ReactNativeSeed.com ,为你的下一个 React Native 项目提供一套初始化模板。模板选项丰富,包括 TypeScript 和 Flow 用于数据类型,状态管理支持 MobX、Redux 和 mobx-state-tree,支持 CRNA 及纯 React-Native 技术栈。

Expo

  • 即将发布 SDK 21,支持 react-native 0.48.3,同时带来一系列修复、稳定性提升和新功能,包括视频录制、新的启动屏 API、支持 react-native-gesture-handler 以及改进的错误处理。
  • 关于 react-native-gesture-handlerKrzysztof Magiera 来自 Software Mansion 持续推进此项目,我们协助测试并资助了部分开发时间。SDK 21 集成该功能后,大家可以在 Snack 中轻松试用,非常期待大家的创意展现。
  • 关于改进的错误日志和处理——详情可见这段 Expo 内部 PR 的 gist(特别是“问题2”部分),以及这次提交,它处理了导入 npm 标准库模块失败的情况。React Native 有很多机会在上游改进错误信息,我们会持续推动相关 PR,也欢迎社区加入。
  • native.directory 持续增长,你可以从GitHub 仓库添加你的项目。
  • 参加北美各地的黑客马拉松活动,包括 PennAppsHack The NorthHackMIT 以及即将到来的 MHacks

Facebook

  • 正在改进 Android 平台上的 <Text><TextInput> 组件。(包括 <TextInput> 的原生自动增长,深层嵌套的 <Text> 组件布局问题,更好的代码结构以及性能优化)。
  • 我们依旧在寻找更多贡献者,帮助处理问题和 Pull Request。

Microsoft

  • 发布了 CodePush 的代码签名功能。React Native 开发者现在可以在 CodePush 中对应用包进行签名。相关公告见 这里
  • 正在完成 CodePush 与 Mobile Center 的集成,同时考虑增加测试和崩溃集成。

下一次会议

下一次会议定于 2017 年 10 月 10 日星期三。鉴于这仅是我们的第四次会议,我们希望了解这些会议记录如何为 React Native 社区带来帮助。如果你有任何关于如何改进会议成果的建议,欢迎通过 Twitter 联系我。

React Native 月刊 #3

· 阅读需 5 分钟
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack

React Native 月度会议继续进行!本月的会议稍微短了一些,因为我们的大多数团队都忙于发布产品。下个月,我们将在波兰弗罗茨瓦夫举办的 React Native EU 大会上见面。务必抢购门票,现场见!与此同时,让我们看看团队们正在做些什么。

团队

在第三次会议上,有 5 个团队加入了我们:

会议纪要

以下是各团队的纪要:

Callstack

  • 最近开源了 react-native-material-palette。它能从图片中提取主要颜色,帮助你创建视觉上更吸引人的应用。目前仅支持 Android,但我们正在考虑未来添加 iOS 支持。
  • 我们已经把 HMR 支持合并到了 haul 以及其它一堆很酷的新功能!请查看最新版本发布。
  • React Native EU 2017 即将来临!下个月全是关于 React Native 和波兰的内容!务必在这里抢购最后的门票。

Expo

  • 推出了对 Snack 中安装 npm 包的支持。Expo 的常规限制依然适用——包不能依赖 Expo 中尚未包含的自定义原生 API。我们还在努力支持在 Snack 中使用多文件和上传资源。Satyajit 将在 React Native Europe 上介绍 Snack。
  • 发布了带有相机、支付、安全存储、磁力计、暂停/恢复文件系统下载和改进的启动/加载屏幕的 SDK20。
  • 继续与 Krzysztof 合作开发 react-native-gesture-handler。欢迎大家试用一下,用它重建之前用 PanResponder 或原生手势识别器实现的手势,并告诉我们遇到了哪些问题。
  • 试验 JSC 调试协议,正在根据 Canny 上的许多功能需求进行工作。

Facebook

  • 上个月我们讨论了 GitHub 问题追踪器的管理,希望尝试改进,以提升项目的可维护性。
  • 目前,打开的问题数保持在约 600 个,并且看起来可能会维持一段时间。过去一个月,我们关闭了 690 个由于缺少活动(定义为过去 60 天内无评论)的问题。在这690个问题中,有 58 个因多种原因被重新打开(例如维护者承诺修复,或贡献者提出保留问题的有力理由)。
  • 我们计划在可预见的未来继续自动关闭陈旧问题。我们的目标是每个有影响力的问题都能得到响应,但我们还没达到。我们需要所有维护者协助完成问题筛查,确保不遗漏引入回归或破坏性变更的问题,尤其是那些影响新建项目的问题。有意帮忙的朋友可以使用 Facebook GitHub Bot 来筛查问题和拉取请求。新的维护者指南包含了筛查和使用 GitHub Bot 的更多信息。请加入我们的问题特别工作组,并鼓励其他活跃社区成员一同加入!

Microsoft

  • 新版 Skype 应用基于 React Native 构建,旨在实现跨平台尽可能多的代码共享。基于 React Native 的 Skype 应用目前已在 Android 和 iOS 应用商店上线。
  • 在使用 React Native 构建 Skype 应用时,我们向 React Native 发送了许多拉取请求,修复遇到的 bug 和缺失功能。目前,我们已有约 70个拉取请求被合并
  • React Native 使我们能够基于同一代码库驱动 Android 和 iOS 版 Skype 应用。我们还希望用该代码库支持 Skype Web 应用。为此,我们打造并开源了名为 ReactXP 的轻量层,位于 React/React Native 之上。ReactXP 提供一套跨平台组件,目标为 iOS/Android 时映射到 React Native,目标为 Web 时映射到 react-dom。ReactXP 的目标类似于另一个开源库 React Native for Web。关于这两个库方法的区别,详见 ReactXP FAQ

Shoutem

  • 我们持续努力改进和简化使用 Shoutem 构建应用的开发者体验。
  • 开始将所有应用迁移到 react-navigation,但最终推迟,等待更稳定版本的发布,或等待某个原生导航方案成熟。
  • 将我们所有的 扩展 以及大部分开源库(animationthemeui)升级到 React Native 0.47.1。

下次会议

下次会议定于 2017 年 9 月 13 日星期三。作为我们仅举行的第三次会议,我们想知道这些纪要对 React Native 社区有何帮助。如果你有关于如何改进会议输出的建议,欢迎随时通过我的 Twitter 联系我。

React Native 在 Marketplace 的性能表现

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

React Native 在 Facebook 旗下的多个应用中均有使用,包括主 Facebook 应用中的顶级标签页。本文主要聚焦一个高度可见的产品,Marketplace。该产品已在十几个国家上线,帮助用户发现其他用户提供的商品和服务。

在 2017 年上半年,通过 Relay 团队、Marketplace 团队、Mobile JS 平台团队和 React Native 团队的共同努力,我们将 Android 平台上 2010-11 年代级别设备的 Marketplace 交互时间(TTI)缩短了一半。Facebook 历来将这些设备视为低端 Android 设备,它们在所有平台和设备类型中具有最慢的 TTI。

典型的 React Native 启动过程大致如下图所示:

免责声明:图中比例不具代表性,具体情况将根据 React Native 的配置和使用方式有所不同。

我们首先初始化 React Native 核心(亦即“Bridge”),接着运行产品特定的 JavaScript,这部分决定了 React Native 将在本地用哪些视图进行渲染,这一步在“Native Processing Time”中完成。

不同的做法

我们早期犯的一个错误是让Systrace 和 CTScan主导了我们的性能工作。这些工具帮助我们发现了许多明显的问题(低垂的果实),但我们后来发现 Systrace 和 CTScan 并不能代表真实生产环境的场景,也无法模拟实际使用时的情况。时间分解中的比例常常不准确,有时偏差极大。在极端情况下,一些我们以为只需几毫秒的操作,实际耗时却达数百甚至数千毫秒。话虽如此,CTScan 仍然有用,我们发现它能在生产前捕获三分之一的性能回归。

在 Android 上,我们将这些工具的局限归因于:1)React Native 是一个多线程框架;2)Marketplace 与复杂视图(如新闻推送和其他顶级标签页)共存;3)计算时间波动极大。因此,本半年度,我们几乎完全依靠生产环境的测量数据和时间分解,作为决策和优先级排序的依据。

生产环境检测之路

在生产环境中做性能监测乍看简单,但实际上过程颇为复杂。该过程经历了多个 2-3 周的迭代周期;原因在于从提交代码到主分支、发布应用到 Play 商店,到收集足够生产环境样本进而确认工作有效性存在较大延迟。每个迭代周期,我们都要验证时间分解的准确性、粒度合适性以及各项数值是否正确累计到整体时间。我们无法依赖 alpha 和 beta 版本,因为它们的数据无法代表整体用户群。其实质是,我们非常细致地基于数百万样本的汇总结果构建了一份非常精确的生产追踪。

我们细致核实每个毫秒在时间分解中的正确计入,原因之一是早期发现检测工具存在遗漏。初期时间分解未考虑到因线程切换导致的阻塞。线程切换本身花费不大,但切换到已经繁忙的线程会非常耗时。我们最终通过在关键时刻人为插入 Thread.sleep() 调用,在本地重现了这些阻塞情形,并通过以下方式予以解决:

  1. 移除对 AsyncTask 的依赖,
  2. 取消在 UI 线程上强制初始化 ReactContext 和 NativeModules,
  3. 取消启动时对 ReactRootView 的强制测量依赖。

这三步合力减少了启动时间超过 25%。

生产数据指标还挑战了我们之前的一些假设。例如,我们过去习惯在启动路径中预加载许多 JavaScript 模块,认为将模块合并到一个包中可降低初始化成本。但事实上,预加载和模块集中加载的成本远大于带来的好处。通过重新配置 inline require 黑名单,并剔除启动路径中的多余模块(如仅需 Relay Modern时不引入 Relay Classic),我们实现了 RUN_JS_BUNDLE 阶段比之前快超过 75%。

产品特定的本地模块优化也带来了显著提升。例如,通过对本地模块依赖采用懒注入策略,我们将其耗时削减了 98%。通过避免 Marketplace 启动时与其他模块竞争资源,我们同样缩短了启动时间。

最棒的是,许多这种改进对所有用 React Native 构建的页面均有广泛适用性。

总结

许多人认为 React Native 的启动性能瓶颈是 JavaScript 执行缓慢或网络延迟过高。虽然优化 JavaScript 执行确实能非同小可地减少 TTI,但这些因素各自占据的时间比之前普遍认为的要少得多。

到目前为止,最重要的经验是——测量,测量,再测量! 一些优化得益于将运行时开销转移到构建时,比如 Relay Modern 和 Lazy NativeModules;另一些则来自更聪明的代码并行化或死代码剔除;还有一部分依赖 React Native 体系内的大规模架构改进,如解决线程阻塞问题。性能优化没有“一劳永逸”的方案,更长远的成效基于持续的检测和改进。不要被认知偏见左右决策,而应仔细收集并解读生产数据,引导后续工作。

未来规划

长期来看,我们期望 Marketplace 的 TTI 能与类似原生产品相当,整体上实现 React Native 性能与原生性能并驾齐驱。此外,尽管本半年度我们已将 Bridge 启动成本大幅降低约 80%,后续计划通过类似 Prepack 等项目以及更多构建时处理,将 React Native Bridge 的开销逼近零。