跳至主内容

在 React Native 中使用 TypeScript

· 1 分钟阅读
Ash Furrow
Artsy 软件工程师
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

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 编译器的所有设置。上述命令创建的默认配置基本可用,但请打开文件并取消注释以下行:

{
/* 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-jestdevDependencies

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

哇!内容有点多,我们来分解说明:

  • 我们不再渲染 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 打造 - Build.com 应用

· 1 分钟阅读
Garrett McCullough
高级移动开发工程师
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Build.com总部位于加利福尼亚州奇科市,是家装领域规模最大的在线零售商之一。团队18年来始终专注于网页业务,并于2015年开始规划移动应用。由于团队规模有限且原生开发经验不足,同时构建Android和iOS双平台应用并不现实。因此我们决定冒险尝试当时全新的React Native框架——2015年8月12日我们首次提交代码,使用的正是React Native v0.8.0!2016年10月15日,应用正式登陆两大应用商店。过去两年间,我们持续升级并扩展应用功能,目前运行的是React Native v0.53.0版本。

您可通过https://www.build.com/app体验这款应用。

功能亮点

我们的应用功能完备,包含电商应用应有的所有特性:产品展示、搜索排序、复杂产品配置、收藏夹等。支持标准信用卡支付,同时为iOS用户提供PayPal和Apple Pay选项。

以下特色功能可能超出您的预期:

  1. 约40款产品提供90种表面处理的3D模型展示

  2. 增强现实(AR)功能让用户以98%的尺寸精度预览灯具/水龙头在家中的实际效果。Build.com的React Native应用因此入选苹果应用商店AR购物推荐!现已在Android/iOS双平台开放AR功能!

  3. 协作式项目管理功能,支持用户为项目不同阶段创建采购清单并协同筛选商品

我们正在开发多项激动人心的新功能以持续优化应用体验,包括AR沉浸式购物的下一阶段升级。

开发工作流

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

  • 集成开发环境包括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引入静态类型检查,并探索可选链式调用(optional chaining)特性。这些功能有助于解决以下两类常见问题:

  • 数据类型错误

  • 因对象属性缺失导致的未定义数据

开源贡献

由于我们深度依赖开源技术,团队致力于回馈社区。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 版本在修复部分问题的同时总会引入新问题。例如 Android 平台的远程 JS 调试功能曾失效数月之久。值得欣慰的是,2017 年后平台稳定性大幅提升。

导航库

导航库的选择始终是我们面临的主要挑战。我们曾长期使用 Expo 的 ex-nav 库,效果良好但最终停止维护。由于当时正处于密集功能开发阶段,我们不得不分叉该库并自行添加对 React 16 和 iPhone X 的支持。最终我们成功迁移到 react-native-navigation,期望它能获得长期维护。

桥接模块

另一大挑战来自桥接模块。项目初期许多关键桥接功能缺失,例如团队成员开发了 react-native-contact-picker 来实现 Android 通讯录选择功能。同时 React Native 的版本升级常导致桥接模块失效,例如 v40 升级时我们不得不主动提交 PR 修复了 3-4 个未更新的库。

未来展望

随着 React Native 持续发展,我们对社区有以下期待:

  • 增强导航库的稳定性与功能

  • 保持 React Native 生态库的兼容维护

  • 优化原生库和桥接模块的集成体验

React Native 社区中的企业和个人始终积极贡献时间精力改进共享工具。若您尚未参与开源贡献,不妨尝试改进所用库的代码或文档。相关入门指南非常丰富,实践难度可能远低于您的预期!

为 React Native 构建 <InputAccessoryView>

· 1 分钟阅读
Peter Argany
Facebook 软件工程师
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

背景动机

三年前,有人提交了一个 GitHub issue,要求 React Native 支持 input accessory view。

在随后的几年里,这个问题收到了无数的 "+1" 和各种变通方案,但 RN 本身却没有任何实质改变——直到今天。我们从 iOS 平台开始,公开了访问原生 input accessory view 的接口,并很高兴分享我们的实现方案。

背景知识

究竟什么是 input accessory view?阅读 Apple 开发者文档可知,它是一个自定义视图,当接收者成为第一响应者时,可以固定在系统键盘顶部。任何继承自 UIResponder 的对象都可重新声明 .inputAccessoryView 属性,并在此管理自定义视图。响应者基础架构会挂载该视图,并使其与系统键盘保持同步。用于关闭键盘的手势(如拖动或点击)会在框架层应用于 input accessory view。这让我们能实现交互式键盘收起功能——这是 iMessage 和 WhatsApp 等顶级通讯应用的核心特性。

将视图固定在键盘顶部有两种常见场景:第一种是创建键盘工具栏,例如 Facebook 发帖编辑器的背景选择器。

在此场景中,键盘聚焦于文本输入框,input accessory view 用于提供额外的键盘功能。这些功能与输入框类型相关:地图应用中可能是地址建议,文本编辑器中则可能是富文本格式工具。


在此场景中,拥有 <InputAccessoryView> 的 Objective-C UIResponder 很明确:当 <TextInput> 成为第一响应者时,底层会变成 UITextViewUITextField 实例。

第二种常见场景是粘性文本输入框:

此时文本输入框本身是 input accessory view 的组成部分。这常见于消息应用,用户可在滚动查看历史消息的同时编写新消息。


此例中谁拥有 <InputAccessoryView>?还能是 UITextViewUITextField 吗?文本输入框竟_位于_ input accessory view 内部,这形成了循环依赖。单独解决这个问题就值得另写一篇博客。剧透:拥有者是一个通用的 UIView 子类,我们会手动触发它的 becomeFirstResponder 方法。

接口设计

现在我们知道 <InputAccessoryView> 是什么以及如何使用它。下一步是设计一个同时满足两种场景的接口,并与现有 React Native 组件(如 <TextInput>)良好协作。

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

  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 字段在原生代码中定位辅助视图的 UIView 层次结构。该函数利用底层原生文本输入的 .inputAccessoryView 属性,从而在 ObjC 实现中有效地将 <InputAccessoryView><TextInput> 关联起来。

支持粘性文本输入(场景二)带来额外约束。这种设计中输入辅助视图本身包含文本输入子元素,因此无法通过 nativeID 关联。我们改为将通用离屏 UIView.inputAccessoryView 属性设置为我们原生的 <InputAccessoryView> 层次结构。通过手动使该通用 UIView 成为第一响应者,响应者基础设施便会挂载该层次结构。此概念在前述博客文章中有详细阐述。

陷阱与解决方案

当然,在构建此 API 的过程中并非一帆风顺。以下是我们遇到的一些陷阱及其解决方案。

最初的构建方案涉及监听 NSNotificationCenter 的 UIKeyboardWill(Show/Hide/ChangeFrame) 事件。该模式在某些开源库和 Facebook 应用内部模块中使用。遗憾的是,在滑动操作时,UIKeyboardDidChangeFrame 事件未能及时触发以更新 <InputAccessoryView> 的帧位置。此外,键盘高度变化也不会被这些事件捕获,导致如下缺陷:

在 iPhone X 上,文本键盘与表情键盘高度不同。依赖键盘事件操控文本输入框的应用都需修复此缺陷。我们的解决方案是坚持使用 .inputAccessoryView 属性,这意味着响应者基础设施会处理此类帧更新。


另一个棘手问题是避免与 iPhone X 的 Home 指示条重叠。您可能认为:"苹果为此专门开发了 safeAreaLayoutGuide,这很简单!" 我们最初也如此天真。首要问题是原生 <InputAccessoryView> 实现在即将显示前没有可锚定的窗口。虽然可通过重写 -(BOOL)becomeFirstResponder 强制执行布局约束,但遵循这些约束会将辅助视图上移后,新问题又会出现:

输入辅助视图成功避开 Home 指示条后,不安全区域后的内容却变得可见。解决方案来自这个 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 版本中提供。

祝键盘操作愉快 :)

在 React Native 中使用 AWS

· 1 分钟阅读
Richard Threlkeld
AWS Mobile 高级技术产品经理
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

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:通过 AWS 签名版本 4 安全地与 RESTful API 交互。该模块在配合 Amazon API Gateway 的无服务器架构中表现优异。

  • Storage:简化了 Amazon S3 内容的上传、下载和列表操作。还可轻松按用户分组公开或私有数据。

  • Caching:跨 Web 应用和 React Native 的 LRU 缓存接口,采用特定实现的持久化方案。

  • i18n and 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用户池处理用户注册和登录。该服务采用安全远程密码协议(SRP)来保护用户认证过程。如果您有兴趣研究该协议的数学原理,会注意到在原始根上计算密码验证器生成群组时必须使用大质数。在React Native环境中JIT被禁用,导致此类安全计算中的BigInteger运算性能较低。为此我们发布了Android和iOS原生桥接模块,可直接集成到项目中:

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

令人振奋的是,Expo团队已将此功能纳入其最新SDK,您现在无需eject项目即可使用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 订阅模式实现。由于订阅功能使用 GraphQL 指令在架构层进行控制,而架构可使用任意数据源,这意味着您可以通过 Amazon DynamoDB 和 Amazon Elasticsearch Service 的数据库操作触发通知,或通过 AWS Lambda 从基础设施其他部分触发。

与 AWS Amplify 类似,您可以在 AWS AppSync 的 GraphQL API 中使用企业级安全功能。该服务支持通过 API 密钥快速上手,但在生产环境中可无缝切换到 AWS IAM 或 Amazon Cognito 用户池的 OIDC 令牌。您可以在解析器层级通过类型策略控制访问权限,甚至能在运行时执行细粒度访问控制检查(例如验证用户是否为特定数据库资源所有者),还支持通过群组成员检查来执行解析器或数据库记录访问。

为帮助 React Native 开发者快速上手,AWS AppSync 控制台主页提供内置 GraphQL 示例架构。该示例会自动部署 GraphQL 架构、配置数据库表并连接查询/变更/订阅操作。我们还提供了可直接运行的 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 应用加载动画

· 1 分钟阅读
Eli White
Eli White
Software Engineer @ Meta
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Twitter iOS 应用的加载动画让我非常喜欢。

当应用准备就绪时,Twitter 小鸟标志会优雅地展开,显露出应用界面。

我想探索如何在 React Native 中重现这个加载动画。


要理解构建方法,首先需要拆解加载动画的各个组成部分。观察细节最简单的方法是放慢速度观看。

这个动画包含几个关键部分需要我们实现:

  1. 小鸟标志的缩放

  2. 小鸟扩展时显示下方应用界面

  3. 最后阶段轻微缩小应用界面

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

最初我有个错误假设:认为蓝色背景和小鸟是覆盖在应用上方的图层,随着小鸟放大逐渐变得透明从而显示下方应用。这种方法行不通,因为小鸟变透明后露出的会是蓝色背景,而不是应用界面!

幸运的是,亲爱的读者,您不必经历同样的挫折。本教程将带您直达关键实现部分!


正确的实现方式

在编写代码前,理解如何拆解这个效果很重要。为帮助可视化,我在 CodePen 上重建了这个效果(嵌入在下方段落中),您可以交互式查看不同图层。

这个效果包含三个主要图层:最底层是蓝色背景层。虽然它看起来像是在应用上方,实际位于最后方。

中间是纯白色图层。最前面则是我们的应用层。


这个动画的核心技巧是将 Twitter 标志作为 mask(遮罩),同时遮盖应用层和白色层。关于遮罩细节我不做深入探讨,网上有大量相关资源

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

我们将 Twitter 标志作为遮罩,用它遮盖两个图层:纯白色层和应用层。

通过将遮罩放大到超过整个屏幕的尺寸来显示应用界面。

在遮罩放大过程中,我们逐渐增加应用层的不透明度,从而显示应用界面并隐藏后方的纯白层。最后阶段,我们将应用层从大于1的初始比例缩小至1。动画结束后隐藏非应用图层。

常言道一图胜千言。那么交互式可视化值多少词呢?点击"Next Step"按钮逐步查看动画效果。图层展示提供了侧视角度,网格线帮助可视化透明图层。

现在进入 React Native 实现

好的。既然我们理解了构建目标和动画原理,现在可以进入代码环节——这才是您阅读的真正目的。

实现的关键组件是 MaskedViewIOS,这是 React Native 的核心组件。

import {MaskedViewIOS} from 'react-native';

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

MaskedViewIOS 接收 maskElementchildren 两个 props。子元素会被 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 提供了特殊机制避免卡顿:通过设置 useNativeDriver 标志,可以在动画开始时将定义从 JavaScript 发送到原生层,让原生端独立处理动画更新,无需每帧与 JavaScript 通信。useNativeDriver 的限制是只能修改特定属性(主要是 transformopacity),目前无法用 useNativeDriver 实现背景色等属性的动画。当然,未来我们会扩展支持范围,也欢迎提交 PR 来增加项目所需属性,让整个社区受益 😀。

为确保动画流畅,我们将在这些约束条件下工作。深入了解 useNativeDriver 底层原理,请参阅相关博客文章

分解动画步骤

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

  1. 放大小鸟图标,逐渐显示应用和纯白层

  2. 淡入应用界面

  3. 轻微缩小应用界面

  4. 完成后隐藏白色层和蓝色层

Animated 提供两种主要动画定义方式:Animated.timing 可精确控制时长和缓动曲线;Animated.spring 则基于物理模型。使用 Animated.spring 时,你可以通过摩擦力和张力参数实现弹性效果。

由于多个动画需要同步进行(例如在遮罩展开中途开始淡入应用),且它们彼此关联,我们将使用 Animated.timing 配合单一 Animated.Value 实现。

Animated.Value 是封装动画状态值的容器。通常整个动画只需一个该值,大多数组件会将其存储在 state 中。

我们将动画视为随时间推进的连续过程:设置 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显然不够😀。有趣的是,这个最终值越大,缩放速度看起来就越快,因为必须在相同时间内达到更大倍数。经过反复调试才找到适合当前Logo的数值。不同尺寸的Logo/设备需要不同的最终缩放值,以确保完整显示屏幕内容。

应用界面需要保持不透明状态一段时间,至少要覆盖Logo缩小阶段。根据官方动画效果,我计划在小鸟放大到一半时开始显示应用界面,并快速完成过渡。因此在动画进度15%时开始显现,到30%时实现完全可见。

应用界面初始缩放值为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 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是学习Animated的绝佳资源,尤其适合已掌握React Native基础文档的开发者

  2. 实际Twitter动画在最后阶段会加速遮罩展开过程。尝试修改加载器,使用不同的缓动函数(或弹性动画)来模拟这种效果

  3. 当前遮罩的最终缩放值是硬编码的,在平板设备上可能无法完整显示内容。根据屏幕尺寸和图像大小动态计算最终缩放值会是很棒的PR改进

React Native 月刊 #6

· 1 分钟阅读
Tomislav Tenodi
Speck 创始人
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

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、新版发布渠道功能(设置指定渠道的活跃版本并支持回滚)、用于追踪独立应用构建的网页仪表盘,以及修复了 OpenGL Android 实现与 Android 多任务处理的长期 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# 和 Samsung Tizen 等方案)。

Wix

  • Detox

    • 为了扩大端到端测试规模,我们正在努力减少 CI 时间消耗,目前正在为 Detox 开发并行化支持功能。
    • 已提交拉取请求,支持自定义构建变体(flavor builds),以优化端到端测试的模拟环境。
  • DetoxInstruments

    • DetoxInstruments 的核心功能开发面临重大技术挑战:实现任意时刻 JavaScript 堆栈追踪需要定制 JSCore 以支持 JS 线程挂起机制。在 Wix 应用内部测试分析器时,我们获得了关于 JS 线程的宝贵洞察。
    • 该项目目前尚未达到稳定可用状态,但团队正积极投入研发,期待尽快发布正式公告。
  • React Native Navigation

    • V2 版本开发进度显著加速:此前仅由 1 名开发者投入 20% 时间维护,现已有 3 名开发者全职投入研发!
  • Android 性能优化

    • 将 React Native 内置的旧版 JSCore 替换为最新版本(基于 webkitGTK 项目尖端版本,采用定制化 JIT 配置),使 JS 线程性能提升 40%。下一步将编译其 64 位版本。该工作基于JSC Android 构建脚本,可通过此链接追踪当前进展。

后续会议安排

我们正讨论调整会议形式:未来可能聚焦单一特定主题(例如导航架构、React Native 模块独立仓库迁移、文档优化等)。这种方式有望最大化对 React Native 社区的贡献。该调整可能于下次会议实施,欢迎通过推特留言提出您希望探讨的主题。

React Native 月报 #5

· 1 分钟阅读
Tomislav Tenodi
Speck 创始人
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

新一期 React Native 月度会议来了!让我们看看各个团队的最新进展。

Callstack

  • 我们持续优化 React Native 的持续集成系统。最重要的进展是将 CI 系统从 Travis 迁移到 CircleCI,为 React Native 建立了统一高效的 CI 流水线。

  • 我们举办了 Hacktoberfest - React Native 特别版活动,与参与者共同向开源项目提交了大量 Pull Request。

  • 我们持续推进 Haul 项目。上月发布的两个新版本已支持 webpack 3。未来计划增加 CRNAExpo 支持,并优化热模块替换功能。路线图已在 issue 跟踪器公开。欢迎提出改进建议或反馈意见!

Expo

  • 发布 Expo SDK 22(基于 React Native 0.49)并同步更新 CRNA

    • 包含改进的启动屏 API、基础 ARKit 支持、"DeviceMotion" API、iOS11 的 SFAuthenticationSession 支持等新特性
  • Snack 编辑器现支持多 JavaScript 文件,并可通过拖拽直接上传图片等资源

  • react-navigation 贡献代码,新增 iPhone X 支持

  • 重点优化大型 Expo 应用的开发体验:

    • 提供多环境部署支持:预发布环境、生产环境及自定义渠道。渠道将支持版本回滚和动态切换。欢迎通过 @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" 的原有含义将被逐步废弃。未来几个月将取消交换逻辑,确保 "left" 始终代表左侧,"right" 始终代表右侧。开发者可通过 I18nManager.swapLeftAndRightInRTL(false) 提前启用此变更
  • 我们正在为内部原生模块添加 Flow 类型注解,并利用这些类型生成 Java 接口和 ObjC 协议,原生实现必须遵循这些规范。我们希望该代码生成工具最早能在明年开源。

Infinite Red

  • 发布了新的开源工具,用于辅助 React Native 及其他项目开发。详情请见此处

  • 正在重构 Ignite,准备发布新版脚手架(代号:Bowser)

Shoutem

  • 优化 Shoutem 平台的开发流程。我们致力于简化从创建应用到实现首个自定义屏幕的全过程,大幅降低 React Native 新手的入门门槛。通过多次工作坊测试了新功能,同时改进了 Shoutem CLI 以支持新流程。

  • Shoutem UI 进行了多项组件优化和错误修复,并验证了与最新 React Native 版本的兼容性。

  • Shoutem 平台迎来多项重要更新,新集成功能已作为开源扩展项目开放使用。看到其他开发者积极参与扩展开发令人振奋,我们主动联系并提供技术指导支持。

下一次会议

下次会议定于 2017年12月6日(星期三)举行。如果您对会议成果改进有任何建议,欢迎随时在 Twitter 上联系我

React Native 月度动态 #4

· 1 分钟阅读
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

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-handler:来自 Software MansionKrzysztof Magiera 持续推动该项目进展,我们协助进行了测试并资助了部分开发工作。SDK21 集成该库后,开发者可通过 Snack 轻松体验,期待看到大家的创意应用。

  • 关于错误日志/处理机制优化:日志改进细节请参考 Expo 内部 PR 摘要(重点关注"问题 2"),处理 npm 标准库模块导入失败的变更详见 此提交记录。React Native 上游仍有大量优化错误提示的空间,我们将跟进相关 PR,也欢迎社区参与贡献。

  • native.directory 持续扩展中,可通过 GitHub 仓库提交您的项目。

  • 正参与北美多地黑客马拉松活动,包括 PennAppsHack The NorthHackMIT,即将亮相 MHacks

Facebook

  • 正在优化 Android 平台的 <Text><TextInput> 组件(包括 <TextInput> 原生自适应高度、深层嵌套 <Text> 布局问题、代码结构优化及性能提升)。

  • 持续招募协助分类 issues 和处理 pull requests 的贡献者。

Microsoft

  • 发布了 CodePush 的代码签名功能。React Native 开发者现在可以为 CodePush 中的应用包进行签名。相关公告可在此查看

  • 正在完成 CodePush 与 Mobile Center 的集成工作,同时考虑整合测试/崩溃报告功能

下一次会议

下次会议定于 2017 年 10 月 10 日星期三举行。由于目前仅召开过四次会议,我们非常希望了解这些会议记录对 React Native 社区的价值。如果您对如何改进会议产出有任何建议,欢迎在 Twitter 上联系我

React Native 月报 #3

· 1 分钟阅读
Mike Grabowski
Mike Grabowski
CTO and Co-Founder @ Callstack
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

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)。我们正着手实现多文件支持和资源上传功能。Satyajit 将在 React Native Europe 大会分享 Snack 相关技术。

  • 发布 SDK20 版本,新增相机、支付、安全存储、磁力计功能,支持文件下载暂停/恢复,并优化了启动/加载界面。

  • 正与 Krzysztof 持续合作开发 react-native-gesture-handler。诚邀各位尝试用其重构 PanResponder 或原生手势识别器实现的手势交互,并反馈遇到的问题。

  • 探索 JSC 调试协议实现,同时处理 Canny 平台上的多项功能需求。

Facebook

  • 上月我们讨论了 GitHub issue 跟踪管理机制,计划通过改进提升项目的可维护性。

  • 目前未解决问题数量稳定维持在 600 个左右,短期内可能保持该态势。过去一月我们关闭了 690 个长期无动态的 issue(定义为最近 60 天无任何评论)。其中 58 个被重新开启,原因包括:维护者承诺提供修复方案,或贡献者提出充分理由需保持 issue 开放。

  • 我们计划在可预见的未来继续推进自动化关闭陈旧 issue 的工作。理想状态下,我们希望追踪系统中每个有影响力的 issue 都能得到处理,但目前尚未达成这一目标。我们需要维护者们全力协助进行 issue 分类,确保不会遗漏那些导致回归问题或引入破坏性变更的 issue,特别是影响新建项目的 issue。有意协助的开发者可使用 Facebook GitHub Bot 参与 issue 和 PR 的分类工作。新版《维护者指南》包含了分类流程和 GitHub Bot 使用的详细信息。请将您添加至 issue 任务组,并鼓励其他活跃社区成员共同参与!

Microsoft

  • 新版 Skype 应用基于 React Native 构建,以实现跨平台最大程度的代码共享。这款采用 React Native 的 Skype 应用现已登陆 Android 和 iOS 应用商店。

  • 在基于 React Native 构建 Skype 应用的过程中,我们通过提交 PR 来修复遇到的缺陷和补充缺失功能。截至目前,我们已有 约 70 个 PR 被合并

  • React Native 让我们能够用同一套代码库驱动 Android 和 iOS 版 Skype 应用。我们还希望用该代码库支持 Skype 网页应用。为此我们构建并开源了位于 React/React Native 上层的轻量级框架 ReactXP。ReactXP 提供了一套跨平台组件:针对 iOS/Android 平台映射到 React Native,针对网页则映射到 react-dom。其目标与另一个开源库 React Native for Web 相似。关于两者实现差异的简要说明可参阅 ReactXP 常见问题

Shoutem

  • 我们持续致力于优化和简化使用 Shoutem 构建应用的开发者体验。

  • 已启动将所有应用迁移至 react-navigation 的工作,但因等待更稳定版本发布而暂缓推进,后续将评估原生导航方案的稳定性。

  • 正在将所有扩展组件及多数开源库(animation, theme, ui)升级至 React Native 0.47.1。

下一次会议

下次会议定于 2017 年 9 月 13 日星期三举行。由于目前仅举办了三期会议,我们想了解这些会议纪要对 React Native 社区的价值。如果您对会议产出形式有任何改进建议,欢迎通过 Twitter 与我联系。

Marketplace 中的 React Native 性能优化

· 1 分钟阅读
Facebook 软件工程师
非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

React Native 在 Facebook 生态的多个应用中均有使用,包括主应用中的顶层标签页。本文重点讨论高曝光产品 Marketplace。该服务已在十余个国家上线,让用户能发现其他用户提供的商品和服务。

2017 年上半年,通过 Relay 团队、Marketplace 团队、移动 JS 平台团队和 React Native 团队的协作,我们将 Android 2010-11 年款设备上的 Marketplace 交互就绪时间(TTI)缩短了一半。Facebook 历来将这些设备视为低端 Android 设备,它们在所有平台和设备类型中具有最长的 TTI。

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

免责声明:图示比例仅供参考,实际比例会因 React Native 的配置和使用方式而异。

我们首先初始化 React Native 核心(即 "Bridge"),然后运行产品特定的 JavaScript 代码,这些代码决定了 React Native 将在原生处理阶段渲染哪些视图。

另辟蹊径

我们早期的一个错误是过度依赖 Systrace 和 CTScan 来驱动性能优化。2016 年这些工具帮助我们发现了许多容易解决的问题,但我们后来认识到 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 模块能降低初始化成本,因此将多个模块集中打包。然而实际证明,预加载和集中这些模块的成本远超收益。通过重新配置内联请求黑名单并从启动路径移除 JavaScript 模块,我们成功避免了加载非必要模块(例如在只需使用 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 整体性能与原生方案持平。尽管过去半年我们将 React Native 桥接的启动成本降低了约 80%,但我们将通过 Prepack 等项目及更多构建时处理技术,最终将桥接成本降至趋近于零。