跳到主要内容

将 TypeScript 与 React Native 结合使用

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

JavaScript!我们都爱它。但我们中的一些人也喜欢类型。幸运的是,存在为 JavaScript 添加更强类型的方法。我最喜欢的是TypeScript,但 React Native 对Flow支持开箱即用。您更喜欢哪种取决于个人喜好,它们都有自己为 JavaScript 添加类型魔法的方法。今天,我们将探讨如何在 React Native 应用中使用 TypeScript。

本文以 Microsoft 的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 编译器的所有设置。上面命令创建的默认设置大部分都很好,但请打开文件并取消注释以下行

{
/* Search the config file for the following line and uncomment it. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
}

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)。

如果您现在尝试运行该应用,将会收到类似 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 应用,我们需要将ts-jest添加到我们的 devDependencies

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 的形状。对于其他库,我们需要在 @types/ npm 作用域中显式安装相应的包。

例如,这里我们需要 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 # import to do this first, to ignore our files
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>
);
}
}

// styles
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',
},
});

哇!这很多,但让我们分解一下:

  • 我们没有渲染像 `div`、`span`、`h1` 等 HTML 元素,而是渲染像 `View` 和 `Button` 这样的组件。这些是可以在不同平台上工作的原生组件。
  • 样式是使用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 编写的组件库,支持 Web 上的 React 和 React Native。

祝您在更类型安全的React Native开发环境中玩得开心!

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

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

Build.com 总部位于加州奇科,是家居装修用品最大的在线零售商之一。该公司拥有强大的以 Web 为中心的业务已有 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 种产品提供 3D 模型,有 90 种饰面可供选择
  2. 增强现实 (AR) 功能,让用户能够以 98% 的尺寸精度在家中看到灯具和水龙头的外观。Build.com React Native 应用在 Apple App Store 中被推荐用于 AR 购物!AR 现在可在 Android 和 iOS 上使用!
  3. 协作项目管理功能,允许人们为项目不同阶段整理购物清单并协作选择

我们正在开发许多新的和令人兴奋的功能,这些功能将继续改善我们的应用体验,包括 AR 沉浸式购物的下一阶段。

我们的开发工作流程

Build.com 允许每位开发人员选择最适合他们的工具。

  • IDE 包括 Atom、IntelliJ、VS Code、Sublime、Eclipse 等。
  • 对于单元测试,开发人员负责为任何新组件创建 Jest 单元测试,我们正在使用 jest-coverage-ratchet 增加应用程序旧部分的覆盖率。
  • 我们使用 Jenkins 来构建我们的测试版和发布候选版本。这个过程对我们来说很有效,但仍然需要大量工作来创建发布说明和其他工件。
  • 集成测试包括一个共享的测试人员池,他们在桌面、移动和网络上工作。我们的自动化工程师正在使用 Java 和 Appium 构建我们的自动化集成测试套件。
  • 工作流程的其他部分包括详细的 eslint 配置、强制执行测试所需属性的自定义规则以及阻止违规更改的预提交钩子。

应用程序中使用的库

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 版本都会修复一些错误,但也会引入更多错误。例如,远程 JS 调试在 Android 上中断了几个月。幸运的是,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 支持输入辅助视图。

接下来的几年里,关于这个问题,已经有无数的“+1”评论、各种变通方法,以及 RN 上零星的实质性更改——直到今天。从 iOS 开始,我们正在公开一个 API 来访问原生的输入附件视图,并且我们很高兴地分享我们是如何构建它的。

背景

输入附件视图究竟是什么?阅读 Apple 的开发者文档,我们了解到它是一个自定义视图,当某个接收器成为第一响应者时,它可以固定在系统键盘的顶部。任何继承自 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,我们在创建 <TextInput> 组件时,使用 nativeID 字段来定位原生代码中的 accessory view UIView 层级结构。此函数使用底层原生文本输入的 .inputAccessoryView 属性。这样做有效地在它们的 ObjC 实现中将 <InputAccessoryView><TextInput> 链接起来。

支持粘性文本输入(场景2)增加了一些额外的限制。对于这种设计,输入附件视图有一个文本输入作为子视图,因此通过 nativeID 链接不是一个选项。相反,我们将通用屏幕外 UIView.inputAccessoryView 设置为我们的原生 <InputAccessoryView> 层次结构。通过手动告知这个通用 UIView 成为第一响应者,层次结构由响应者基础设施挂载。这个概念在前面提到的博客文章中得到了详细解释。

陷阱

当然,在构建这个API时并非一帆风顺。以下是我们遇到的一些陷阱,以及我们如何修复它们。

构建此 API 的一个初步想法是监听 NSNotificationCenter 的 UIKeyboardWill(Show/Hide/ChangeFrame) 事件。这种模式在一些开源库中以及 Facebook 应用程序的某些部分内部使用。不幸的是,UIKeyboardDidChangeFrame 事件没有及时调用来更新滑动时的 <InputAccessoryView> 帧。此外,键盘高度的变化也未被这些事件捕获。这导致了一类如下所示的错误:

在 iPhone X 上,文本和表情符号键盘的高度不同。大多数使用键盘事件来操纵文本输入帧的应用程序都必须修复上述错误。我们的解决方案是致力于使用 .inputAccessoryView 属性,这意味着响应者基础设施会处理此类帧更新。


我们遇到的另一个棘手的 bug 是如何避免 iPhone X 上的 home pill(底部横条)。你可能会想,“Apple 开发 safeAreaLayoutGuide 就是为此目的,这很简单!”我们也曾同样天真。第一个问题是,原生 <InputAccessoryView> 实现直到即将出现的那一刻都没有窗口可以锚定。没关系,我们可以重写 -(BOOL)becomeFirstResponder 并在那里强制执行布局约束。遵守这些约束会将 accessory view 向上推,但会出现另一个 bug:

输入附件视图成功避开了 home pill,但现在不安全区域后面的内容变得可见了。解决方案在于这个 radar。我将原生 <InputAccessoryView> 层级结构包装在一个不符合 safeAreaLayoutGuide 约束的容器中。原生容器覆盖了不安全区域的内容,而 <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>
);
}
}

在仓库中可以找到 Sticky Text Inputs 的另一个示例

我什么时候能使用它?

此功能实现的完整提交 在此<InputAccessoryView> 将在即将发布的 v0.55.0 版本中提供。

快乐打字 :)

将 AWS 与 React Native 结合使用

·10 分钟阅读
Richard Threlkeld
AWS 移动部门高级技术产品经理

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) 代码。目前支持的类别有:

  • 身份验证:提供凭证自动化。开箱即用的实现使用 AWS 凭证进行签名,以及来自Amazon Cognito 的 OIDC JWT 令牌。支持常见功能,例如 MFA 功能。
  • 分析:只需一行代码,即可在Amazon Pinpoint 中获取已认证或未认证用户的跟踪信息。您可以根据需要扩展此功能以自定义指标或属性。
  • API:以安全的方式提供与 RESTful API 的交互,利用AWS Signature Version 4。API 模块非常适合与Amazon API Gateway 结合使用的无服务器基础设施。
  • 存储:简化在Amazon S3 中上传、下载和列出内容的命令。您还可以轻松地按用户对数据进行分组,将其分为公共或私有内容。
  • 缓存:一个跨 Web 应用程序和 React Native 的 LRU 缓存接口,使用特定于实现的持久性。
  • i18n 和日志:提供国际化和本地化功能,以及调试和日志记录功能。

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 已禁用。这使得诸如此类安全操作的 BigInteger 计算性能较低。为了解决这个问题,我们在 Android 和 iOS 中发布了可以链接到项目中的原生桥接

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

我们也很高兴看到 Expo 团队已将其包含在他们最新的 SDK 中,以便您无需退出即可使用 Amplify。

最后,对于 React Native(和 React)开发而言,Amplify 包含高阶组件 (HOC),可以轻松地包装功能,例如用于应用程序的注册和登录。

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)}/>

您可以通过 props 控制许多组件功能,如前面所示,包括公共或私人存储选项。甚至还具有在用户与某些 UI 组件交互时自动收集分析的功能。

return <S3Album track/>

AWS Amplify 倾向于“约定优于配置”的开发风格,提供全局初始化例程或按类别级别进行初始化。最快的入门方法是使用aws-exports 文件。然而,开发人员也可以独立使用该库并结合现有资源。

要深入了解其理念并观看完整演示,请查看来自AWS re:Invent 的视频。

AWS AppSync

在 AWS Amplify 发布后不久,我们也发布了AWS AppSync。这是一个完全托管的 GraphQL 服务,同时具备离线和实时功能。尽管您可以在不同的客户端编程语言(包括原生 Android 和 iOS)中使用 GraphQL,但它在 React Native 开发人员中非常受欢迎。这是因为数据模型非常适合单向数据流和组件层次结构。

AWS AppSync 使您能够连接到您自己 AWS 账户中的资源,这意味着您拥有和控制您的数据。这通过使用数据源来实现,并且该服务支持Amazon DynamoDBAmazon ElasticsearchAWS Lambda。这使您能够将功能(例如 NoSQL 和全文搜索)组合在一个 GraphQL API 中作为模式。这使您可以混合搭配数据源。AppSync 服务还可以从模式进行预配,因此如果您不熟悉 AWS 服务,您可以编写 GraphQL SDL,点击一个按钮,即可自动启动并运行。

AWS AppSync 中的实时功能通过GraphQL 订阅和广为人知的事件驱动模式进行控制。由于 AWS AppSync 中的订阅通过模式进行控制,并使用 GraphQL 指令,而模式可以使用任何数据源,这意味着您可以从 Amazon DynamoDB 和 Amazon Elasticsearch Service 的数据库操作触发通知,或者从 AWS Lambda 的基础设施的其他部分触发通知。

与 AWS Amplify 类似,您可以使用企业级安全功能在您的 GraphQL API 上使用 AWS AppSync。该服务允许您快速开始使用 API 密钥。但是,当您过渡到生产环境时,可以切换到使用 AWS Identity and Access Management (IAM) 或 Amazon Cognito 用户池的 OIDC 令牌。您可以使用类型上的策略在解析器级别控制访问。您甚至可以使用逻辑检查来实现细粒度访问控制检查,例如检测用户是否是特定数据库资源的拥有者。还有关于检查组成员身份以执行解析器或单个数据库记录访问的功能。

为了帮助 React Native 开发人员了解更多关于这些技术的信息,AWS AppSync 控制台主页上有一个内置 GraphQL 示例模式,您可以对其进行启动。此示例会自动为您部署 GraphQL 模式、预配数据库表并连接查询、变异和订阅。还有一个功能齐全的适用于 AWS AppSync 的 React Native 示例,它利用了这个内置模式(以及一个React 示例),让您可以在几分钟内启动您的客户端和云组件。

使用 AWSAppSyncClient 时,入门非常简单,它会集成到Apollo Client 中。AWSAppSyncClient 负责处理 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 的应用加载动画

·12 分钟阅读
Eli White
Eli White
Meta 软件工程师

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

应用程序准备好后,Twitter 标志会令人愉悦地展开,露出应用程序。

我想弄清楚如何用 React Native 重现这个加载动画。


为了理解**如何**构建它,我首先必须理解加载动画的不同部分。最容易看出细微之处的方法是放慢速度。

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

  1. 鸟的缩放。
  2. 随着小鸟长大,下面的应用程序也随之显示
  3. 最后稍微缩小应用程序

我花了很长时间才弄清楚如何制作这个动画。

我最初有一个错误的假设,认为蓝色背景和 Twitter 小鸟是应用之上的一层,并且随着小鸟的变大,它会变得透明,从而显示下面的应用。这种方法行不通,因为 Twitter 小鸟变得透明会显示蓝色层,而不是下面的应用!

幸运的是,亲爱的读者,您不必经历我同样的沮丧。您将获得这个直接进入精彩部分的教程!


正确的方法

在开始编码之前,理解如何分解这个问题很重要。为了帮助您可视化这个效果,我在 CodePen 中重新创建了它(并嵌入在几段文字中),以便您可以交互式地查看不同的图层。

这种效果主要有三层。第一层是蓝色背景层。尽管这看起来出现在应用程序的顶部,但它实际上在后面。

然后我们有一个纯白层。最后,在最前面,是我们的应用程序。


这个动画的关键技巧是使用 Twitter logo 作为 mask,并用它来遮罩应用和白色图层。我不会深入介绍遮罩的细节,因为网上有 很多 资源 可以查阅

在此背景下,遮罩的基本原理是:遮罩的不透明像素显示其遮罩的内容,而透明像素则隐藏其遮罩的内容。

我们使用 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 接收 maskElementchildren 属性。子元素由 maskElement 遮罩。请注意,遮罩不需要是图像,它可以是任何任意视图。上述示例的行为是渲染蓝色视图,但只有在 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 具有特殊行为,允许您获得无卡顿的动画。Animated 有一个名为 useNativeDriver 的标志,它在动画开始时将您的动画定义从 JavaScript 发送到原生,从而允许原生端处理您的动画更新,而无需在每一帧都往返于 JavaScript。useNativeDriver 的缺点是您只能更新特定的属性集,主要是 transformopacity。您不能使用 useNativeDriver 动画背景颜色之类的东西,至少目前还不能——我们将来会添加更多,当然您也可以随时提交 PR 来支持您项目所需的属性,从而造福整个社区 😀。

由于我们希望动画流畅,我们将在此约束下工作。要更深入地了解 useNativeDriver 的工作原理,请查看我们 宣布它的博文

分解我们的动画

我们的动画有4个组成部分

  1. 放大鸟,显示应用程序和纯白层
  2. 应用程序淡入
  3. 缩小应用程序
  4. 完成后隐藏白色层和蓝色层

使用 Animated,有两种主要方式来定义动画。第一种是使用 Animated.timing,它允许您精确地指定动画将运行多长时间,以及一个缓动曲线来平滑运动。另一种方法是使用基于物理的 API,例如 Animated.spring。使用 Animated.spring,您可以指定弹簧中的摩擦力和张力等参数,并让物理引擎运行您的动画。

我们希望同时运行多个动画,它们彼此之间都密切相关。例如,我们希望应用在遮罩显示到一半时开始淡入。由于这些动画密切相关,我们将使用 Animated.timing 和一个单独的 Animated.Value

`Animated.Value` 是一个原生值的包装器,Animated 用它来了解动画的状态。通常,您希望一个完整的动画只拥有一个这样的值。大多数使用 Animated 的组件会将该值存储在状态中。

由于我将此动画视为在整个动画的不同时间点发生的步骤,因此我们将Animated.Value的起始值设为0(表示0%完成),结束值设为100(表示100%完成)。

我们的初始组件状态将如下。

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

当我们准备开始动画时,我们告诉Animated将这个值动画到100。

Animated.timing(this.state.loadingProgress, {
toValue: 100,
duration: 1000,
useNativeDriver: true, // This is important!
}).start();

然后我尝试估算动画不同部分的粗略值,以及在整个动画的不同阶段我希望它们具有的值。下面是一个动画不同部分的表格,以及我认为它们在随着时间的推移在不同点应该具有的值。

Twitter 小鸟遮罩应该从比例 1 开始,然后变小,然后猛地变大。因此,在动画进行到 10% 时,它的比例值应该为 0.8,然后猛地变大到 70。坦率地说,选择 70 是相当随意的,它需要足够大,以便小鸟完全显示屏幕,而 60 不够大 😀。不过,这部分有趣的是,数字越大,看起来它增长得越快,因为它必须在相同的时间内到达那里。这个数字经过了一些反复试验才使这个标志看起来不错。不同大小的标志/设备需要不同的最终比例,以确保整个屏幕都被显示出来。

应用程序应该保持不透明一段时间,至少在 Twitter 标志变小期间。根据官方动画,我希望在小鸟缩放过程中途开始显示它,并很快完全显示它。因此,在 15% 时我们开始显示它,在整个动画进行到 30% 时它完全可见。

应用程序的比例从1.1开始,并在动画结束时缩小到其常规比例。

现在,用代码实现。

我们上面所做的本质上是将动画进度百分比的值映射到各个部分的实际值。我们使用 Animated 的 .interpolate 来实现这一点。我们创建了 3 个不同的样式对象,每个动画部分一个,使用基于 this.state.loadingProgress 的插值值。

const loadingProgress = this.state.loadingProgress;

const opacityClearToVisible = {
opacity: loadingProgress.interpolate({
inputRange: [0, 15, 30],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
// clamp means when the input is 30-100, output should stay at 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中有一个值来判断动画是否完成,我们可以修改我们的蓝色和白色图层来使用它。

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 是在阅读 React Native 文档后,进一步学习 Animated 的绝佳资源。
  2. 实际的 Twitter 动画似乎在蒙版显示接近尾声时加速。尝试修改加载器以使用不同的缓动函数(或弹簧!)来更好地匹配该行为。
  3. 蒙版当前的结束缩放是硬编码的,可能无法在平板电脑上显示整个应用程序。根据屏幕尺寸和图像尺寸计算结束缩放将是一个很棒的 PR。

React Native 月报 #6

·阅读时间:4 分钟
Tomislav Tenodi
Speck 创始人

React Native 月度会议仍在如火如荼地进行中!请务必查看此帖子底部的下一届会议通知。

Expo

  • 恭喜 Devin AbbottHoussein Djirdeh 预发布了《全栈 React Native》一书!这本书通过构建几个小型应用程序,引导您学习 React Native。
  • 发布了 reason-react-native-scripts 的第一个(实验性)版本,以帮助人们轻松尝试 ReasonML
  • Expo SDK 24 已 发布!它使用了 React Native 0.51,并包含了一系列新功能和改进:在独立应用程序中打包图像(首次加载无需缓存!)、图像处理 API(裁剪、调整大小、旋转、翻转)、面部检测 API、新的发布渠道功能(为给定渠道设置活动版本并回滚)、用于跟踪独立应用程序构建的 Web 仪表板,以及对 OpenGL Android 实现和 Android 多任务处理程序的长期存在的 bug 的修复,仅举几例。
  • 从今年一月开始,我们将为 React Navigation 分配更多资源。我们坚信,仅使用 React 组件和 Animated 和 react-native-gesture-handler 等基元来构建 React Native 导航是可能且可取的,并且我们对我们计划的一些改进感到非常兴奋。如果您想为社区做出贡献,请查看 react-native-mapsreact-native-svg,这两个项目都需要一些帮助!

Infinite Red

Microsoft

  • 已启动一个 pull request,将核心 React Native Windows bridge 迁移到 .NET Standard,使其有效地与操作系统无关。希望许多其他 .NET Core 平台能够通过其自身的线程模型、JavaScript 运行时和 UIManagers(例如 JavaScriptCore、Xamarin.Mac、Linux Gtk# 和 Samsung Tizen 选项)来扩展 bridge。

Wix

  • Detox
    • 为了扩展 E2E 测试,我们希望最大程度地缩短 CI 上的时间,我们正在为 Detox 开发并行化支持。
    • 提交了一个 pull request 以启用对自定义 flavor 构建的支持,以便更好地支持 E2E 上的模拟。
  • DetoxInstruments
    • 开发 DetoxInstruments 的杀手级功能被证明是一项非常具有挑战性的任务,随时获取 JavaScript 回溯需要自定义 JSCore 实现来支持 JS 线程暂停。在 Wix 应用程序内部测试分析器揭示了关于 JS 线程的有趣见解。
    • 该项目目前尚不稳定,无法广泛使用,但正在积极开发中,我们希望很快宣布。
  • React Native Navigation
    • V2 的开发速度大幅提升,到目前为止,我们只有 1 名开发人员投入其 20% 的时间,现在我们有 3 名开发人员全职投入!
  • Android 性能
    • 将 RN 中捆绑的旧 JSCore 替换为最新版本(webkitGTK 项目的尖端,带有自定义 JIT 配置)使 JS 线程的性能提高了 40%。接下来是编译其 64 位版本。此工作基于 JSC build scripts for Android。在此 关注其当前状态。

下次会议

关于将这次会议用于讨论单个特定主题(例如导航、将 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 tracker 上公开。如果您想提出改进建议或提供反馈,请告知我们!

Expo

  • 发布了 Expo SDK 22(使用 React Native 0.49),并为之更新了 CRNA
    • 包括改进的启动屏幕 API、基本的 ARKit 支持、“DeviceMotion” API、iOS11 上的 SFAuthenticationSession 支持,以及 更多内容
  • 您的 snacks 现在可以包含多个 JavaScript 文件,您只需将图片和其他资源拖放到编辑器中即可上传。
  • react-navigation 贡献以增加对 iPhone X 的支持。
  • 当我们使用 Expo 构建大型应用程序时,将注意力集中在粗糙的边缘上。例如
    • 一流的多环境部署支持:staging、production 和任意 channels。Channels 将支持回滚和设置给定 channel 的活动发布版本。如果您想成为早期测试者,请告知我们,@expo_io
    • 我们还在改进我们的独立应用构建基础设施,并增加了在独立应用构建中捆绑图像和其他非代码资产的支持,同时保留了通过空中更新资产的能力。

Facebook

  • 更好的 RTL 支持
    • 我们正在引入许多方向感知的样式。
      • 位置
        • (左|右)→ (开始|结束)
      • 边距
        • 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 中,position、margin、padding 和 border 样式的“left”和“right”被交换了。在几个月内,我们将取消此行为,使“left”始终表示“左”,“right”始终表示“右”。这些重大更改隐藏在一个标志下。在您的 React Native 组件中使用I18nManager.swapLeftAndRightInRTL(false)来选择加入它们。
  • 我们正在为内部原生模块添加 Flow 类型,并使用它们生成 Java 接口和 ObjC 协议,原生实现必须实现这些接口。我们希望最快在明年开源此代码生成工具。

Infinite Red

  • 新的 OSS 工具,旨在帮助 React Native 和其他项目。更多信息 在此
  • 正在为新的 boilerplate 版本(代号:Bowser)重新设计 Ignite

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 兼 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 的支持,以及 Expo SDK 中的一系列错误修复/可靠性改进/新功能,包括视频录制、新的启动画面 API、对 react-native-gesture-handler 的支持以及改进的错误处理。
  • 关于 react-native-gesture-handlerKrzysztof Magiera(来自 Software Mansion)正在持续推进此项目,我们一直在帮助他进行测试并资助他部分开发时间。将其集成到 Expo 的 SDK21 中将使人们能够在 Snack 中轻松试用它,因此我们很期待看到大家能创造出什么。
  • 关于改进的错误日志记录/处理,请参阅 此gist 中的 Expo 内部 PR 以了解日志记录的详细信息(特别是“问题 2”),以及 此提交,它处理了导入 npm 标准库模块失败的尝试。React Native 中有很多机会可以这样改进上游的错误消息,我们将继续跟进上游 PR。社区的参与也将非常有益。
  • native.directory 持续增长,您可以从 GitHub 仓库 添加您的项目。
  • 参加北美各地的黑客马拉松,包括 PennAppsHack The NorthHackMIT,以及即将到来的 MHacks

Facebook

  • 正在努力改进 Android 上的 <Text><TextInput> 组件。(<TextInput> 的原生自动增长;深度嵌套的 <Text> 组件布局问题;更好的代码结构;性能优化)。
  • 我们仍在寻找愿意协助分类问题和拉取请求的额外贡献者。

Microsoft

  • 为 CodePush 发布了代码签名功能。React Native 开发者现在可以在 CodePush 中签名他们的应用程序包。公告可以在 此处 找到。
  • 正在努力完成 CodePush 与 Mobile Center 的集成。同时也在考虑测试/崩溃集成。

下一次会议

下一次会议定于 2017 年 10 月 10 日星期三。这是我们第四次会议,我们想了解这些笔记对 React Native 社区有多大益处。如果您对如何改进会议输出有任何建议,请随时在 Twitter 上给我留言。

React Native 月报 #3

·阅读时长5分钟
Mike Grabowski
Mike Grabowski
CTO 兼 Callstack 联合创始人

React Native 月度会议仍在继续!本月会议时间稍短,因为我们的大多数团队都在忙于发布。下个月,我们将在波兰弗罗茨瓦夫举行的 React Native EU 会议上。务必购票,并在那里与我们见面!与此同时,让我们看看我们的团队都在忙些什么。

团队

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

备忘

以下是各团队的备注

Callstack

  • 最近开源了 react-native-material-palette。它可以从图像中提取突出颜色,帮助您创建视觉上引人注目的应用程序。目前它仅支持 Android,但我们正在研究在未来添加对 iOS 的支持。
  • 我们在 haul 中实现了 HMR 支持以及许多其他很棒的功能!查看最新的发布版本。
  • 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 个左右,并且似乎会在一段时间内保持这种状态。在过去的一个月里,我们因缺乏活动(定义为过去 60 天内没有评论)而关闭了 690 个问题。在这 690 个问题中,有 58 个因各种原因重新打开(维护人员承诺提供修复,或者贡献者提出了充分的理由来保持问题开放)。
  • 我们计划在可预见的未来继续自动关闭过时的 issues。我们希望能够处理跟踪器中打开的每个有影响力的 issue,但我们还没有做到。我们需要维护者全力协助来分类 issues,并确保我们不会错过引入回归或破坏性更改的 issues,尤其是那些影响新创建项目的 issues。有兴趣帮忙的人可以使用 Facebook GitHub Bot 来分类 issues 和 pull requests。新的维护者指南包含有关分类和使用 GitHub Bot 的更多信息。请加入我们的 issue task force,并鼓励其他活跃的社区成员也这样做!

Microsoft

  • 新的 Skype 应用基于 React Native 构建,以尽可能多地在平台之间共享代码。基于 React Native 的 Skype 应用目前可在 Android 和 iOS 应用商店中获取。
  • 在构建 Skype 应用时,我们使用 React Native,并向 React Native 提交 pull requests,以解决我们遇到的 bug 和缺失的功能。到目前为止,我们已经合并了 约 70 个 pull requests
  • React Native 使我们能够使用相同的代码库为 Android 和 iOS Skype 应用提供支持。我们也希望使用该代码库为 Skype Web 应用提供支持。为了帮助我们实现这一目标,我们构建了一个名为 ReactXP 的薄层,并将其开源。ReactXP 提供了一组跨平台组件,在针对 iOS/Android 时映射到 React Native,在针对 Web 时映射到 react-dom。ReactXP 的目标与另一个名为 React Native for Web 的开源库相似。在 ReactXP FAQ 中对这些库的方法差异进行了简要描述。

Shoutem

  • 我们正继续努力,在构建使用 Shoutem 的应用程序时,改进和简化开发人员的体验。
  • 开始将我们所有的应用程序迁移到 react-navigation,但我们最终推迟了这一进程,直到更稳定的版本发布,或者其中一个原生导航解决方案变得稳定。
  • 将我们所有的 extensions 和大多数开源库(animationthemeui)更新到 React Native 0.47.1。

下一次会议

下一届会议定于 2017 年 9 月 13 日星期三举行。由于这只是我们的第三次会议,我们想知道这些笔记对 React Native 社区有多大的帮助。如果您对如何改进会议的输出有任何建议,请随时通过 Twitter 与我联系。

市场中的 React Native 性能

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

React Native 在 Facebook 家族的多个应用中都有使用,包括主 Facebook 应用中的顶层标签页。本文将重点介绍一个高度可见的产品——Marketplace。它已经在十几个国家/地区上线,让用户可以发现其他用户提供的商品和服务。

在 2017 年上半年,通过 Relay 团队、Marketplace 团队、Mobile JS Platform 团队和 React Native 团队的共同努力,我们将 Marketplace 在 Android 2010-11 年设备上的交互时间 (TTI) 缩短了一半。Facebook 过去一直将这些设备视为低端 Android 设备,它们的 TTI 在任何平台或设备类型中都是最慢的。

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

免责声明:比例不具代表性,会因 React Native 的配置和使用方式而异。

我们首先初始化 React Native 核心(即“Bridge”),然后运行特定于产品的 JavaScript,它决定了 React Native 将在原生处理时间中渲染哪些原生视图。

一种不同的方法

我们早期犯的一个错误是让 Systrace 和 CTScan 来驱动我们的性能优化工作。这些工具在 2016 年帮助我们发现了许多容易解决的问题,但我们发现 Systrace 和 CTScan **都不能代表生产场景**,也无法模拟实际情况。 breakdowns 中时间分配的比例常常是不正确的,有时甚至完全错误。极端情况下,一些我们认为只需要几毫秒的事情实际上需要数百甚至数千毫秒。尽管如此,CTScan 仍然很有用,我们发现它可以在故障进入生产环境之前捕获三分之一的回归问题。

在 Android 上,我们将这些工具的缺点归因于以下事实:1) React Native 是一个多线程框架,2) Marketplace 与 Newsfeed 和其他顶级选项卡等众多复杂视图共存,3) 计算时间差异巨大。因此,本半年,我们几乎所有的决策和优先级都由生产测量和细分来驱动。

深入生产环境检测

表面上,生产环境检测可能听起来很简单,但事实证明这是一个相当复杂的过程。它经历了多个迭代周期,每个周期持续 2-3 周;由于将提交合并到主分支、将应用程序推送到 Play 商店以及收集足够的生产样本以确保我们工作的准确性存在延迟。每个迭代周期都涉及发现我们的细分是否准确,它们是否具有正确的粒度,以及它们是否正确地加总到整个时间跨度。我们不能依赖 alpha 和 beta 版本,因为它们不代表一般用户群体。本质上,我们非常细致地构建了一个基于数百万样本聚合的非常准确的生产跟踪。

我们之所以一丝不苟地验证细分中的每一毫秒都正确地加总到它们的父指标,原因之一是我们很早就意识到我们的检测存在空白。事实证明,我们最初的细分并未考虑到线程跳转导致的停顿。线程跳转本身并不昂贵,但跳转到已经在忙碌的线程却非常昂贵。我们最终通过在正确的时间点添加 Thread.sleep() 调用来在本地重现这些阻塞,并通过以下方式成功修复了它们:

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

综合来看,消除这些线程阻塞问题将启动时间缩短了25%以上。

生产环境指标也挑战了我们之前的一些假设。例如,我们过去会预先加载许多 JavaScript 模块到启动路径中,认为将模块放在同一个 bundle 中可以降低它们的初始化成本。然而,预加载和共用这些模块的成本远远超过了其带来的好处。通过重新配置我们的内联 require 黑名单,并从启动路径中移除 JavaScript 模块,我们得以避免加载不必要的模块,例如 Relay Classic(当时只需要 Relay Modern)。今天,我们的 RUN_JS_BUNDLE breakdown 速度提高了 75% 以上。

我们还通过调查特定产品原生模块发现了胜利。例如,通过惰性注入原生模块的依赖项,我们将该原生模块的成本降低了 98%。通过消除 Marketplace 启动与其他产品之间的竞争,我们相应地缩短了启动时间。

最棒的是,其中许多改进都广泛适用于所有使用 React Native 构建的屏幕。

结论

人们通常认为 React Native 启动性能问题是由于 JavaScript 运行缓慢或网络时间过长造成的。虽然加快 JavaScript 等会使 TTI 降低一个不小的总和,但这些因素对 TTI 的贡献百分比远低于之前的估计。

到目前为止,我们学到的经验是:*测量、测量、再测量!* 一些优化是通过将运行时成本转移到构建时来实现的,例如 Relay Modern 和 Lazy NativeModules。另一些优化是通过更智能地并行化代码或移除死代码来避免不必要的工作。还有一些优化来自于 React Native 的重大架构变更,例如清理线程阻塞。性能没有万能的解决方案,长期的性能提升将来自于渐进式的检测和改进。不要让认知偏差影响你的决策。相反,仔细收集和解读生产数据来指导未来的工作。

未来计划

从长远来看,我们希望 Marketplace 的 TTI 能够与原生构建的类似产品相媲美,并且总的来说,React Native 的性能能够与原生性能持平。此外,虽然本季度我们已将 bridge 的启动成本大幅降低了约 80%,但我们计划通过 Prepack 等项目以及更多的构建时处理,将 React Native bridge 的成本降至接近零。