跳到主要内容

30 篇带有标签“engineering”的文章

查看所有标签

React Native 2018 年状况

·5 分钟阅读
Sophie Alpert
Facebook React 工程经理

自从我们上次发布关于 React Native 的状态更新以来已经有一段时间了。

在 Facebook,我们比以往任何时候都更多地使用 React Native,并将其用于许多重要项目。我们最受欢迎的产品之一是 Marketplace,它是我们应用中的顶级选项卡之一,每月有 8 亿人使用。自 2015 年创建以来,Marketplace 的所有内容都是使用 React Native 构建的,包括应用不同部分的 100 多个全屏视图。

我们还在应用的许多新部分中使用 React Native。如果您观看了上个月的 F8 主题演讲,您会认出献血、危机响应、隐私快捷方式和健康检查——所有这些都是最近使用 React Native 构建的功能。主 Facebook 应用之外的项目也在使用 React Native。新的 Oculus Go VR 头显包括 一个完全使用 React Native 构建的配套移动应用,更不用说 React VR 为头显本身中的许多体验提供动力。

当然,我们也使用许多其他技术来构建我们的应用。LithoComponentKit 是我们在应用中广泛使用的两个库;两者都为构建原生屏幕提供了类似 React 的组件 API。React Native 的目标从来不是取代所有其他技术——我们专注于使 React Native 本身变得更好,但我们很高兴看到其他团队借鉴 React Native 的想法,例如将 即时重新加载 也带到非 JavaScript 代码中。

架构

当我们在 2013 年启动 React Native 项目时,我们将其设计为在 JavaScript 和原生之间有一个单一的“桥梁”,它是异步的、可序列化的和批处理的。正如 React DOM 将 React 状态更新转换为对 DOM API(如 document.createElement(attrs).appendChild())的命令式、可变调用一样,React Native 被设计为返回一个 JSON 消息,其中列出了要执行的突变,例如 [["createView", attrs], ["manageChildren", ...]]。我们将整个系统设计为永远不依赖于获得同步响应,并确保该列表中的所有内容都可以完全序列化为 JSON 并返回。我们这样做是为了它给我们的灵活性:在此架构之上,我们能够构建诸如 Chrome 调试之类的工具,这些工具通过 WebSocket 连接异步运行所有 JavaScript 代码。

在过去的 5 年中,我们发现这些初始原则使得构建某些功能更加困难。异步桥意味着您无法将 JavaScript 逻辑与许多期望同步答案的原生 API 直接集成。批处理桥对原生调用进行排队意味着 React Native 应用更难调用以原生方式实现的函数。可序列化桥意味着不必要的复制,而不是直接在两个世界之间共享内存。对于完全在 React Native 中构建的应用,这些限制通常是可以忍受的。但是对于在 React Native 和现有应用代码之间进行复杂集成的应用,它们令人沮丧。

我们正在对 React Native 进行大规模的重新架构,以使该框架更灵活,并更好地与混合 JavaScript/原生应用中的原生基础设施集成。 通过这个项目,我们将应用我们在过去 5 年中学到的知识,并逐步将我们的架构带入更现代的架构。我们正在重写 React Native 的许多内部组件,但大多数更改都在幕后:现有的 React Native 应用将继续工作,几乎没有或没有更改。

为了使 React Native 更轻量级并更好地适应现有原生应用,这种重新架构有三个主要的内部更改。首先,我们正在更改线程模型。不再是每次 UI 更新都需要在三个不同的线程上执行工作,而是可以在任何线程上同步调用 JavaScript 以进行高优先级更新,同时仍然将低优先级工作放在主线程之外以保持响应性。其次,我们正在将 异步渲染 功能整合到 React Native 中,以允许多个渲染优先级并简化异步数据处理。最后,我们正在简化我们的桥梁,使其更快更轻量;原生和 JavaScript 之间的直接调用更有效,并将使构建跨语言堆栈跟踪等调试工具更容易。

一旦这些更改完成,更紧密的集成将成为可能。今天,如果不进行复杂的 hack,就不可能集成原生导航和手势处理或原生组件(如 UICollectionView 和 RecyclerView)。在我们更改线程模型后,构建此类功能将变得很简单。

我们将在今年晚些时候发布有关这项工作的更多详细信息,因为它即将完成。

社区

除了 Facebook 内部的社区之外,我们很高兴在 Facebook 之外拥有蓬勃发展的 React Native 用户和协作人员。我们希望更多地支持 React Native 社区,既要更好地服务 React Native 用户,也要使该项目更易于贡献。

正如我们的架构更改将帮助 React Native 与其他原生基础设施更简洁地互操作一样,React Native 在 JavaScript 方面应该更纤薄,以便更好地适应 JavaScript 生态系统,其中包括使 VM 和 bundler 可互换。我们知道重大更改的步伐可能难以跟上,因此我们希望找到减少主要版本发布的方法。最后,我们知道有些团队正在寻找更全面的文档,例如启动优化等主题,而我们在这些主题方面的专业知识尚未写下来。预计在未来一年会看到其中的一些变化。

如果您正在使用 React Native,您就是我们社区的一份子;请继续告诉我们如何才能让 React Native 对您更好。

React Native 只是移动开发人员工具箱中的一种工具,但它是我们坚信的一种工具——我们每天都在使其变得更好,在过去一年中,有来自 500 多位贡献者的 2500 多次提交。

将 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

我们将这些声明文件包保存到我们的 dev 依赖项中,因为这是一个 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',
},
});

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

  • 我们不是渲染像 divspanh1 等 HTML 元素,而是渲染像 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 编写的组件库,它同时支持 Web 上的 React 和 React Native。

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

为 React Native 构建 <InputAccessoryView>

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

动机

三年前,在 GitHub 上打开了一个问题,以支持来自 React Native 的输入附件视图。

在随后的几年中,出现了无数的“+1”、各种解决方法,以及关于此问题的 RN 的零具体更改 - 直到今天。从 iOS 开始,我们正在公开一个 API 以访问原生输入附件视图,我们很高兴分享我们是如何构建它的。

背景

输入附件视图到底是什么?阅读 Apple 的开发者文档,我们了解到它是一个自定义视图,只要接收器成为第一响应者,就可以将其锚定到系统键盘的顶部。任何从 UIResponder 继承的东西都可以将 .inputAccessoryView 属性重新声明为读写,并在此处管理自定义视图。响应者基础设施会挂载该视图,并使其与系统键盘保持同步。像拖动或点击这样关闭键盘的手势会在框架级别应用于输入附件视图。这使我们能够构建具有交互式键盘关闭功能的内容,这是 iMessage 和 WhatsApp 等顶级消息应用程序中的一个重要功能。

将视图锚定到键盘顶部有两种常见的用例。第一个是创建键盘工具栏,例如 Facebook 组件背景选择器。

在这种情况下,键盘专注于文本输入字段,输入附件视图用于提供额外的键盘功能。此功能与输入字段的类型相关。在地图应用程序中,它可以是地址建议,或者在文本编辑器中,它可以是富文本格式工具。


在这种情况下拥有 <InputAccessoryView> 的 Objective-C UIResponder 应该是明确的。<TextInput> 已成为第一响应者,在幕后,这变成了 UITextViewUITextField 的实例。

第二个常见的场景是粘性文本输入

在这里,文本输入实际上是输入附件视图本身的一部分。这通常用于消息应用程序中,在滚动浏览先前消息的线程时可以撰写消息。


在这个示例中,谁拥有 <InputAccessoryView>?它可以再次是 UITextViewUITextField 吗?文本输入在输入附件视图内部,这听起来像是一个循环依赖。仅解决这个问题本身就是 另一篇博文。剧透:所有者是一个通用的 UIView 子类,我们手动告诉它 becomeFirstResponder

API 设计

我们现在知道 <InputAccessoryView> 是什么,以及我们想如何使用它。下一步是设计一个对两种用例都有意义的 API,并且可以与现有的 React Native 组件(如 <TextInput>)很好地配合使用。

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

  1. 我们希望能够将任何通用的 React Native 视图层次结构提升到 <InputAccessoryView> 中。
  2. 我们希望这种通用的、分离的视图层次结构能够接受触摸并能够操作应用程序状态。
  3. 我们希望将 <InputAccessoryView> 链接到特定的 <TextInput>
  4. 我们希望能够跨多个文本输入共享一个 <InputAccessoryView>,而无需复制任何代码。

我们可以使用类似于 React portals 的概念来实现 #1。在这种设计中,我们将 React Native 视图传送到由响应者基础设施管理的 UIView 层次结构。由于 React Native 视图渲染为 UIView,这实际上非常简单 - 我们可以只覆盖

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

并将所有子视图管道传输到新的 UIView 层次结构。对于 #2,我们为 <InputAccessoryView> 设置一个新的 RCTTouchHandler。状态更新是通过使用常规事件回调来实现的。对于 #3 和 #4,我们在创建 <TextInput> 组件期间,使用 nativeID 字段在原生代码中定位附件视图 UIView 层次结构。此函数使用底层原生文本输入的 .inputAccessoryView 属性。这样做有效地将 <InputAccessoryView> 链接到其 ObjC 实现中的 <TextInput>

支持粘性文本输入(场景 2)添加了一些更多约束。对于此设计,输入附件视图有一个文本输入作为子项,因此通过 nativeID 链接不是一种选择。相反,我们将通用屏幕外 UIView.inputAccessoryView 设置为我们的原生 <InputAccessoryView> 层次结构。通过手动告诉此通用 UIView 成为第一响应者,层次结构由响应者基础设施挂载。前述博文中彻底解释了这一概念。

陷阱

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

构建此 API 的最初想法包括监听 NSNotificationCenter 的 UIKeyboardWill(Show/Hide/ChangeFrame) 事件。这种模式在一些开源库中以及 Facebook 应用程序的某些部分内部使用。不幸的是,UIKeyboardDidChangeFrame 事件没有及时调用以在滑动时更新 <InputAccessoryView> 框架。此外,键盘高度的变化不会被这些事件捕获。这会产生一类像这样的错误

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


我们遇到的另一个棘手的错误是避免 iPhone X 上的主页指示器。您可能会想,“Apple 开发了 safeAreaLayoutGuide 就是为了这个原因,这很简单!”。我们也同样幼稚。第一个问题是,原生 <InputAccessoryView> 实现没有要锚定的窗口,直到它即将出现的那一刻。没关系,我们可以覆盖 -(BOOL)becomeFirstResponder 并在那里强制执行布局约束。遵守这些约束会使附件视图向上移动,但会出现另一个错误:

输入附件视图成功避免了主页指示器,但现在不安全区域后面的内容是可见的。解决方案在于这个 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>
);
}
}

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

我什么时候可以使用它?

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

键盘输入愉快:)

将 AWS 与 React Native 结合使用

·9 分钟阅读
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 的实现。例如,如果您希望用户注册、登录,然后上传私人照片,您只需将 Auth 和 Storage 类别拉入您的应用程序即可

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) 代码以及电子邮件或 SMS。今天支持的类别是

  • Auth:提供凭证自动化。开箱即用的实现使用 AWS 凭证进行签名,以及来自 Amazon Cognito 的 OIDC JWT 令牌。支持常见功能,例如 MFA 功能。
  • Analytics:只需一行代码,即可在 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。这使您可以在单个 GraphQL API 中将功能(例如 NoSQL 和全文搜索)组合为一个模式。这使您可以混合和匹配数据源。AppSync 服务还可以从模式进行预置,因此如果您不熟悉 AWS 服务,您可以编写 GraphQL SDL,单击一个按钮,即可自动启动并运行。

AWS AppSync 中的实时功能通过 GraphQL 订阅(具有众所周知的、基于事件的模式)进行控制。由于 AWS AppSync 中的订阅是在模式上通过 GraphQL 指令控制,并且模式可以使用任何数据源,这意味着您可以从 Amazon DynamoDB 和 Amazon Elasticsearch Service 的数据库操作或来自基础设施其他部分的 AWS Lambda 触发通知。

以类似于 AWS Amplify 的方式,您可以在 AWS AppSync 上对您的 GraphQL API 使用 企业安全功能。该服务使您可以使用 API 密钥快速入门。但是,当您投入生产时,它可以过渡到使用 AWS Identity and Access Management (IAM) 或来自 Amazon Cognito 用户池的 OIDC 令牌。您可以使用类型上的策略在解析器级别控制访问。您甚至可以使用逻辑检查进行运行时细粒度访问控制检查,例如检测用户是否是特定数据库资源的所有者。还存在围绕检查组成员身份以执行解析器或单个数据库记录访问的功能。

为了帮助 React Native 开发人员了解有关这些技术的更多信息,AWS AppSync 控制台主页上有一个内置的 GraphQL 示例模式,您可以启动它。此示例部署 GraphQL 模式,预置数据库表,并自动为您连接查询、变更和订阅。还有一个功能齐全的 React Native 示例(用于 AWS AppSync),它利用此内置模式(以及 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 的应用加载动画

·11 分钟阅读
Eli White
Eli White
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”来自 maskElement 时才可见。我们刚刚制作了复杂的蓝色文本。

我们想要做的是渲染我们的蓝色图层,然后在顶部使用 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 徽标变小的过程中是这样。根据官方动画,我希望在小鸟正在中间向上缩放时开始显示它,并在整个动画进行到 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 预发布了“Full Stack React Native”一书!它将引导您通过构建几个小型应用程序来学习 React Native。
  • 发布了 reason-react-native-scripts 的第一个(实验性)版本,以帮助人们轻松试用 ReasonML
  • Expo SDK 24 已发布!它使用 React Native 0.51,并包含大量新功能和改进:在独立应用程序中捆绑图像(无需在首次加载时缓存!)、图像操作 API(裁剪、调整大小、旋转、翻转)、人脸检测 API、新的发布渠道功能(设置给定渠道的活动发布和回滚)、Web 仪表板以跟踪独立应用程序构建,以及修复 OpenGL Android 实现和 Android 多任务处理器的长期存在的错误,仅举几例。
  • 我们从今年 1 月开始为 React Navigation 分配更多资源。我们坚信,仅使用 React 组件和 Animated 和 react-native-gesture-handler 等基元来构建 React Native 导航是可行且可取的,并且我们对我们计划的一些改进感到非常兴奋。如果您希望为社区做出贡献,请查看 react-native-mapsreact-native-svg,这两个项目都需要一些帮助!

Infinite Red

Microsoft

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

Wix

  • Detox
    • 为了让我们能够扩展 E2E 测试,我们希望最大限度地减少在 CI 上花费的时间,我们正在开发 Detox 的并行化支持。
    • 提交了一个 pull request 以启用对自定义 flavor 构建的支持,从而更好地支持 E2E 上的模拟。
  • DetoxInstruments
    • 事实证明,DetoxInstruments 的杀手级功能是一项非常具有挑战性的任务,在任何给定时间获取 JavaScript 回溯都需要自定义 JSCore 实现来支持 JS 线程挂起。在 Wix 的应用程序上内部测试分析器揭示了关于 JS 线程的有趣见解。
    • 该项目仍然不够稳定,无法普遍使用,但正在积极开发中,我们希望很快宣布它。
  • React Native Navigation
    • V2 的开发速度已大幅提高,到目前为止,我们只有 1 名开发人员花费 20% 的时间在其上工作,而现在我们有 3 名开发人员全职在其上工作!
  • Android 性能
    • 用最新版本(webkitGTK 项目的 tip,带有自定义 JIT 配置)替换 RN 中捆绑的旧 JSCore,使 JS 线程的性能提高了 40%。下一步是编译 64 位版本。此工作基于 Android 的 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 tracker 上公开。如果您想提出改进建议或提供反馈,请告诉我们!

Expo

  • 发布了 Expo SDK 22(使用 React Native 0.49)并更新了 CRNA 以支持它。
    • 包括改进的启动画面 API、基本的 ARKit 支持、“DeviceMotion”API、iOS11 上的 SFAuthenticationSession 支持以及更多功能。
  • 你的 snack 现在可以拥有多个 JavaScript 文件,并且你可以通过拖拽的方式将图片和其他资源上传到编辑器中。
  • 贡献 react-navigation 以增加对 iPhone X 的支持。
  • 当使用 Expo 构建大型应用时,请关注我们的粗糙边缘。例如
    • 一流支持部署到多个环境:staging、production 和任意渠道。渠道将支持回滚和为给定渠道设置活动版本。如果您想成为早期测试者,请在 Twitter 上告知我们 @expo_io
    • 我们还在努力改进我们的独立应用构建基础设施,并增加对在独立应用构建中捆绑图片和其他非代码资源的支持,同时保持通过 OTA 更新资源的能力。

Facebook

  • 更好的 RTL 支持
    • 我们正在引入一些方向感知样式。
      • 位置
        • (左|右) → (开始|结束)
      • 外边距
        • margin(左|右) → margin(开始|结束)
      • 内边距
        • padding(左|右) → padding(开始|结束)
      • 边框
        • borderTop(左|右)Radius → borderTop(开始|结束)Radius
        • borderBottom(左|右)Radius → borderBottom(开始|结束)Radius
        • border(左|右)Width → border(开始|结束)Width
        • border(左|右)Color → border(开始|结束)Color
    • 在 RTL 中,位置、外边距、内边距和边框样式中,“left”和“right”的含义被交换了。在几个月内,我们将移除此行为,并使 “left” 始终表示 “left”,“right” 始终表示 “right”。 破坏性更改隐藏在一个标志下。在您的 React Native 组件中使用 I18nManager.swapLeftAndRightInRTL(false) 来选择启用它们。
  • 正在进行 Flow 类型标注我们的内部原生模块,并使用它们在 Java 中生成接口,在 ObjC 中生成原生实现必须实现的协议。我们希望这个代码生成器最早在明年开源。

Infinite Red

  • 用于帮助 React Native 和其他项目的新 OSS 工具。更多信息请点击 这里
  • 改造 Ignite 以发布新的样板代码(代号:Bowser)

Shoutem

  • 改进 Shoutem 上的开发流程。我们希望简化从创建应用到第一个自定义屏幕的过程,使其变得非常容易,从而降低新 React Native 开发人员的门槛。准备了一些研讨会来测试新功能。我们还改进了 Shoutem CLI 以支持新的流程。
  • Shoutem UI 收到了一些组件改进和错误修复。我们还检查了与最新 React Native 版本的兼容性。
  • Shoutem 平台收到了一些值得注意的更新,新的集成作为 开源扩展项目 的一部分提供。我们很高兴看到其他开发人员在 Shoutem 扩展上进行积极开发。我们积极联系并提供有关他们扩展的建议和指导。

下次会议

下次会议安排在 2017 年 12 月 6 日星期三。如果您对我们应该如何改进会议的输出有任何建议,请随时在 Twitter 上 ping 我 @TomislavTenodi

React Native 月刊 #4

·3 分钟阅读
Mike Grabowski
Mike Grabowski
首席技术官兼联合创始人 @ 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 of Software Mansion 继续推进这项工作,我们一直在帮助他进行测试并资助他的一部分开发时间。将其集成到 SDK21 的 Expo 中将使人们可以在 Snack 中轻松使用它,因此我们很高兴看到人们会创造出什么。
  • 关于改进的错误日志记录/处理 - 有关日志记录的详细信息,请参阅 此内部 Expo PR 的 gist(特别是“问题 2”),以及 此提交,了解处理导入 npm 标准库模块失败尝试的更改。在这种方式下,有很多机会改进 React Native 上游的错误消息,我们将致力于后续的上游 PR。社区参与其中也很棒。
  • native.directory 持续增长,您可以从 GitHub 仓库 添加您的项目。
  • 访问北美各地的黑客马拉松,包括 PennApps, Hack The North, HackMIT, 以及即将到来的 MHacks

Facebook

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

Microsoft

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

下次会议

下次会议安排在 2017 年 10 月 10 日星期三。由于这只是我们的第四次会议,我们想知道这些笔记如何使 React Native 社区受益。如果您对我们应该如何改进会议的输出有任何建议,请随时在 Twitter 上 ping 我 @grabbou

React Native 月刊 #3

·5 分钟阅读
Mike Grabowski
Mike Grabowski
首席技术官兼联合创始人 @ Callstack

React Native 每月例会继续进行!本月的会议时间较短,因为我们的大多数团队都忙于交付。下个月,我们将参加在波兰弗罗茨瓦夫举行的 React Native EU 大会。请务必购买门票,我们在那里与您亲自见面!与此同时,让我们看看我们的团队在忙些什么。

团队

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

笔记

以下是每个团队的笔记

Callstack

  • 最近开源了 react-native-material-palette。它从图像中提取突出颜色,以帮助您创建视觉上引人入胜的应用程序。目前仅支持 Android,但我们正在考虑将来增加对 iOS 的支持。
  • 我们已将 HMR 支持添加到 haul 以及许多其他很酷的东西!查看最新版本。
  • React Native EU 2017 即将到来!下个月将是关于 React Native 和波兰!请务必在此处购买最后剩余的门票 here

Expo

  • 发布了在 Snack 上安装 npm 包的支持。通常的 Expo 限制适用 -- 包不能依赖于 Expo 中尚未包含的自定义原生 API。我们还在努力支持 Snack 中的多个文件和上传资源。Satyajit 将在 React Native Europe 大会上谈论 Snack。
  • 发布了 SDK20,其中包含相机、支付、安全存储、磁力计、暂停/恢复 fs 下载以及改进的启动/加载屏幕。
  • 继续与 Krzysztof 合作开发 react-native-gesture-handler。请尝试一下,重建一些您之前使用 PanResponder 或原生手势识别器构建的手势,并告知我们您遇到的问题。
  • 正在试验 JSC 调试协议,并处理 Canny 上的一系列功能请求。

Facebook

  • 上个月我们讨论了 GitHub issue tracker 的管理,以及我们将尝试进行改进以解决项目的可维护性问题。
  • 目前,未解决问题的数量稳定在 600 个左右,并且似乎可能会持续一段时间。在过去一个月中,由于缺乏活动(定义为过去 60 天内没有评论),我们关闭了 690 个问题。在这些 690 个问题中,有 58 个因各种原因重新打开(维护者承诺提供修复,或者贡献者为保持问题打开提出了充分的理由)。
  • 我们计划在可预见的将来继续自动关闭过时的问题。我们希望达到这样一种状态:跟踪器中打开的每个有影响的问题都能得到处理,但我们尚未达到目标。我们需要维护者尽可能多的帮助来分类问题,并确保我们不会错过引入回归或引入破坏性更改的问题,尤其是那些影响新创建项目的问题。有兴趣提供帮助的人可以使用 Facebook GitHub Bot 来分类问题和拉取请求。新的维护者指南包含有关分类和使用 GitHub Bot 的更多信息。请将自己添加到 issue task force 并鼓励其他活跃的社区成员也这样做!

Microsoft

  • 新的 Skype 应用程序构建在 React Native 之上,以便尽可能地在平台之间共享代码。基于 React Native 的 Skype 应用程序目前已在 Android 和 iOS 应用商店中提供。
  • 在基于 React Native 构建 Skype 应用程序时,我们会向 React Native 发送拉取请求,以解决我们遇到的错误和缺少的功能。到目前为止,我们已经合并了大约 70 个拉取请求
  • React Native 使我们能够从同一代码库为 Android 和 iOS Skype 应用程序提供支持。我们还希望使用该代码库为 Skype Web 应用程序提供支持。为了帮助我们实现该目标,我们在 React/React Native 之上构建并开源了一个名为 ReactXP 的薄层。ReactXP 提供了一组跨平台组件,这些组件在以 iOS/Android 为目标时映射到 React Native,在以 Web 为目标时映射到 react-dom。ReactXP 的目标与另一个名为 React Native for Web 的开源库类似。ReactXP FAQ 中简要描述了这些库的方法有何不同。

Shoutem

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

下次会议

下次会议安排在 2017 年 9 月 13 日星期三。由于这只是我们的第三次会议,我们想知道这些笔记如何使 React Native 社区受益。如果您对我们应该如何改进会议的输出有任何建议,请随时在 Twitter 上 ping 我 @grabbou

React Native 在 Marketplace 中的性能

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

React Native 在 Facebook 系列的多个应用程序中的多个位置使用,包括主要 Facebook 应用程序中的顶级选项卡。我们这篇文章的重点是一个高度可见的产品,Marketplace。它在十几个国家/地区可用,使用户能够发现其他用户提供的产品和服务。

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

一个典型的 React Native 启动过程大致如下

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

我们首先初始化 React Native 核心(又名“Bridge”),然后再运行产品特定的 JavaScript,该 JavaScript 确定 React Native 将在 Native Processing Time 中渲染哪些原生视图。

一种不同的方法

我们早期犯的一个错误是让 Systrace 和 CTScan 驱动我们的性能工作。这些工具帮助我们在 2016 年找到了很多唾手可得的成果,但我们发现 Systrace 和 CTScan 都不能代表生产场景,也无法模拟野外发生的情况。细分中花费时间的比率通常是不正确的,有时甚至会严重偏离实际。极端情况下,我们期望花费几毫秒的一些事情实际上需要数百甚至数千毫秒。即便如此,CTScan 仍然很有用,我们发现它可以捕获三分之一的回归错误,然后再投入生产。

在 Android 上,我们将这些工具的缺点归因于以下事实:1) React Native 是一个多线程框架,2) Marketplace 与大量复杂视图(例如 Newsfeed 和其他顶级选项卡)位于同一位置,以及 3) 计算时间变化很大。因此,在下半年,我们让生产测量和细分驱动了我们几乎所有的决策和优先级排序。

生产仪表化的道路

生产仪表化表面上听起来很简单,但事实证明这是一个相当复杂的过程。它花费了多个迭代周期,每个周期为 2-3 周;由于将提交登陆到 master、将应用程序推送到 Play 商店以及收集足够的生产样本以对我们的工作充满信心都存在延迟。每个迭代周期都涉及发现我们的细分是否准确、是否具有正确的粒度级别以及它们是否正确地加起来整个时间跨度。我们不能依赖 alpha 和 beta 版本,因为它们不能代表一般人群。本质上,我们非常繁琐地基于数百万个样本的聚合构建了一个非常准确的生产跟踪。

我们仔细验证细分中的每一毫秒是否正确地加到其父指标的原因之一是,我们很早就意识到我们的仪表化存在差距。事实证明,我们最初的细分没有考虑到线程跳转造成的停顿。线程跳转本身并不昂贵,但是跳转到已经忙于工作的繁忙线程非常昂贵。我们最终通过在正确的时刻散布 Thread.sleep() 调用在本地重现了这些阻塞,并通过以下方式设法修复了它们

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

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

生产指标也挑战了我们之前的一些假设。例如,我们过去常常在启动路径上预加载许多 JavaScript 模块,假设将模块共置在一个捆绑包中会降低它们的初始化成本。然而,预加载和共置这些模块的成本远远超过了收益。通过重新配置我们的内联 require 黑名单并从启动路径中删除 JavaScript 模块,我们能够避免加载不必要的模块,例如 Relay Classic(当只需要 Relay Modern 时)。今天,我们的 RUN_JS_BUNDLE 细分速度提高了 75% 以上。

我们还通过调查特定于产品的原生模块找到了优势。例如,通过延迟注入原生模块的依赖项,我们将该原生模块的成本降低了 98%。通过消除 Marketplace 启动与其他产品的争用,我们将启动时间缩短了相同的间隔。

最好的部分是,这些改进中的许多都广泛适用于使用 React Native 构建的所有屏幕。

结论

人们认为 React Native 启动性能问题是由 JavaScript 缓慢或网络时间过长引起的。虽然加速 JavaScript 之类的东西会使 TTI 降低一个可观的量,但与之前认为的相比,这些因素对 TTI 的贡献百分比要小得多。

到目前为止的教训是:测量,测量,再测量! 一些优势来自于将运行时成本转移到构建时,例如 Relay Modern 和 Lazy NativeModules。其他优势来自于通过更智能地并行化代码或删除死代码来避免工作。还有一些优势来自于 React Native 的大型架构更改,例如清理线程阻塞。没有解决性能的宏大解决方案,长期性能的提升将来自于增量仪表化和改进。不要让认知偏差影响您的决定。相反,请仔细收集和解释生产数据以指导未来的工作。

未来计划

从长远来看,我们希望 Marketplace TTI 与使用 Native 构建的类似产品相媲美,并且总的来说,使 React Native 性能与原生性能相当。此外,尽管下半年我们将 bridge 启动成本大幅降低了约 80%,但我们计划通过 Prepack 等项目和更多的构建时处理,将 React Native bridge 的成本降至接近于零。