跳到主要内容

33 篇关于“工程”的文章

查看所有标签

为 iOS 15 和 Android 12 准备您的应用

·阅读时间:4 分钟
Samuel Susla
Samuel Susla
Meta 软件工程师

大家好!

随着今年晚些时候新的移动操作系统版本发布,我们建议您提前准备好您的 React Native 应用,以避免在这些版本普遍可用时出现问题。

引入新的 iOS WebViews

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

长期以来,Apple 一直不鼓励使用 UIWebView,转而推荐 WKWebView。在即将在未来几个月发布的 iOS 12 中,UIWebView 将被正式弃用。React Native 的 iOS WebView 实现严重依赖 UIWebView 类。因此,鉴于这些发展,我们为使用 WKWebView 的 WebView React Native 组件构建了一个新的原生 iOS 后端。

这些更改的最后部分已合并到此提交,并且将在 0.57 版本中提供。

要选择加入此新实现,请使用 useWebKit 属性

<WebView
useWebKit={true}
source={{url: 'https://www.google.com'}}
/>

改进

UIWebView 没有合法的方式来促进 WebView 中运行的 JavaScript 与 React Native 之间的通信。当消息从 WebView 发送时,我们依靠一种 hack 方式将其传递给 React Native。简单来说,我们将消息数据编码到一个带有特殊方案的 URL 中,然后将 WebView 导航到该 URL。在原生端,我们拦截并取消了这次导航,从 URL 中解析出数据,最后调用了 React Native。这种实现容易出错且不安全。我很高兴地宣布,我们利用了 WKWebView 的特性来完全取代它。

WKWebView 相较于 UIWebView 的其他优势包括更快的 JavaScript 执行速度和多进程架构。请参阅此 2014 WWDC 以获取更多详细信息。

注意事项

如果您的组件使用以下属性,那么在切换到WKWebView时可能会遇到问题。目前,我们建议您避免使用这些属性

不一致的行为

automaticallyAdjustContentInsetscontentInsets提交

当您为 WKWebView 添加 contentInsets 时,它不会改变 WKWebView 的视口。视口的大小保持与帧相同。而对于 UIWebView,视口大小实际上会改变(如果内容内边距为正,则会变小)。

backgroundColor提交

使用 WebView 的新 iOS 实现,如果您使用此属性,您的背景颜色可能会闪烁显示。此外,WKWebView 渲染透明背景的方式与 UIWebview 不同。请查看提交说明了解更多详情。

不支持

scalesPageToFit提交

WKWebView 不支持 `scalesPageToFit` 属性,因此我们无法在 WebView React Native 组件中实现此功能。

辅助功能 API 更新

·7分钟阅读
Ziqi Chen
加州大学伯克利分校学生

动机

随着技术的进步和移动应用在日常生活中变得越来越重要,创建无障碍应用的需求也同样变得越来越重要。

React Native有限的无障碍API一直是开发人员的一大痛点,因此我们对无障碍API进行了一些更新,使其更容易创建包容性移动应用。

现有 API 的问题

问题一:两个完全不同但相似的属性 - accessibilityComponentType (Android) 和 accessibilityTraits (iOS)

accessibilityComponentTypeaccessibilityTraits 是两个属性,用于告诉 Android 上的 TalkBack 和 iOS 上的 VoiceOver 用户正在与哪种 UI 元素进行交互。这两个属性最大的问题是:

  1. 它们是两个具有不同用法但目的相同的属性。 在以前的 API 中,它们是两个独立的属性(每个平台一个),这不仅不方便,而且让许多开发人员感到困惑。iOS 上的 accessibilityTraits 允许 17 个不同的值,而 Android 上的 accessibilityComponentType 只允许 4 个值。此外,这些值在大多数情况下没有重叠。甚至这两个属性的输入类型也不同。accessibilityTraits 允许传入特性数组或单个特性,而 accessibilityComponentType 只允许单个值。
  2. Android上的功能非常有限。使用旧属性,Talkback 唯一能识别的 UI 元素是“按钮”、“单选按钮已选中”和“单选按钮未选中”。

问题二:不存在的无障碍提示:

无障碍提示可帮助使用 TalkBack 或 VoiceOver 的用户了解当他们对无障碍元素执行操作时会发生什么,而这些信息仅凭无障碍标签是无法得知的。这些提示可以在设置面板中开启和关闭。以前,React Native 的 API 完全不支持无障碍提示。

问题三:忽略反色:

一些视力受损的用户会在手机上使用反色以获得更高的屏幕对比度。Apple 为 iOS 提供了一个 API,允许开发人员忽略某些视图。这样,当用户开启反色设置时,图像和视频就不会失真。React Native 目前不支持此 API。

新 API 的设计

解决方案一:合并 accessibilityComponentType (Android) 和 accessibilityTraits (iOS)

为了解决 accessibilityComponentTypeaccessibilityTraits 之间的混淆,我们决定将它们合并为一个属性。这样做是有道理的,因为它们在技术上具有相同的预期功能,通过合并它们,开发人员在构建无障碍功能时不再需要担心平台特定的复杂性。

背景

在 iOS 上,UIAccessibilityTraits 是一个可以设置在任何 NSObject 上的属性。通过 JavaScript 属性传递到原生代码的 17 个特性中的每一个都映射到 Objective-C 中的 UIAccessibilityTraits 元素。特性由长整型表示,所有设置的特性都通过按位或运算组合在一起。

然而,在 Android 上,AccessibilityComponentType 是 React Native 创造的一个概念,并没有直接映射到 Android 中的任何属性。无障碍功能由无障碍委托处理。每个视图都有一个默认的无障碍委托。如果你想自定义任何无障碍操作,你必须创建一个新的无障碍委托,覆盖你想要自定义的特定方法,然后将被处理的视图的无障碍委托设置为与新的委托关联。当开发人员设置 AccessibilityComponentType 时,原生代码会根据传入的组件创建一个新的委托,并将视图设置为具有该无障碍委托。

所做的更改

对于我们的新属性,我们希望创建这两个属性的超集。我们决定主要以现有属性 accessibilityTraits 为蓝本设计新属性,因为 accessibilityTraits 的值明显更多。Android 对这些特性的功能将通过修改无障碍委托进行兼容。

iOS 上的 accessibilityTraits 可以设置为 17 个 UIAccessibilityTraits 值。然而,我们并未将所有这些值都包含在新属性的可能值中。这是因为设置其中一些特性的效果实际上并不为人所知,而且其中许多值几乎从未使用过。

UIAccessibilityTraits 的值通常具有两种目的之一。它们要么描述 UI 元素所扮演的角色,要么描述 UI 元素所处的状态。我们观察到的以前属性的大多数用法通常使用一个表示角色的值,并将其与“已选状态”、“已禁用状态”或两者结合使用。因此,我们决定创建两个新的无障碍属性:accessibilityRoleaccessibilityState

accessibilityRole

新属性accessibilityRole用于告诉Talkback或Voiceover UI元素的角色。此新属性可以取以下值之一

  • 按钮
  • 链接
  • 搜索
  • 图片
  • 键盘键
  • 文本
  • 可调整
  • 标题
  • 总结
  • 图片按钮

此属性只允许传入一个值,因为UI元素通常逻辑上不会承担其中多个角色。例外是图片和按钮,因此我们添加了一个组合两者的角色“图片按钮”。

辅助功能状态

新属性accessibilityStates用于告诉Talkback或Voiceover UI元素的状态。此属性接受一个包含以下一个或两个值的数组

  • selected
  • disabled

解决方案二:添加无障碍提示

为此,我们添加了一个新属性:accessibilityHint。设置此属性将允许Talkback或Voiceover向用户朗读提示。

accessibilityHint

此属性以字符串形式接收要读取的辅助功能提示。

在 iOS 上,设置此属性将设置视图上相应的原生属性 AccessibilityHint。如果 iPhone 中开启了辅助功能提示,则 Voiceover 将朗读该提示。

在 Android 上,设置此属性会将提示的值附加到无障碍标签的末尾。此实现的好处是它模仿了 iOS 上提示的行为,但缺点是这些提示不能像 iOS 上那样在 Android 的设置中关闭。

我们在Android上做出这个决定的原因是,通常情况下,辅助功能提示与特定动作(例如,点击)相关联,我们希望在不同平台之间保持行为一致。

问题三的解决方案

accessibilityIgnoresInvertColors

我们已将 Apple 的 API `AccessibilityIgnoresInvertColors` 暴露给 JavaScript,因此现在当您有一个不想反转颜色的视图(例如图片)时,您可以将此属性设置为 true,它就不会被反转。

新用法

这些新属性将在React Native 0.57版本中可用。

如何升级

如果您当前正在使用accessibilityComponentTypeaccessibilityTraits,这里是您可以采取的步骤来升级到新属性。

1. 使用 jscodeshift

最简单的用例可以通过运行jscodeshift脚本来替换。

脚本 替换了以下实例

accessibilityTraits=“trait”
accessibilityTraits={[“trait”]}

带有

accessibilityRole= “trait”

此脚本还删除了AccessibilityComponentType的实例(假设您在所有设置AccessibilityComponentType的地方都同时设置了AccessibilityTraits)。

2. 使用手动代码转换

对于使用了 AccessibilityTraitsAccessibilityRole 没有相应值的情况,以及将多个特性传递给 AccessibilityTraits 的情况,需要进行手动代码转换。

通常,

accessibilityTraits= {[“button”, “selected”]}

将被手动替换为

accessibilityRole=“button”
accessibilityStates={[“selected”]}

这些属性已经在 Facebook 的代码库中使用了。Facebook 的代码转换出奇地简单。jscodeshift 脚本修复了大约一半的实例,另一半是手动修复的。总的来说,整个过程不到几个小时。

希望您会发现更新后的API很有用!并请继续让应用程序无障碍!#包容

2018 年 React Native 现状

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

距离我们上次发布 React Native 的状态更新已经有一段时间了。

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

我们也在使用 React Native 开发应用的许多新部分。如果你看了上个月的 F8 主题演讲,你就会认识血型捐赠(Blood Donations)、危机响应(Crisis Response)、隐私快捷方式(Privacy Shortcuts)和健康检查(Wellness Checks)——这些都是最近用 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 和打包器可互换。我们知道破坏性更改的速度可能难以跟上,因此我们希望找到减少主要版本发布的方法。最后,我们知道有些团队正在寻求更详尽的文档,例如启动优化等主题,而我们的专业知识尚未被记录下来。预计在未来一年内会看到其中一些变化。

如果您正在使用 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

我们将这些声明文件包保存到我们的 开发依赖项中,因为这是一个 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 构建 <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 上联系我。