跳到主要内容

27 篇帖子已标记 “engineering”

查看所有标签

React Native 0.77 - 新样式功能、Android 16KB 页面支持、Swift 模板

·15 分钟阅读
Vojtech Novak
Vojtech Novak
Expo 软件工程师
Mazen Chami
Mazen Chami
InfiniteRed 软件工程师
Blake Friedman
Blake Friedman
Meta 软件工程师
Rob Hogan
Rob Hogan
Meta 软件工程师

今天,我们很高兴发布 React Native 0.77!

此版本发布了几个功能:新的样式功能,例如支持 display: contentsboxSizingmixBlendModeoutline 相关属性,以提供更强大的布局选项;Android 16KB 页面支持,以兼容较新的 Android 设备。我们还在现代化社区模板,将其迁移到 Swift,同时继续支持和维护与喜欢 Objective-C 的开发者的兼容性。

React Native 0.75 - 支持布局中的百分比值、新架构稳定化、模板和 init 更新及更多

·14 分钟阅读
Gabriel Donadel Dall'Agnol
Gabriel Donadel Dall'Agnol
Expo 软件工程师
Siddharth Kulkarni
Siddharth Kulkarni
Coinbase 软件工程师
Thibault Malbranche
Thibault Malbranche
Brigad 首席移动工程师
Blake Friedman
Blake Friedman
Meta 软件工程师
Riccardo Cipolleschi
Riccardo Cipolleschi
Meta 软件工程师
Nicola Corti
Nicola Corti
Meta 软件工程师

今天,我们很高兴发布 React Native 0.75!

此版本发布了多项功能,例如支持 % 值的 Yoga 3.1、新架构的多个稳定化修复,以及引入了建议用户使用 React Native 框架的建议。

亮点

重大更改

React Native 0.71-RC0 Android 服务中断事后分析

·7 分钟阅读
Nicola Corti
Nicola Corti
Meta 软件工程师
Lorenzo Sciandra
Lorenzo Sciandra
Microsoft 高级软件工程师

现在 0.71 版本已发布,我们想分享一些关于事件的关键信息,该事件在 2022 年 11 月 4 日发布 React Native 和 Expo Android 构建的第一个 0.71 候选版本时,中断了所有 React Native 版本的 Android 构建。

帮助解决此事件的贡献者最近参加了一次事后分析会议,详细讨论了发生的事情、我们从中吸取的教训以及我们将采取哪些行动来避免未来发生类似的服务中断。

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

·3 分钟阅读
Samuel Susla
Samuel Susla
Meta 软件工程师

大家好!

随着新的移动操作系统版本将在今年晚些时候发布,我们建议您提前准备好 React Native 应用,以避免在版本正式发布时出现回归。

引入新的 iOS WebView

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

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

这些更改的尾声已在 此提交 中完成,并将在 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 允许传入 traits 数组或单个 trait,而 accessibilityComponentType 仅允许单个值。
  2. Android 上的功能非常有限。 使用旧属性,Talkback 能够识别的 UI 元素只有“button”、“radiobutton_checked”和“radiobutton_unchecked”。

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

无障碍功能提示帮助使用 TalkBack 或 VoiceOver 的用户了解当他们对仅通过无障碍功能标签不明显的无障碍功能元素执行操作时会发生什么。这些提示可以在设置面板中打开和关闭。以前,React Native 的 API 根本不支持无障碍功能提示。

问题三:忽略反转颜色:

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

新 API 的设计

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

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

背景

在 iOS 上,UIAccessibilityTraits 是一个可以设置在任何 NSObject 上的属性。通过 javascript 属性传递到原生的 17 个 traits 中的每一个都映射到 Objective-C 中的 UIAccessibilityTraits 元素。Traits 各自用一个长整型表示,并且每个设置的 trait 都被 ORed 在一起。

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

所做的更改

对于我们的新属性,我们希望创建一个这两个属性的超集。我们决定将新属性主要仿照现有属性 accessibilityTraits 建模,因为 accessibilityTraits 具有明显更多的值。Android 上这些 traits 的功能将通过修改无障碍功能委托来进行 polyfill。

accessibilityTraits 在 iOS 上可以设置为 17 个 UIAccessibilityTraits 值。但是,我们没有将所有这些值都包含为我们新属性的可能值。这是因为设置其中一些 traits 的效果实际上不是很清楚,并且其中许多值实际上从未使用过。

UIAccessibilityTraits 设置的值通常具有两个目的之一。它们要么描述 UI 元素具有的角色,要么描述 UI 元素所处的状态。我们观察到以前属性的大多数用途通常使用一个代表角色的值,并将其与“state selected”、“state disabled”或两者结合使用。因此,我们决定创建两个新的无障碍功能属性:accessibilityRoleaccessibilityState

accessibilityRole

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

  • 按钮
  • 链接
  • 搜索
  • 图像
  • 键盘按键
  • 文本
  • 可调节
  • 标题
  • 摘要
  • 图像按钮

此属性仅允许传入一个值,因为 UI 元素通常在逻辑上不会承担多个角色。例外情况是图像和按钮,因此我们添加了一个角色 imagebutton,它是两者的组合。

accessibilityStates

新属性 accessibilityStates 用于告诉 Talkback 或 Voiceover UI 元素所处的状态。此属性采用一个数组,其中包含以下一个或两个值

  • 已选中
  • 已禁用

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

为此,我们添加了一个新属性 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. 使用手动 codemod

对于使用了 AccessibilityTraits 但没有 AccessibilityRole 的相应值的情况,以及将多个 traits 传递到 AccessibilityTraits 的情况,必须进行手动 codemod。

一般来说,

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

将手动替换为

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

这些属性已在 Facebook 的代码库中使用。Facebook 的 codemod 非常简单。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 和 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 添加 typings

好的,让我们继续运行这些。

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 上提出了一个 issue,要求从 React Native 支持输入附件视图。

在随后的几年里,出现了无数的“+1”回复、各种变通方法,以及关于此 issue 的 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> 链接到 <TextInput> 在它们的 ObjC 实现中。

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

陷阱

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

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

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


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

输入附件视图成功地避开了主屏幕指示器,但现在不安全区域后面的内容是可见的。解决方案在于这个 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 版本中可用。

键盘操作愉快 :)