Galio-快速移动应用开发-全-
Galio 快速移动应用开发(全)
原文:
zh.annas-archive.org/md5/f91209cf76a6d03c176d458f709fab87
译者:飞龙
前言
本书是 Galio 移动应用程序开发的权威指南,向您展示如何为自己的想法设置 React Native 项目。通过基本概念的逐步解释和实际示例,本书帮助您了解 React Native 的基础知识以及 Galio 的工作原理。
本书适合对象
本书适合希望学习新技能或构建个人移动应用程序的开发人员。任何试图改变工作的人以及初学者和中级 Web 开发人员也会发现本书很有用。需要基本的 CSS、HTML 和 JavaScript 的理解才能充分利用本书。
本书涵盖内容
在第一章中,“React Native 和 Galio 简介”,您将了解 React Native 的强大之处。将简单介绍 React Native 是什么,以及您将发现 Galio 的作用以及它如何节省您的时间和压力。
在第二章中,“React Native 基础”,您将了解 React Native 的基本概念,如 JSX 和这个框架提供的基本组件。您还将了解有关应用程序的正确目录结构以及如何充分利用它的更多信息。
第三章中,“正确的心态”,涉及任何用户在使用 React 时应该如何看待。这将帮助您养成一些开发移动应用程序和软件的良好习惯。它还作为基础知识和实际创建您的第一个跨平台移动应用程序之间的过渡。
在第四章中,“你的第一个跨平台应用程序”,您将学习如何通过实际示例创建您的第一个跨平台应用程序。本章旨在作为打包、如何使用npm
以及为什么需要 Galio 的介绍。
在第五章中,“为什么选择 Galio?”,我们将介绍 Galio 的优势,它如何帮助您,以及如何与社区联系和帮助可以使您受益。这将激励您成为开源社区的积极成员,并更多了解 React Native。
第六章,移动 UI 构建基础,帮助你理解构建应用基本但美观 UI 的基础知识。你可能已经厌倦了丑陋的应用,如果有机会,你会想要创造出美丽的东西。这一章就是关于你如何做到这一点的。
在第七章,探索我们应用的状态,你将看到组件是如何并排运行的,并理解如何、为什么以及在哪里使用 Galio 组件。这样做将帮助你培养自己的批判性思维方式。
第八章,创建你自己的自定义组件,将教你如何基于 Galio 构建你自己的组件。你将发现如何将那些已经存在的美丽组件组合成你在应用中需要的组件。
第九章,调试和寻求帮助,将教你如何调试你自己的应用,并在需要时寻求帮助。
在第十章,构建引导屏幕,你将开始创建 React Native 应用;我选择了引导屏幕,因为通常这是你打开应用时看到的第一个屏幕。
在第十一章,让我们构建-秒表应用,你将学会如何结合你的第一个屏幕,并使用 React Navigation 来连接它与秒表屏幕。这个屏幕会更加困难一些,因为它有一个真实的使用案例,但这将使事情更有回报。
第十二章,接下来做什么?,在这里你将学到更多关于 React Native、Galio,以及如何转变自己成为一个出色和成功的移动开发者。
为了充分利用本书
我假设你已经具备 HTML、CSS 和 JavaScript 的初学者水平知识。有一些 React 的经验肯定是一个优势,但并非必需。你需要一台安装了最新软件的 Windows/Mac 电脑。
如果您使用的是本书的数字版本,我们建议您自己输入代码或从书的 GitHub 存储库中访问代码(链接在下一节中提供)。这样做将有助于避免与复制和粘贴代码相关的任何潜在错误。
阅读完本书后,我希望你尝试重新完成书中的所有挑战,而不查看任何代码,同时为每个练习增添自己的个人风格。
下载示例代码文件
您可以从 GitHub 上的github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
下载本书的示例代码文件。如果代码有更新,将在 GitHub 存储库中更新。
我们还提供了来自我们丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/
上找到。去看看吧!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图和图表的彩色图像。您可以在这里下载:static.packtcdn.com/downloads/9781801073165_ColorImages.pdf
。
使用的约定
本书中使用了许多文本约定。
文本中的代码
:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。例如:"现在,对于第二行,让我们进入我们的styles.row2
对象并添加填充。"
代码块设置如下:
const styles = theme => StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.COLORS.FACEBOOK
}
});
任何命令行输入或输出都以以下方式编写:
npm i galio-framework
粗体:表示一个新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。例如:"在记录下您的用户名和密码后,您应该收到以下响应:成功。您现在以 YOUR-USERNAME 登录。"
提示或重要说明
显示如下。
第一章:介绍 React Native 和 Galio
让我们首先了解本书的内容以及它如何帮助您学习如何使用 React Native 和 Galio。通过阅读本书,您将了解如何在 macOS 和 Windows 上安装 React Native 以及所有必要的工具。然后,您将了解如何创建一个 Expo 项目以及为什么我们使用 Expo,模板工作流之间的区别以及它们如何方便,以及如何在物理设备和模拟器上启动新项目。事情应该非常容易跟随,所以您可能会发现这种体验是有益的。
了解跨平台移动编程的世界并不容易,但肯定是可行的。购买这本书是您迈出的第一步,第二步正在进行中,因为您正在阅读本书,了解 React Native 的工作原理以及 Galio 如何帮助您更快地构建应用。本书的主要目的是让您熟悉 React Native 的工作原理,如何在项目中使用它,以及 Galio 如何方便并可能节省大量时间。
我能理解一开始这可能不是一项容易的任务,但我强烈建议您尽可能多地查看每个部分。如果有一些东西一开始可能不太容易理解,您可以在 Stack Overflow 或不同的 subreddits 等地方提问。我们将在本书的后面深入了解寻求帮助的地方。
起初,大多数程序员,包括我自己在内,都认为跨平台移动编程框架可能比本地框架慢得多。这只是一种想法,因为我们将看到 React Native 是创建移动应用的一种非常好的方式,因为它们与本地应用相比并不慢。
很快您就会明白,这本书与 Galio 紧密相连,我认为 Galio 是最好看的 UI 库之一。Galio 将帮助我们更快地构建 React Native 应用,并且比我们自己能做的更有风格。
您还将学习许多开发自己 UI 的方法,以及在开发应用程序时如何开始跳出思维定式。这很重要,因为这可能是成功应用和失败应用之间的区别。
学习设计和编程的基本规则只是成为一个完整的前端开发人员过程中的第一步。学会如何打破这些规则将进一步发展你的技能。
有时,会有提示出现在最需要的地方,遵循这些提示将有利于任何试图进入程序员思维的人。
在本书的结尾,你会找到练习和许多关于如何为你的移动应用程序开发更复杂的 UI 的提示。所有这些都有一个很好的目的,那就是在拥有一个良好的基础的同时发展编程风格。
我坚信在读完本书之后,任何人都应该能够创建至少一个基本的跨平台移动应用程序,这将作为个人项目的一个很好的 MVP。学习和体验本书中所写的所有内容不仅对你作为学习使用 React Native 和 Galio 的人很重要,对你作为一个程序员也很重要。
本章将涵盖以下主题:
-
为什么选择 React Native?
-
Galio - 最好的 UI 替代品
-
配置你的 React Native 环境
-
创建你的第一个 React Native 项目
为什么选择 React Native?
所以,你可能会想,“为什么选择 React Native?”。市面上有很多跨平台框架,比如 Flutter、Xamarin 和 Cordava 等,所以我们需要了解为什么 React Native 是移动应用程序开发的一个很好的选择。
你需要明白,没有绝对正确的选择。这只是基于当前市场环境和个人欣赏。
编程框架就像画家的画笔。画家有多种画笔,每一种都有不同的用途。你需要尽可能多地了解你正在使用的工具,因为画家对画笔了解得越多,他们就能越好地绘画并将他们的愿景变为现实。
你需要学会如何使用 React Native 快速轻松地开发跨平台应用程序。所以,让我们更深入地了解为什么 React Native 是开发应用程序的一个很好的选择。
你只需要学习一次
首先,React Native 是基于 React 的,这意味着你只需要学习一次,就可以在任何地方开发。这是扩展你技能的一个非常好的方式。通过学习 React Native,你将准备好为你的网站编写 React 代码。
这应该让您很容易理解为什么这是一个如此好的选择。想象一下,您已经创建了一个应用程序。您的应用程序很酷 - 人们开始从 App Store 或 Google Play 下载它 - 但更有帮助的是一个落地页。因为您已经学会了 React Native,利用您的 React 技能将变得轻而易举。
更大的人才储备
在早期的编程时代,当您有一个应用程序想法并想要开发它时,您必须寻找具有一些 C#或 Java 技能的后端开发人员,具有 Objective-C 技能的 iOS 开发人员,必须了解 Java 的 Android 开发人员,甚至可能还需要一些网页前端开发人员来开发应用程序的网站。
这需要大量的努力和相当大的预算。在项目结束时,您的想法可能在今天的市场上行不通,而您将浪费大量的时间和金钱。
现在,所有这些特定的工作都可以由 JavaScript 工程师来处理 - 我们有多种替代方案来使用与原生框架一样好的 JavaScript 编写的框架,而 JavaScript 是目前最常用的语言之一。市场上甚至有更多的 JavaScript 开发人员,并且从一个框架转移到另一个框架比以往任何时候都更容易。通过雇佣 JavaScript 开发人员,预算减少了一半,应用程序开发速度更快,即使他们有不同的工作,他们也可以互相帮助。
JavaScript 开发人员可以轻松更换团队。后端开发人员可以帮助前端开发人员,甚至移动应用团队。他们可以随时提供帮助,无论您在哪里需要更多的人手来加快开发速度。当您的开发人员因辞职或疾病而缺席时,这一点尤为重要。
拥有更多可供选择的人才对于任何应用程序开发来说都是一个巨大的优势。
React 的流行
你可能会认为 React 的流行与 React Native 无关,但实际上,就编写代码和方法论而言,React 和 React Native 是密切相关的。我的建议是始终关注 Google 趋势,因为它可以帮助我们了解一个框架是否受欢迎:
图 1.1 - Google 趋势显示了 React 目前的流行程度
React 使开发人员能够轻松构建出色的 Web UI,但基于组件的方法也使应用程序更容易维护。React Native 将所有这些优势带到了移动应用程序开发领域。
那么,这给我们展示了什么?有一个相当庞大的 React 搜索社区,而 React Native 拥有一个最大且最活跃的社区之一。对于您可能遇到的几乎每一个小问题,都有人已经在 GitHub 上撰写了文章或为其提出了问题。GitHub 上的社区也非常庞大,这将会很有帮助,因为您可以与更多开发人员联系,寻求关于您可能在应用程序中使用的任何库的帮助,并为您可能拥有的任何开源想法获得更多帮助,这可能会对许多开发人员有所帮助。
我建议每个人都参与开源项目,因为这将有助于发展您的技能,并扩展您作为程序员的思维方式。社区是如此乐于助人和友好,以至于您可能会发现很难转向任何其他框架,因为这似乎是大多数需求的最佳选择。
性能
React Native 在性能方面接近原生应用,但您必须以正确的方式使用它。从技术上讲,您有一个运行缓慢的 JavaScript 线程,与 Android 的 Kotlin 或 iOS 的 Swift 等本地代码相比,速度相当慢。
React Native 的闪光点在于它在 JavaScript 线程和本地线程之间创建了一个桥梁。它旨在将诸如渲染之类的最昂贵和强大的任务移动到本地端。这是异步使用的,因此 JavaScript 线程不必等待本地计算。
假设用户按下按钮-React Native 将把这个转化为 JavaScript 可以处理的事件。之后,通过在本地平台(如 iOS 或 Android)和 JavaScript 代码库之间发送消息,React Native 桥将本地事件转换为 React 组件可以理解和响应的事件。
这里有一些挑战,比如默认组件-这些是 React Native 提供的内置元素-在两个平台上看起来或响应起来并不相同,因为有很多特定于平台的事件。不过不用担心,因为这种桥接架构允许我们使用来自平台、SDK 和 JavaScript 库的所有现有本地视图。
语言
JavaScript 是作为客户端语言创建的。它被设计用来使网站具有交互性。如果你想象一个基本的网站布局,你有你的 HTML,描述基本内容和网站结构,然后你有你的 CSS,为 HTML 添加样式并使其美观。这是一个不做太多事情的静态网站,所以我们需要一种编程语言,可以为我们的网站添加功能并使其生动起来。这就是 JavaScript 进入游戏的地方。
时间过去了,人们意识到他们可以用 JavaScript 做更多的事情。JavaScript 最流行的用途是客户端,但自从 Node.js 出现在编程场景中以来,这种语言已经发展得如此之多,以至于这不再是情况。JavaScript 现在是一种多用途的编程语言,意味着你可以用它来构建几乎任何东西。你甚至可以使用 TypeScript 或 Flow 来获得类型化的 JavaScript。代码编辑器内的支持也变得更好了。
说了这么多,React Native 使用 JavaScript 作为其主要编程语言。随着我们的学习,我们会发现 React Native 也可以使用原生代码来运行得更快,做更好的计算。
Stack Overflow(程序员最大的社区之一)每年都会进行一项调查,试图更多地了解开发者和使用他们平台的人。你可以问任何开发者关于他们的平台,几乎任何人都会告诉你他们至少浏览过一次。他们 2020 年的研究显示,几乎 70%的用户是使用 JavaScript 的专业开发者。
作为一种多才多艺的语言,学习它用于 React Native 或其他框架只会帮助你扩展作为程序员的领域。React Native 使用它是一个很大的优势,因为它显示了这样做可以让你在不同技术之间轻松移动。
你可以通过访问insights.stackoverflow.com/survey/2020
了解更多关于 Stack Overflow 关于 2020 年调查的统计数据。
得出结论
阅读了关于 React Native 的所有内容之后,我们需要明白,尽管 React Native 并不像原生应用那样快速,但它几乎可以和原生应用一样快。而且,考虑到这种语言对开发者有如此多的机会,以及社区如此强大友好,我们可能会将 React Native 视为跨平台移动应用开发的最佳框架之一。
为了选择适合你需求的库,你需要考虑对你来说最重要的是什么。我希望你对 React Native 有了一些了解,并且对这个框架是一个好选择有信心。
接下来,我们将更多地了解 UI 库是什么,以及 Galio 是如何像一个伟大的助手一样帮助我们编写代码的。
Galio - 最佳 UI 替代品
所以,你已经学会了一些关于 React Native 的工作原理,现在你想知道 Galio 如何帮助你。首先,Galio 到底是什么?
简而言之,Galio 是一个 React Native UI 库,因此它是一组资源,旨在帮助开发人员更快、更容易地编写代码。问题是... React Native 没有那么多组件。我们将在本书的后面回到组件的具体含义,但现在,只需将它们视为拼图块。
React Native 有一定数量的拼图块,每个拼图块尽可能简单。Galio 作为这些拼图块的包装器,添加了一些颜色和功能。有时,甚至可以找到通过将更基本的拼图块组合成一个非常大的拼图块的不同拼图块,出于特定原因。
现在,让我们来看看为什么 Galio 可能是你在跨平台移动开发旅程中最好的 UI 库。
节省时间
好吧,比喻太多了。事实是,React Native 只有基本外观的组件,这使开发人员需要构建自己的组件。这是耗时的,因为你总是需要为你的新应用构建新组件。
这就是 Galio 派上用场的地方!它拥有许多已经美丽的组件,减轻了一直创建自己的痛苦。
此外,所有组件都更容易定制,仍然适合整个设计布局,而不会给开发人员施加太大压力,让他们考虑如何做以及从哪里开始。从 Galio 定制组件的过程很简单,通常围绕着使用 props,这使整个过程更加可读。
我知道像“组件”和“props”这样的词对你来说完全或可能有些陌生,但重要的是它们可以节省你大量的时间。我们很快就会了解更多关于这些关键词,但我们需要了解一些关于所有这些技术在整体方案中意味着什么。
使用 Galio 构建应用通常更多地取决于您选择创建布局的方式,而不是实际编程 UI。它是通过直接放置每个组件在前一个组件下面的方式来创建移动屏幕。这使我们能够更高效,尽可能少地浪费时间编写代码并思考最终屏幕的样子。
以下图表显示了您可以使用我们讨论的拼图块创建的基本程序结构:
图 1.2 - 添加更多组件如何帮助构建移动屏幕的表示
这是一种很好的思考方式,因为它让您了解了一些最佳的原子设计原则。它还创建了一个更有组织的代码库,您可以将其扩展为更复杂和更完整的应用程序。
这真的很美
Galio 已经预先设计了一个设计系统,这意味着所有组件都将遵循相同的设计原则,因此组件之间永远不会有任何差异。
一致的设计是使应用完整的关键。一致的设计将帮助用户更好地理解您的应用流程,您希望在应用中引入的所有符号以及如何使用它。这一切都与您的按钮、文本和设计保持一致有关。
起初您可能不喜欢颜色,这没关系,因为您可以借助<GalioProvider>
轻松更改它们。我们稍后会在本书中讨论这一点。
现在,我们已经了解了为什么 React Native 是一个如此好的选择,以及为什么 Galio 是一个很好的 UI 库,让我们开始构建应用。下一步是了解如何配置一个良好的环境,以便我们可以开始构建跨平台移动应用。
配置您的 React Native 环境
有两件事我们需要了解:Expo和React Native CLI。
它们都很容易安装,我们将介绍它们,以确保我们覆盖所有可能的情况。我建议不要跳过这部分,因为它将帮助您在开始开发移动项目时做出一个好选择。
在 macOS 上安装东西要比在 Windows 上容易得多,因为 macOS 是基于 UNIX 的系统,所以终端更加强大。但不用担心 - 我们也会解决 Windows 上的这个问题。
在我们继续之前,我们必须考虑一些要求。这些将帮助我们为 Expo 和 React Native CLI 以及一般的 JavaScript 编程创建一个良好的环境。
无论是哪个系统,我们都需要安装以下技术:
-
Homebrew - 仅限 macOS
-
Chocolatey - 仅限 Windows
-
Node.js
-
文本编辑器
-
Android Studio
-
Xcode - 仅限 macOS
我们将从安装 Node.js 开始,这是我们在浏览器之外运行 JavaScript 所需的最重要的技术之一。Node.js 是建立在 Chrome 的 V8 JavaScript 引擎上的,这意味着您可以运行在最新 Chrome 版本(Web 浏览器)上运行的任何 JavaScript 代码。
安装 Node.js 的推荐方式取决于您的操作系统。对于 macOS 用户,最好的方式是使用 Homebrew,而对于 Windows 用户,您将使用 Chocolatey。Homebrew 和 Chocolatey 是包管理器,可以让您更轻松,更快速地安装不同的软件包,如 Node.js,都可以通过命令行或终端进行。您也可以通过官方网站nodejs.org
安装它,但是在本书中我们将使用 Homebrew 或 Chocolatey。
Homebrew
对于 macOS,我们有 Homebrew,它很容易安装。您可以在他们的官方网站brew.sh
上找到更多信息。
要安装它,您应该打开终端并输入以下命令:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
在输入命令后,按Enter。将会出现有关即将安装的所有内容的更多信息;只需再次按Enter,您就准备好了。
Chocolatey
对于 Windows,我们有 Chocolatey,它比 Homebrew 更复杂,但是按照这里的步骤,您应该已经准备好了。您可以通过访问它们的官方网站chocolatey.org
了解更多关于 Chocolatey 的信息。
首先,我们需要以管理员权限使用 PowerShell。要访问它,您只需在键盘上按Windows 标志+X。一个新菜单将出现在屏幕的左下角。在这里,选择Windows Powershell(管理员)。一个新窗口将打开。
首先,您需要验证Get-ExecutionPolicy
是否不受限制,因此在 PowerShell 中写入以下内容:
Get-ExecutionPolicy
如果返回Restricted
,那么您需要运行以下命令:
Set-ExecutionPolicyAllSigned
现在,您已经准备好运行以下命令:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
现在,等待几分钟让一切都安装好。如果在安装过程中没有遇到任何错误,只需输入choco
即可返回 Chocolatey 版本。如果返回了版本号,那么你就已经准备好了。
现在我们需要做的就是安装 Node.js,这样我们就可以学习 Expo 和 React Native CLI 了。安装了 Homebrew 或 Chocolatey 后,只需输入以下命令,Node.js 就会开始安装:
- 在 macOS 上使用以下命令:
brew install node
- 在 Windows 上使用以下命令:
choco install -y nodejs
恭喜!我们现在已经准备好继续前进了!有了这个,我们已经安装了 Node.js。在设置环境之前,让我们讨论一下文本编辑器-我保证不会花太长时间。
我敢打赌你现在在想,“等等,他说我们可以在 Word 文档中写代码吗?”其实不是。Microsoft Word 不是一个纯文本编辑器,但你可以使用诸如 Notepad 之类的东西来写代码。仅仅因为我们可以使用 Notepad 并不意味着我们会使用它;它看起来并不太专业,对吧?
我们将使用的文本编辑器将具有一些很酷的功能,比如代码语法的颜色方案,以及将帮助我们更快更漂亮地编写代码的不同附加组件。
有许多不同的免费文本编辑器,包括 Sublime、Atom、Visual Studio Code、Notepad++和 Brackets。它们都同样优秀,我建议你至少下载两三个来试试。我个人偏好 Visual Studio Code,并且在本书中将一直使用它。如果你不喜欢某个文本编辑器的外观,你不需要使用相同的文本编辑器,因为你可以使用上述任何一个编辑器来跟随本书。
你可以从code.visualstudio.com/
下载 Visual Studio Code(或简称 VSCode)。
现在我们已经解决了一些必需品,是时候继续学习 Expo 和 React Native CLI 了。它们都可以用来实现相同的结果-它们只是创建 React Native 应用的不同方式,我们将尽量理解它们。了解它们的一切将帮助我们选择适合我们和我们的应用的正确方式。
React Native CLI
React Native CLI 是创建 React Native 项目的官方和首选方法。通常比 Expo 更难配置,需要更多时间,但是很值得。毕竟,您需要模拟器来测试您的应用在不同手机上的情况。我建议不要跳过这一部分。
macOS
拥有 macOS 的一个好处是您可以模拟 iPhone,并查看您的项目在不同的 Apple 技术上的外观。这是在 Windows 上无法做到的,但 Android 可以在两者上运行,因此 macOS 在能够模拟所有类型的平台方面具有优势。
我们应该开始并安装所有必要的依赖项;打开终端并写入以下内容:
brew install watchman
Watchman 是 Facebook 开发的一个工具,用于监视文件系统内的更改。它还提供更好的性能。
现在,您需要安装 Xcode。前往 Mac App Store,搜索 Xcode,然后点击安装。这也将安装 iOS 模拟器和构建 iOS 应用所需的其他工具。您的 Xcode 版本至少需要是 9.4 才能与 React Native 一起使用。
现在,我们需要安装 Xcode 命令行工具包。一旦 Xcode 下载和安装完成,打开它,然后转到首选项(在导航栏的 Xcode 菜单下; 或者,只需按Cmd + ,)。应该会打开一个新窗口。转到位置,通过从下拉菜单中选择最新版本来安装命令行工具:
![图 1.3 - Xcode 中的首选项窗口
]
图 1.3 - Xcode 中的首选项窗口
现在,转到组件选项卡,并安装您希望使用的最新模拟器。
重要提示
苹果支持的每个 iOS 版本都有一个模拟器。您应该尝试安装最近的两个主要版本,因为您的应用用户可能始终使用较早的 iOS 版本。
现在,您只需要通过在终端中写入以下内容来安装 CocoaPods:
sudo gem install cocoapods
这是一个 Ruby gem,用于管理 Xcode 项目的依赖关系。
有了这些,您就可以在 macOS 上创建您的第一个项目了!我们马上就会做到!
Windows
众所周知,我们无法在 Windows 上安装任何 iOS 模拟器,所以我们不妨只安装 Android 模拟器。
我们已经安装了 Node.js,现在剩下的就是通过转到我们的管理员 PowerShell(我们在安装 Node.js 和 Chocolatey 时解释了如何打开它)来安装 JDK。一旦打开它,写入以下内容:
choco install -y openjdk8
如果您已经安装了 JDK,请确保至少是 v8。
现在,是时候安装我们的 Android 开发环境了,这可能有点乏味。然而,这是值得的,因为我们将能够在虚拟 Android 模拟器上运行我们的 React Native 应用程序。
前往developer.android.com/studio
下载 Android Studio。安装完成后,启动 Android Studio。打开后,选择您喜欢的主题和适合您计算机的所有首选项。在某个时候,SDK 组件设置页面将出现。确保已选择了Android SDK、Android SDK 平台和Android 虚拟设备复选框。
安装完成后,是时候继续了。Android Studio 默认安装最新的 Android SDK。然而,使用本地 Android 代码构建 React Native 应用程序需要Android 10 (Q) SDK。要安装它,打开 Android Studio,点击窗口右下角的配置,然后选择SDK Manager:
图 1.4 – Android Studio 和按钮的位置
现在,选择SDK 平台选项卡,并在右下角的复选框中选中显示包详细信息。查找并展开 Android 10 (Q),并确保以下内容已被选中:
-
Android SDK 平台 29
-
Intel x86 Atom_64 系统映像或 Google APIs Intel x86 Atom 系统映像
接下来,您应该选择SDK Tools选项卡,并选中显示包详细信息旁边的复选框。查找Android SDK 构建工具,并选择29.0.2。
点击应用并下载所有必要的文件。
现在,是时候配置ANDROID_HOME
环境变量,以便我们可以使用本地代码。打开控制面板,点击用户帐户,然后再次点击用户帐户。在左侧,您会找到更改我的环境变量;点击它。现在,点击新建…并写入以下内容:
-
变量名:
ANDROID_HOME
。 -
变量值:
C:\Users\{name}\AppData\Local\Android\Sdk
,其中{name}
是您的 PC 用户名:
图 1.5 – Windows 显示我的用户变量
检查环境变量是否已加载,转到 PowerShell 环境并输入以下内容:
Get-ChildItem -Path Env:\
您应该看到一个列表,其中ANDROID_HOME
应该是其中的一部分。
之后,我们需要将platform-tools
添加到Path中。我们可以通过转到控制面板,点击用户帐户,然后再次点击用户帐户来实现这一点。点击更改我的环境变量,然后查找Path。选择Path并点击编辑。一个新窗口将出现,我们可以点击新建。现在,使用与之前相同的变量值,但这次进入Sdk
文件夹 - 更确切地说是platform-tools
文件夹:
C:\Users\{name}\AppData\Local\Android\Sdk\platform-tools
我们现在已经准备好在 Windows 上开始开发 React Native 应用程序了!
Expo
Expo 是初学者开始 React Native 项目的最简单方式。它内置了一套为 React Native 构建的大量工具,但我们现在对此不感兴趣。我们只对 Expo 可以在几分钟内让您上手并且不需要您安装模拟器感兴趣,因此您可以在几分钟内玩转您的应用程序。
他们还推出了一个名为 Snack(snack.expo.io/
)的东西,如果您希望直接在浏览器中尝试不同的代码想法,这将非常有帮助!这很酷,因为即使只是想快速草拟一些东西,您甚至都不需要启动一个项目。
让我们安装它,看看这是否像我所说的那样简单。打开终端或命令行并输入以下内容:
npm install -g expo-cli
Expo 现在已经准备就绪!很简单,对吧?
准备进一步前进了吗?
现在我们已经安装了所有必要的技术,我们准备创建自己的 React Native 项目并创建一些很棒的应用程序!
但首先,让我们了解 React Native CLI 和 Expo 之间的区别。之前,我告诉过您不要跳过 React Native CLI 部分,即使它比 Expo 大得多。这是因为我们需要安装 Xcode 或 Android Studio,以便直接从我们的 PC 上控制我们的应用程序。
我们还没有使用 React Native CLI 或 Expo 创建项目,因为它们都是以不同的方式创建的。但是,我们已经安装了它们的要求。使用 React Native CLI 创建项目会让开发人员完全从 0 开始创建应用程序。您对应用程序拥有完全的控制权,没有任何东西能够限制您的想象力。您甚至可以使用本地代码 - Kotlin/Java 用于 Android 或 Swift/Objective-C 用于 iOS - 并创建自己完全本地的组件。不过,这都是非常高级的,我们并不需要它。
Expo 内置了许多工具,适合那些想要创建快速强大的应用程序,而不必费心考虑应用程序如何运行并与特定平台连接的所有细节的人。
因此,我们将使用 Expo 来创建本书中的项目。
创建您的第一个 React Native 项目
我们准备好了!让我们打开一个终端,直接开始吧!
一旦终端打开,只需移动到您的Desktop
文件夹或任何您希望在其中创建项目的文件夹。您可以使用cd
命令在文件夹之间移动。因此,只需输入cd Desktop
,我们就到达了Desktop目录,准备创建我们的 Expo 项目。
我们可以通过以下方式使用 Expo 创建一个新的 React Native 项目:
expo init MyFirstProject
按下Enter键后,Expo 告诉我们可以在多种模板之间进行选择。最大的两个类别是托管工作流程和原始工作流程:
图 1.6 - 初始化项目后您将看到的表示
我们将在几秒钟内解释这两者是什么。现在,在托管工作流程下选择空白。等待几秒钟,您将在终端中看到以下消息:
Your project is ready!
现在,我们准备开始了。在终端中输入以下命令;这将把我们移动到我们项目的文件夹中:
cd MyFirstProject
既然我们在这里,让我们了解每种类型的模板是如何工作的,这样我们就可以开始玩我们的第一个 React Native 应用程序了。
托管工作流程
托管工作流程试图处理几乎所有你必须做的复杂操作。这通常适用于完全的初学者,他们不想用 Xcode 或 Android Studio 复杂化事情,这也正是我们开始创建这种类型工作流程的原因。你可以通过 app.json
更改应用的所有信息,比如它的图标或启动画面,并且可以直接、轻松地访问不同的服务,比如推送通知。
然而,这种工作流程也有一些局限性。假设你想使用 Expo SDK 没有通过其 API 提供的特定设备功能。这意味着你需要运行 eject
,expo-cli
将会做所有的工作并将你转移到裸工作流程。
裸工作流程
裸工作流程为开发者提供了对应用的完全控制。然而,这也伴随着需要处理关于应用的每一个细节的复杂性。现在,轻松配置 app.json
的能力已经消失,你只剩下安装了 Expo SDK 并且没有进行预配置。
这使你可以使用本地代码并以任何你想要的方式管理你的依赖。你可能会想,“嗯...这不就是使用 React Native CLI 一样吗?”。嗯,其实不是,因为你可以立即访问 Expo SDK 和 Expo 框架,这本身对于开发者来说是一个巨大的优势,因为它仍然简化了你的开发过程。
打开我们的项目文件
现在我们已经了解了每个模板的作用以及为什么选择了托管工作流程,让我们看看代码是什么样子的。
还记得我们讨论过文本编辑器吗?继续打开你选择的文本编辑器。我会使用 VSCode,因为我更喜欢它的设计。
点击文件 | 打开文件夹,然后搜索项目文件夹。我的在桌面
文件夹中。打开它让我们看到项目中所有的文件和文件夹。
我很确定你对每个文件的目的感到困惑。我们马上会看一下这个,但现在,花几分钟四处看看,看看你是否可以自己找到一些东西。
提示
调查你找到和创建的任何代码片段是确保你正在学习你所阅读或编码的内容的最佳方式。最优秀的程序员总是那些运用他们的演绎能力的人。
为预览准备我们的物理设备
现在是时候准备我们的手机,这样它就可以预览我们的应用了,因为向朋友展示我们的新技能总是很酷的。
所以,让我们去应用商店或 Google Play 搜索 Expo。安装它,你就准备好了。这个应用程序允许我们在手机上测试我们的应用,所以让我们试一试吧!
去你的终端,如果你还没有在项目文件夹中,就前往那里。输入以下命令:
npm start
之后,按下Enter。一个新窗口应该会在你的默认浏览器中打开。一个服务器已经创建,你的应用现在可以在你的物理设备上或者模拟器上预览了。在侧边栏上,有一个 QR 码,上面有一个链接和一些按钮。这是什么,我们怎样利用它呢?
图 1.7 - 屏幕上显示的所有按钮的预览
现在,你可以打开你的智能手机,扫描 QR 码或者将 QR 码上方的链接粘贴到浏览器中。一个消息会出现,询问你是否愿意用 Expo 打开链接。按下是,你就成功了 - 你的第一个 React Native 应用在你的物理设备上了。
相当酷,对吧?
让我们看看如果我们按下在 Android 设备/模拟器上运行或者在 iOS 模拟器上运行会发生什么。一个消息会出现在右上角,告诉你 Expo 正在尝试打开模拟器。取决于你在哪个操作系统上启动了你的项目,以及你安装了哪个模拟器,选择适当的按钮。
对于 Android 模拟器,你需要先打开 Android Studio。然后,你必须去右上角,那里写着配置,选择AVD Manager。现在,一个新窗口会出现,显示所有可用的虚拟设备。如果你没有看到任何设备,去左下角点击创建虚拟设备。
在这一点上,你会看到一个 Android 设备列表。我选择了 Pixel 3a,但如果你的 CPU 不是很强大,你可以选择一个旧的设备。之后,点击下一步;你将被要求选择一个系统镜像。找到推荐旁边的x86 镜像选项卡,并选择不需要下载的镜像,名为Q。如果所有镜像都需要下载,那么你需要回到 Android Studio 安装部分,重复该过程。选择镜像后,点击下一步,并命名 AVD;它可以被称为任何东西,所以要有创意。点击完成;你应该在AVD 管理器列表中看到你的设备。在同一行的右边,你会找到一个看起来像播放符号的小绿色按钮。点击它。
一个新的 Android 模拟器将打开,所以让我们回到浏览器标签,我们的 Expo 服务器正在运行,然后点击在 Android 设备/模拟器上运行。如果你看一下终端,你会看到一些文字出现了。我们只需要等待一会儿,Expo 客户端就会被下载并安装到我们的模拟器上。它应该会显示类似这样的内容:
Downloading the Expo client app [================================================================] 100% 0.0s
Installing Expo client on device
Opening exp://192.168.1.111:19000 on Pixel_3a_API_30_x86
现在,你的 Android 模拟器应该显示你的第一个 React Native 应用的预览。感觉如何?你经历了很多,但最终,能够正确初始化一个项目是相当有回报的:
图 1.8 - Android 模拟器显示一个全新的 React Native 项目
在我们继续之前,我认为我们应该学习如何在终端/命令行中关闭一个项目。回到终端窗口,点击它,使其聚焦。现在,如果你按下Ctrl + C,服务器应该停止,你应该能够再次使用终端窗口。
总结
本章以对 React Native 和 Galio 的简要介绍开始,然后我们了解了为什么这些库对你的下一个个人跨平台移动应用项目很有好处的主要焦点。在充分理解为什么学习这些库将帮助它们成为未来的重要资产之后,我们开始设置我们的 React Native 环境,并学习关于测试和利用我们即将创建的移动应用的一切。
我们接着创建并测试了我们的第一个 React Native 应用!对于一个新手来说,进入这个美丽的编程世界确实是一次很棒的旅程,相信我,这一切都是值得的。你在这里学到的一切将作为接下来要学习的基础。这一切都很令人兴奋,不是吗?在下一章中,是时候学习 React Native 代码编写的基础知识,并创建更酷的应用了。
第二章:React Native 基础知识
我们首先学习了为什么 React Native 和 Galio 形成了最佳组合,可以帮助我们开始构建我们的第一个跨平台移动应用程序。在设置环境并配置必要文件后,我们使用 Expo 创建了我们的第一个 React Native 项目,并学习了不同的测试应用程序的方法,包括物理和数字化测试。
我相信在学习如何之前先了解为什么有助于建立更好、更健壮的知识基础。经过了解为什么,现在是时候学习 React Native 的工作原理以及如何使用它来创建我们的应用程序了。
这就是为什么我们将从我们的 React Native 项目的文件结构开始这一章,以便我们了解这些文件和文件夹是如何连接的。然后我们将详细介绍App.js
文件,并解释这对我们作为应用程序的主要入口点是如何工作的。
一旦我们了解了文件结构,就是时候学习JSX是什么以及如何使用它了——这是任何 React 应用程序的骨架。我们将经常将 JSX 与 HTML 进行比较,因此您必须事先了解一些 HTML。如果您对 Web 开发了解不多,也不用担心——我们也会介绍一些 HTML 概念,但自己学习一些可能会对您有所帮助。理解 JSX 的概念是我们将处理组件概念的地方,这是我们在第一章中几乎没有涉及的概念。到本章结束时,这应该是完全理解的。
一旦我们了解了 JSX 的主要概念以及它与 React 和 React Native 的关系,我们将进行我们的第一个组件导入。我们将学习 npm/yarn 是什么以及如何使用它来导入和上传组件或库到网络上。这是令人兴奋的,因为您将看到拥有一个庞大的社区支持一个框架的重要性,以及您如何参与并结交新朋友。
现在是时候学习 React Native 的核心组件了。我们将了解它们的用途和上下文,并讨论改进它们或完全更改它们的不同方法。核心组件是我们在网上找到的所有组件的基础组件。这意味着几乎每个组件都继承自核心组件,这使得了解和理解它们非常重要。
在本章结束时,你将学会如何构建一个组件。你还将学会如何在我们的应用程序或未来的应用程序中使用它,以及如何组织文件,这样你就永远不会迷失在寻找你的组件中。
我相信在本章结束时,你将能够开始构建非常简单的应用程序,这些应用程序可以作为构建更大、更复杂项目的基石。理解这些概念并不仅限于阅读本书 - 它会更进一步,你会看到我一直鼓励你查看我们使用的项目/框架的官方文档,因为文档应该始终是程序员应该感到舒适阅读的东西。学会阅读文档是一个你会通过阅读和对你热衷的项目尽可能感兴趣来发展的技能。
本章将涵盖以下主题:
-
使用
App.js
- 主入口点 -
理解 JSX 的概念
-
导入你的第一个组件
-
核心组件
-
理解和创建一个组件
技术要求
你可以通过访问 GitHub 上的github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
来查看本章的代码。你会发现一个名为Chapter 02
的文件夹,其中包含我们在本章中编写的所有代码。要使用该项目,请按照README.md
文件中的说明进行操作。
使用 App.js - 主入口点
就像我们所知道的,React Native 是一个用于构建 iOS 和 Android 应用程序的开源框架。它使用 React 来描述 UI,同时通过我们可用的方法访问平台的功能。
理解我们的文件夹结构很重要,因为在开发初期我们不应该触碰一些文件。让我们来看看我们新创建的项目结构。
提示
不要忘记你可以使用任何你喜欢的文本编辑器;我只是使用 VSCode 是因为我喜欢它的外观,并且它有很多我使用的插件,但这并不意味着你不能用任何你感觉舒适的文本编辑器打开项目。当然,这意味着你将无法使用code.
命令,因为那只能用于 VSCode。
首先,让我们打开我们的终端并导航到我们的文件夹。现在,如果我们到达文件夹后写code.
,它将打开 Visual Studio Code。
一旦我们的文本编辑器打开,我们将在项目目录中看到以下输出:
图 2.1 – 打开文本编辑器后的项目目录
正如我们所看到的,这里有几个文件夹和文件,它们都旨在在你完成应用程序后帮助捆绑项目。我们将在接下来的几节中查看这些文件夹中的每一个。
.expo 和.expo-shared 文件夹
我们将从带有点的文件夹开始:.expo
和.expo-shared
。点在那里是为了显示一个隐藏文件。这是一个你在打开文件浏览器时无法直接看到的文件;只有在你明确选择查看它时才能看到。这些文件是隐藏的,因为你不需要触碰它们。它们是在你第一次使用expo start
命令时创建的配置文件。
资产文件夹
接下来的文件夹是assets
文件夹。在里面,你会找到几个.png
图像,这些图像是 Expo 用于启动屏幕的 – 应用程序加载时出现的屏幕 – 以及应用程序在设备上安装时使用的图标。
node_modules 文件夹
现在,你会看到一个名为node_modules
的文件夹。如果你打开这个文件夹,你会看到很多很多的文件夹。所有这些文件夹都是我们用来使这个应用程序工作的包和依赖项。你安装或从互联网引入的所有东西都会直接放入这个文件夹。这个文件夹会随着你为应用程序使用的外部包的数量而变得越来越大。
一旦我们通过这些文件夹,我们会发现一些具有一些有趣特征的文件。
文件内部
首先,我们可以看到.gitignore
,它可以帮助我们在 GitHub 上上传时节省空间。如果你打开这个文件,你会看到里面已经写了一些文字。一旦你上传项目到 GitHub,你在里面看到的所有东西都会被忽略。你会发现.expo
在那里,因为那些文件夹只对程序员有用,不打算共享。你可以通过使用任何你不想转移到在线或者你不打算更改的文件名来编辑这个文件。
重要提示
GitHub 是一个像互联网托管公司一样为开源软件程序提供服务的平台,同时还使用 Git 为程序员提供版本控制。开发人员使用 Git 来跟踪他们项目中的变化,并与他们的团队协调。
现在,我们会忽略App.js
,因为我们将在本节末尾解释这个文件。所以,让我们直接转到app.json
文件。这个文件就像是你的应用程序的配置文件 - 基本上,所有与代码无关的东西都会在那里找到。比如,例如,我们想要更改启动画面的图片。除了进入这个文件并编辑启动图片的路径之外,我们没有其他办法。从这里,你可以改变几乎与你的应用程序相关的一切,比如图标或其方向。你会发现自己经常去那里,为最终发布版本配置你的应用程序。
我们不关心babel.config.js
,但我相信你对那个文件也会感到好奇。Babel 是一个几乎每个人都在使用的 JavaScript 编译器,用于获得对 JavaScript 最新标准的访问权限。编辑这个文件并不是必要的,但如果你想了解更多关于编译器的信息,我建议搜索更多关于 Babel 的信息。
最后两个文件是package-lock.json
和package.json
。当你在项目中使用 npm 安装依赖时,第一个文件总是会被创建。我已经告诉过你,我们将在本章学习 npm,但现在不是时候。现在,我希望你熟悉项目目录中的所有文件。通过命令行创建应用程序时,Expo 自动使用 npm 从互联网上获取了许多你在项目中会使用的文件。这些文件存储在node_modules
文件夹中。你可以在package.json
中找到更多关于你正在使用的所有直接依赖项的信息。
现在我们终于到了所有文件的末尾,我们应该开始讨论App.js
。所以,让我们打开那个文件,看看它。
App.js 文件
打开App.js
文件后,你会看到以下内容:
图 2.2 - App.js 文件中的代码
你可以立即看到打开 App.js 开始工作你的应用程序!文本。我相信你记得,在上一章中,当我们测试我们的应用程序时,这就是出现在屏幕中央的文本。这意味着通过更改文本,我们也应该在我们的应用程序中看到变化。
我们现在不会这样做,因为我们的重点是理解文件和代码,然后根据自己的喜好进行更改。
我相当肯定,在看到这个文件后,你已经连接了其中的内容,并意识到这是我们应用的入口点。入口点是连接所有文件并启动应用程序的主文件。我们使用 Expo 的主要函数是App()
函数。整个应用程序将存在于该函数内。
当你打开应用程序时看到居中的文本,原因是文本位于App()
函数内。在这里,我们将开始构建我们的应用程序。为了实现这一点,我们必须理解 JSX 是什么,以及如何在我们的应用程序中使用它。我假设你已经能够阅读一些 JavaScript,并且理解诸如函数和对象之类的概念;我们不会在本书中涉及这个主题。我们将在下一节中掌握 JSX。
理解 JSX 的概念
我们终于到了这里,现在准备好审查 JSX 并学习如何在我们的应用程序中使用它。React 非常依赖于 JSX,因为这是构建应用程序布局的主要方式。
首先,我们来看一个包含一些 JSX 代码的变量:
const text = <Text>Hi, this is a message</Text>;
这种看起来很奇怪的语法看起来有点熟悉,对吧?它看起来像HTML。我相当肯定你至少见过一次 HTML 代码是什么样子。如果你还没有,那就打开你最喜欢的浏览器,然后转到reactnative.dev
之类的网站。一旦你到达那里,右键单击网站的任何位置,然后左键单击检查。
一旦你做到了,你会看到大量的 HTML 代码。随机点击其中任何一个 HTML 元素都会带你到网站上的特定元素。所以,你可以看到,HTML 是一种描述事物应该如何看起来的语言,或者更准确地说,它语义地定义了每个元素对于浏览器来说是什么。
不过,我们在 React/React Native 中不使用 HTML。相反,我们使用一种叫做JavaScript XML(JSX)的东西。
JSX 是 JavaScript 的扩展,允许我们在 JavaScript 中编写 HTML 元素。事实上,React Native 甚至不使用 JSX 的 HTML 部分。它只是使用它的语法,因为这样更容易观察和阅读。它也是基于 React 的,所以很明显它在编写代码方面会非常相似。
我觉得仅仅通过阅读前面的 JSX 代码,我们就可以很容易地理解那里发生了什么。它应该是一个带有消息“嗨,这是一条消息”的文本。
我们都知道在 HTML 中“text”不是要使用的正确标记,因为它不存在。我们在这里称之为text,因为这是一个 React Native 组件。
太好了!所以,现在终于是时候涉及组件了。
发现组件
组件只是 JavaScript 函数。因此,我们可以这样写:
function Element(props) {
return <Text>Welcome to your first component</Text>;
}
这个函数被称为组件,因为它返回一个 JSX 元素。我们稍后会讨论props,但它们非常重要,因为任何组件都可以在其函数内部接收一个 props 参数。
定义一个组件很容易,但它的使用非常重要。这使我们能够创建尽可能多的组件,它们可以是我们喜欢的任何样子。这是因为它清除了我们的代码,并使事情更容易组织。
让我们看看我们在App.js
文件中找到的代码。试着观察App()
函数的样子。它唯一要做的就是返回一大堆 JSX 元素。在这种情况下,我们甚至可以将这个函数称为一个组件,并且我们可以将其视为一个 JSX 标记。
Expo 正在使用此组件来启动您的应用程序,这意味着您的 React Native 应用程序只是一个封装了您将要编写的所有其他组件的大组件。
我所说的通过将此组件用作 JSX 标记是,如果出于某种原因,我们想将此组件带到应用程序的不同部分中,我们可以轻松地转到需要它的文件并在堆栈中写入<App />
。然后,App()
函数中的所有内容都将被呈现。
让我们尝试在我们唯一的App.js
文件中使用一个已经存在的组件。我们知道<Text>
是一个已经定义的组件,因为当我们第一次测试我们的应用程序时,我们看到它起作用了。
您应该已经打开了项目和终端。让我们继续在终端中写入expo start
,这样服务器就会开始启动。
一个新的窗口将在您的浏览器中打开,就像在上一章中一样。点击Run on…并选择您想要使用的模拟器(或者如果对您来说更容易,可以使用您的物理设备)。我们已经讨论了如何运行应用程序,所以如果有什么东西似乎有点难以理解,请回到第一章,React Native 和 Galio 简介,以刷新您的记忆。
现在应用程序在您的设备上运行良好,我们看到的是我们已经看到的基本屏幕。让我们通过删除<Text>
标签之间的文本并用其他内容替换来稍微改变一下。在那里写上你的名字;我也会这样做。
所以,现在,我们有以下行:
<Text>Open up App.js to start working on your app!</Text>
此时,它应该看起来像这样(但是用您的名字代替我的):
<Text>Alin Gheorghe</Text>
在那之后,转到行的末尾并按Enter。将创建一个新行,所以让我们添加一些内容,使这个起始应用程序感觉更像是个人的东西,是我们自己的东西。
我们应该添加一些描述我们年龄和家乡的文本。您的App()
函数现在应该看起来像这样:
图 2.3-您最近修改的代码
现在保存您修改过的文件(通常通过按下Ctrl + S或cmd + S),您会突然观察到一些很酷的东西。一旦您这样做了,代码会自动在您的模拟器/物理设备上更改。
这太棒了,对吧?通常,您需要重新启动服务器,但我们只需要保存我们一直在编辑的文件。这被称为热重载,是 React Native 自带的一个很棒的功能。
由于我们在我们的App
函数中添加了一个新的Text
组件,您可能已经猜到我们需要从某个地方获取这个组件。您不能在文件中使用组件而没有先导入它。所以,让我们学习如何做到这一点。
导入您的第一个组件
现在,是时候让我们更多地了解导入组件了。导入很棒,因为我们可以从任何地方获取组件并在我们的应用程序中使用它们。我的意思是-您可以从互联网的任何地方获取组件。
首先,让我们看看我们一直在App.js
文件中使用的Text
组件是如何进入的。
如果我们查看App()
函数上面,我们会看到代码的第一行都是不同组件的导入。让我们看看它们是否那么复杂:
图 2.4-在 App.js 文件中显示的导入
这很容易阅读和理解这里到底发生了什么。让我们以第一行为例。我们从名为expo-status-bar
的包中导入StatusBar
。
为什么我们要这样做?在我们的App()
函数中,您会看到我们使用了一个名为StatusBar
的组件。
为了能够使用特定组件,我们需要从软件包或项目内的定义路径中导入它。
我们可以看到来自React的导入,但我们在代码中找不到 React 组件;为什么呢?这主要是因为我们需要 React 来能够在创建所有这些组件和编写 JSX 时使用 React 框架。
在下面,我们可以看到有三个来自名为react-native
的软件包的不同导入。我们可以看到StyleSheet
,Text
和View
。React Native 内置了许多基本但非常重要的本地代码实现,供我们在 React 应用程序中使用。
我们将在下一节更详细地查看这些核心组件,但您必须了解这样一个事实,即这些组件被导入然后在我们的主函数中使用。
您可以在网上找到软件包,因此您可以通过使用npm轻松将它们导入到您的文件中。这已经与您的 Node.js 配置一起安装好了,所以现在就可以使用了。我们可以在npmjs.com
上搜索软件包,并使用npm i package-name
命令轻松安装其中任何一个。
现在,我们将专注于我们从react-native
中收到的组件。我们将在接下来的章节中安装更多组件,但首先,我们需要学习如何使用我们已经拥有的东西,以及如何在此基础上构建。
让我们从导入一些最重要的组件并在我们的应用程序中使用它们开始。因此,让我们转到我们的App.js
文件中的第三行。在我们导入StyleSheet
,Text
和View
的大括号之间,我们将添加Image
,TextInput
和Button
组件。
现在,我们的行将如下所示:
import { StyleSheet, Text, View, Image, TextInput, Button } from 'react-native';
让我们试着理解每个组件的目的以及我们如何在应用程序中使用它们。
核心组件
在我们继续之前,我们需要了解所有基本组件。这将帮助我们意识到如何混合它们,以便我们可以创建更大更复杂的组件。这也将使我们在规划应用程序时更容易。在第四章,您的第一个跨平台应用程序中,我们将创建一个功能齐全的应用程序,让我们为之自豪,让我们的朋友仰慕。以下列表显示了核心组件:
- 视图:
所以,让我们开始讨论 React Native 中最重要的组件:View。这个组件是所有组件的基础。View
组件非常重要,因为没有它,你无法构建 UI。作为其他组件的容器,如果你想以特定的方式进行样式设置或布局排列,这是你最好的选择。
让我们看一个基本的例子:
<View>
<Text>Hi! Welcome!</Text>
</View>
- Text:
我们已经使用了这个组件,它非常直接了当。我们可以使用这个组件在屏幕上显示文本。
让我们看一个基本的例子:
<Text>This is a text</Text>
- Image:
这很酷,因为它允许我们显示一张图片并按照我们想要的方式进行样式设置。
让我们看一个基本的例子:
<Image source={{uri: 'https://via.placeholder.com/300'}} />
- StyleSheet
我们可以通过再次查看我们的App.js
文件找到这个组件的使用示例。它创建了一个类似于 CSS 但具有更少样式规则的样式表。一旦你理解了它,就会发现它真的很容易使用,一旦我们到达我们的第一个实际挑战,我们将创建和设计我们自己的第一个屏幕时,我们将进一步进行样式设置。
让我们看一个基本的例子:
const styles = StyleSheet.create({
logo: {
backgroundColor: '#fff',
}
});
- TextInput
这是一个用于使用键盘将文本输入到应用程序中的组件。它包含了您希望从输入中获得的所有必要方法,比如onSubmitEditing
和onFocus
。别担心 - 当我们需要时,我们会使用所有这些方法。
让我们看一个基本的例子:
<TextInput placeholder='email' />
- Button
这个组件渲染一个处理触摸的基本按钮。
让我们看一个基本的例子:
<Button title='Press me' />
我相当肯定你已经注意到一些这些组件在它们的标签内有另一个单词。例如,对于我们的Image
组件,我们有单词“source”,它获取我们提供的链接以知道要显示哪个图像。那个词叫做prop,我们将在下一章中更多地了解它们。
在继续之前,让我们在我们的应用程序中使用这里的Button
和TextInput
的示例。我们这样做是为了练习,并且在使用这些组件后,习惯于在我们的设备上看到的东西。
让我们去编写一些代码,在我们的Text
组件下面显示我们的年龄和家乡,使用我们为TextInput
和Button
的示例。现在,主要函数将如下所示:
图 2.5 - 导入和使用新组件后的新代码
现在,让我们刷新并查看我们的模拟器/物理设备。我们会看到两个新的东西:一个输入框,如果按下,会打开一个键盘,您可以在其中写东西,以及一个蓝色的按钮,上面写着大写字母的文本。
我们还没有使用Image
组件,因为它需要样式才能工作。它需要告诉图像应该是什么大小。我们将在下一章更详细地讨论样式。
在这一点上,我们已经稍微详细地讨论了所有这些组件,并解释了每个组件的目的是什么。这些都是核心组件,因为它们涉及硬件功能,并且它们需要本地代码来运行。通过本地代码,我们指的是为 iOS 或 Android 编写的 Swift 或 Java 代码。开发人员正在构建和设计从这些组件继承的组件。
接下来,我们将学习如何创建组件以及如何组织我们的文件,以便我们永远不会忘记从哪里导入。
理解并创建您自己的组件
我们离我们的目标越来越近:创建一个跨平台的移动应用程序。为了使这成为现实,我们需要学习如何创建组件。
首先,让我们在项目的主目录中创建一个新文件夹,并将其命名为components
。在这里,我们将创建一个名为PersonalInformation.js
的新文件。
这个文件夹将作为所有我们组件的安全空间,一个我们可以随时导入我们组件的地方,就像我们通常会在网上找到的任何包一样。
因此,我们已经讨论了组件是如何创建的 - 它们是返回一堆 JSX 代码的 JavaScript 函数。但是,我还没有告诉你这些组件被称为功能组件,并且那里有不同类型的组件。
让我们通过在新创建的文件中编写所有必要的代码来构建我们的第一个功能组件。我们将创建一个组件,其主要目的是在屏幕上显示我们已经编写的个人信息。
我们将首先编写我们需要的导入。因此,对于这个组件,我们知道我们需要一个Text
组件。让我们继续导入。在文件开头写入以下内容:
import React from 'react';
import { Text } from 'react-native';
我们已经导入了 React,因为正如我在本章前面提到的,如果我们想要创建组件并使用 JSX,我们需要它。因为这是最重要和基本的导入,我们将把它放在我们代码的开头。之后,我们从 React Native 导入了Text
组件。
创建函数
让我们继续编写我们的功能组件,就像我们之前学到的那样:
function PersonalInformation(props) {
return <Text>some text</Text>;
}
之前,我们提到我们需要它来显示我们之前所做的相同信息(我们的姓名,年龄和家乡),但我还没有写过类似的东西。那是因为我们遇到了我们的第一个问题。
假设我们尝试写这样的东西:
function PersonalInformation(props) {
return (
<Text>Alin Gheorghe</Text>
<Text>24, Bucharest</Text>
);
}
在这里,我们会看到一堆红线在我们的代码下面。那是因为 JSX 不允许两个标签挨在一起,如果它们没有封装在一个更大的标签中。这就是View
派上用场的地方。所以,让我们也导入它。我们的第二行代码现在会是这样的:
import { Text, View } from 'react-native';
因为我们现在有了View
组件,我们可以在其中编写我们的函数,同时封装我们的Text
组件,就像这样:
function PersonalInformation(props) {
return (
<View>
<Text>Alin Gheorghe</Text>
<Text>24, Bucharest</Text>
</View>
);
}
有了这个,我们成功地创建了我们的第一个组件。但为什么我们要写相同的东西呢?我们已经在我们的主App.js
文件中有了这些信息。我们这样做是为了理解为什么组件是如此酷。
导出和导入我们的组件
在我们转到主文件之前,我们必须能够导入它。在我们导出它之前,我们不能这样做。有道理,对吧?让我们继续在文件顶部添加以下行:
export default PersonalInformation;
现在,你的代码应该是这样的:
图 2.6 - 我们在 PersonalInformation.js 文件中编写的代码
如果一切看起来正确,保存文件并转到App.js
,这样我们就可以看看组件最有用的特性:可重用性和可读性。
现在我们在App.js
中,让我们删除我们自定义组件中已经有的东西 - 我说的是显示我们个人信息的Text
组件。删除这些之后,我们可以导入我们的新组件。如果你迄今为止一直跟着做,那么导入这个应该很容易 - 你只需要在最后一个导入的下面添加另一行。在那里,你将导入你的组件,就像这样:
import PersonalInformation from './components/PersonalInformation';
现在,让我们使用这个组件来代替之前已经移除的Text
组件。这就像写<PersonalInformation />
一样简单。
现在,你的代码应该是这样的:
图 2.7 - 在所有修改后我们的代码
现在,让我们保存并查看我们的应用程序。正如你所看到的,没有什么改变,但我们已经清理了我们的代码,因为我们只需要写一行代码就可以得到两行输出,这样更容易理解。它更简单易读,因为我们立即知道Personal Information
组件将输出个人信息,而且,当我们寻找代码的特定部分时,很容易找到我们感兴趣的内容。
因此,如果我们想继续并从我们的主屏幕更改一些东西 - 比如说我们想改变我们的年龄,因为我们现在大了 1 岁 - 你可以很容易地看到你的个人信息在一个叫做PersonalInformation
的组件中,它是从一个叫做components
的文件夹中导入的。现在,你只需要进入那个文件夹,找到那个特定的文件,并修改文本。这很容易理解,对吧?
让我们再创建一个,这样我们就可以看到如何进一步简化和清理这个过程。
创建 Bio 组件
现在,让我们从App.js
中删除TextInput
和Button
组件。我们现在不使用它们,而且它们似乎与我们的个人信息无关。
在从主函数中删除它们之后,进入我们的components
文件夹并创建一个名为Bio.js
的新文件。这是相当不言自明的,但我觉得个人资料应该在顶部有一个简短的传记,只有你的名字和年龄。
我们已经知道我们想要导入一个Text
组件并创建我们的功能组件。我不会重复创建新组件的过程;相反,我会在Text
组件内写一些个人的东西。
重要提示
不要忘记现在不需要View
组件,因为我们这里只使用了Text
组件。我们只有一个 JSX 元素的事实意味着我们的组件可以轻松地返回它,而不需要一个封装它的父组件。
新组件应该是这样的:
图 2.8 – 我们的新 Bio 组件
让我们保存并将其导入到我们的主文件App.js
中。就像之前一样,我们在最后一个导入的下面创建一行新的,并写入以下内容:
import Bio from './components/Bio';
现在,让我们在我们的应用程序中使用它 - 我把它放在我们的<PersonalInformation />
组件下面。保存并刷新。现在你应该能够在设备上看到你的年龄和家乡下面的个人简介了。
这很棒,但我们要继续为每个组件都添加一个新行吗?想象一下有 30 个自定义组件。那将变成一个可怕的噩梦,让人难以忍受。
为我们的组件创建主文件
我们可以通过进入PersonalInformation.js
文件并从文件的最后一行删除default
关键字来轻松解决这个问题。对Bio.js
做同样的事情。你们两个文件的最后一行应该是这样的:
export Component;
当然,您将使用实际的函数名称,应该是PersonalInformation
或Bio
,而不是Component
。
因为我们已经这样做了,我们可以在我们的components
文件夹中创建一个名为index.js
的新文件。我们将在这里创建所有组件的列表,这将允许我们从一行中导入这些自定义组件。
在我们新创建的文件index.js
中,我们将导入我们的组件,然后导出它们。这听起来很容易,有点多余,但这很有用,因为这将使事情变得更清晰,更容易阅读和遵循。
在我们的索引文件中写完所有内容后,代码内部应该是这样的:
图 2.9 - index.js 文件中包含的所有代码
现在我们有了存储所有新创建的自定义组件的文件,让我们进入我们的App.js
文件,并按照应该编写的方式重写我们的导入。
重构我们的主要代码
在这里,我们必须删除我们的前两个自定义组件导入,并编写以下代码:
import { PersonalInformation, Bio } from './components';
这是我们正在做的唯一改变。很容易,对吧?看起来更好,更有组织。
现在,让我们删除未使用的组件,如Text
和Image
,并保存我们的文件。在进行所有这些修改后,您的App.js
文件将如下所示:
图 2.10 - 本章的最终代码
耶!我们已经完成了为我们的应用程序创建两个新组件,同时以一种任何程序员都会为我们感到自豪的方式组织代码。我不相信作业,但我相信锻炼的力量。现在轮到你了。创建尽可能多的组件。不要止步于简单的基于文本的组件;尝试并使用更多 React Native 提供的核心组件。不要害怕出错 - 这是学习的最佳方式:反复试验。
总结
在本章中,我们开始学习有关 Expo 基本文件结构的知识,以及所有这些文件是如何连接的,App.js
是我们应用程序的主要入口点,以及在启动时调用了哪个函数。之后,我们深入了解了 JSX 的主要概念,解释并将 JSX 与其他标记语言进行了比较,并理解 JSX 更像是 JavaScript 的扩展。
我们把理论放在一边,开始导入我们的第一个组件,同时讨论 npm 以及在创建更复杂的应用程序时我们将如何使用它。我们导入了 React Native 的核心组件并对它们进行了解释。使用它们感觉很舒适,而且相当容易,所以我们想,为什么不创建一个组件呢?创建了一个组件后,我们学到了更多关于文件结构以及如何将所有组件索引到单个文件中,这帮助我们进一步清理了我们的代码。
在下一章中,我们将学习 React/React Native 开发者的正确思维方式,并了解如何以 React 的方式思考。这将极大地帮助我们,因为当我们开始一个新项目时,它将节省我们的时间。如果从一开始规划正确,我们在构建项目时就不会遇到任何问题。
第三章:正确的思维方式
我认为我们一起学到了很多东西,希望您会对继续学习过程感到兴奋。在上一章中,我们更多地了解了 React Native 项目的工作原理以及每个文件或文件夹的作用。之后,我们开始学习JavaScript XML(JSX)以及如何使用它,并且实际上导入了我们的第一个组件。了解您每次创建新项目时要使用的核心组件,使我们开始理解并创建自己的组件。
本章将主要关注 React 架构以及在花费一些时间与框架一起后它如何让我们以一种特定的方式思考。首先,我们将从人们启动 React 应用程序的主要思想开始,或者在我们的情况下,启动 React Native 应用程序,然后我们将轻松过渡到 React 的更高级概念,如 props。
通过掌握 props 的概念,我们将能够为我们的应用程序添加更高级别的复杂性。这将使我们能够创建更酷的组件,释放 React 的更多功能。您会发现自己在几乎每个创建的组件中都在使用 props。
之后,是时候学习如何渲染列表以及如何使用它们来更改组件内的信息。听起来很不错,对吧?我们将能够根据我们想要在组件内进行的任何计算或我们需要展示的项目数量来显示不同的信息。
完成本章将教会您作为 React 开发人员的思考方式。这将为您节省大量时间,特别是在您首次启动任何项目时,了解如何正确地构建文件和代码非常重要。
我们将意识到程序员是如何以一种方式重复使用他们的代码,以至于您会一直对您的家人说:“写一次,到处使用!”
本章将涵盖以下主题:
-
以 React 思考
-
始终首先构建静态版本
-
Props 以及如何使用它们
技术要求
你可以通过访问 GitHub 上的github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
来查看本章的代码。您会发现一个名为Chapter 03
的文件夹,其中包含本章中编写的所有代码。为了使用该项目,请按照README.md
文件中的说明进行操作。
以 React 思考
让我们不要忘记 Facebook 为他们自己的项目创建了 React,并且它实际上在几乎任何类型的网站(或使用 React Native 的移动应用程序)中都有大规模的可扩展功能。如果 Facebook 可以在他们的平台上使用它,我们肯定可以在我们的应用程序中使用它。
为了充分利用这个框架,我们需要开始思考 React。当我开始我的编程之旅时,框架的概念对我来说似乎有点陌生。我不明白它被称为框架,是因为它带有特定的工作流程。嗯,这不是它被称为框架的唯一原因——它还因为它带有大量的功能和方法来使我们的工作更容易。
让我们想象一下,我们和朋友们一起合作开发一个应用想法,我们打算只是为了好玩而将其称为PiggyBank。这个想法是,我们需要始终跟踪我们用信用卡进行的所有交易。因此,基本上意味着我们将在我们的应用程序中的某个地方有一张卡来跟踪我们所有的交易。
我在 Adobe XD 中设计了下面的卡片,我认为这将帮助我们更好地可视化事物:
图 3.1 - 显示我们交易的卡组件
因此,我们的朋友设计了这个很酷的卡片,并要求我们在移动应用程序中实现它。很简单,对吧?我们已经看到如何通过使用 JSX 代码从上到下编写所有内容;除此之外,这张卡上有很多文本,这使得事情对我们来说更容易。你甚至可能认为我们不需要任何自定义组件,或者我们可能只需要一个。
嗯,这并不完全正确。这是我们的 React 知识发光的时刻,它帮助我们将一切划分为组件,使代码更容易、更清晰。但问题仍然存在...你如何知道在哪里画矩形,我们如何划分组件?React 推荐的一种技术是单一责任原则(SRP)。
提示
SRP 是一个编程原则,它规定我们编写的程序中的每个类都应该对程序功能的一个部分负责。这是SOLID的一部分,它是软件工程师创建更易于维护、灵活和可理解的代码的五个设计原则的首字母缩写。
使用这个原则,我们现在应该能够将卡分成组件。让我们拿这张卡,并为我们遇到的每个组件在上面画一个矩形,如下所示:
图 3.2 - 绘制矩形以划分组件
因此,我们提取了以下组件:
-
TransactionCard
(红色)- 包含整个卡和所有元素 -
TransactionCardHeader
(绿色)- 代表卡的上半部分,即名称和总花费的部分 -
TransactionCardList
(黄色)- 包含项目列表 -
TransactionItem
(粉色)- 显示交易和价格的单个项目
正如你所看到的,我们成功地将这张卡分成了四个不同的组件,它们将不得不以某种方式相互交流,并最终具有显示有关我们交易的信息的这一目的。
它们每个都只有一个单一的目的,这样就可以检查我们一直在谈论的 SRP。让我们编写它,暂时不要样式 - 我们将在下一章中进行。始终先构建静态版本。
首先,我们需要明白,我们可以开始开发一个应用程序的最简单方法是构建静态页面,然后分离一切并构建逻辑。为此,我们将基本上复制我们在图像中看到的一切,甚至文本 - 这就是为什么我们称其为静态版本。
让我们从打开终端,转到我们项目的文件夹,并输入以下命令来创建一个新项目:
expo init TransactionCard
我选择了TransactionCard
作为我的项目名称,但不要忘记你可以随意命名它。接下来,我们将选择空白的托管工作流模板,并等待它开始初始化我们的项目。一旦完成,让我们打开我们的集成开发环境(IDE)/文本编辑器,并查看项目。
我将打开App.js
并删除StatusBar
导入(对于这个练习来说并不重要),以及我们App
函数中View
组件内的所有内容。
让我们决定我们需要哪些类型的组件。很容易看到,这张卡只需要一个View
组件和一个Text
组件。幸运的是,我们已经在文件中导入了它们,所以让我们使用它们来构建卡的静态版本。
我们将从我们的设计开始,尝试将一切划分为容器。正如我们所看到的,这张卡片有两部分:上半部分是带有一般信息的标题,下半部分则充满了我们最近的所有交易。
所以,让我们写一下那些部分的代码,但首先,我们将专注于我们卡片的上半部分,如下所示:
图 3.3 - 我们卡片的上半部分
我们在这里看到了很多View
组件,但为什么呢?正如我们在上一章中讨论的那样,View
组件通常用于布局设计和将元素分组在一起。所以,我们将这两部分放在其中,然后为标题编写代码。如果我们保存并打开我们的模拟器,我们应该能够看到我们刚刚写的文本。
重要提示
不要忘记,如果你使用的是安卓模拟器,你首先需要打开安卓工作室,然后转到安卓虚拟设备(AVD)管理器。运行你的模拟器,然后从 Expo 仪表板启动应用程序。
现在让我们考虑一下我们的交易。我们有两段文本 - 左边是我们购买商品的公司名称,右边是交易的价格。我们该如何做呢?到目前为止,我们看到的元素总是以从上到下的列式样式对齐在屏幕上。
嗯,我们需要为此进行样式设置,但这里的重要一点是,我们需要理解这两个组件在某种程度上是相互连接的,所以我们必须将这些元素放在同一个View
组件中。
现在让我们这样做,准备好我们的组件来写所有这些交易。首先,试着自己做一下,然后看看你是否得到了和我一样的代码。
我将为每一行创建一个不同的View
组件,然后在其中添加文本,就像这样:
图 3.4 - 静态代码的其余部分
让我们打开我们的应用程序,看看一切的样子。现在,它只是一堆文本,以列的形式显示我们设计中的所有信息。不过,它看起来并不像一张卡片,但我们肯定可以看到与我们的设计相似之处,即以相同的顺序包含相同的信息。因此,我们现在创建了我们卡片的最基本版本。
下一步是最终将这棵大树分解成更小的组件。通过这样做,我们将使代码更易于阅读和理解,同时也更模块化,这基本上意味着我们可以在不同的卡片或其他需要时使用相同的组件。
分解我们的代码
所以,记得我们已经将设计分成了四个不同的组件吗?让我们创建一个components
文件夹,并为每个组件创建四个不同的文件,如下所示:
图 3.5 – 我们的文件夹中所有文件的创建
现在,是时候开始编写每一个了。所以,我们知道大卡片——或者第一个组件——应该能够分成两部分,List
和Header
组件。让我们将App.js
中的所有代码复制到TransactionCard
中,因为这是主要组件。所有的代码,我指的是只有在第一个View
组件内的代码。
创建完我们的函数后,我们将所有的代码粘贴到其中。让我们导出组件并查看它,如下所示:
图 3.6 – 以箭头函数形式编写的 TransactionCard 组件
我们将这个组件写成箭头函数,因为这样写起来更容易——至少在我看来是这样的——但老实说,如果你愿意,你甚至可以将其写成类。作为一个经验法则,通常只有在涉及状态时才使用class
,但状态是我们需要在后面的章节中更深入地讨论的东西。
所以,我们在这里有所有的代码,并且我们已经导出了我们的函数。一切顺利——现在,下一步是更深入地划分我们的组件。让我们将组件的头部部分移到它特定的文件中。
在我们将代码复制到TransactionCardHeader
组件后,让我们将该组件导入到TransactionCard
组件中,并在其中使用它,而不是复制的代码。我们应该对我们卡片的第二部分做同样的事情,也就是TransactionCardList
组件。让我们这样做,看看一切是什么样子。这是结果:
s
图 3.7 – 我们新创建的 TransactionCard 组件
好的 - 这看起来干净多了。如果我们将这个组件导入到我们的App.js
文件中,一切应该看起来和我们在开始对代码进行所有这些更改之前一样。
提示
不要忘记我们总是需要运行import React from 'react';
,这样我们才能使用我们所有的组件。组件需要知道它是一个组件,而不仅仅是文件中的随机写入。这个导入帮助我们的代码识别哪些对象是 React 对象,以及如何渲染一切。
一切都正常,对吧?如果你遇到任何问题,在继续之前停顿 2 秒钟,检查我们到目前为止所做的一切;也许你拼错了什么,或者在你的文件中忘记了一些导出。
如果一切正常,让我们进入我们的TransactionItem
组件。嗯,顾名思义,这是一个单独的项目,那是什么意思?正如我们在TransactionCardList
组件中所看到的,我们确实有几个不同的项目。我们要为每一个创建一个不同的组件吗?
实际上不是 - 我们实际上要创建一个单一的组件,根据接收的任何信息来改变显示的信息。听起来很酷,对吧?嗯,这个输入被称为 prop,每个组件在渲染时默认会得到一组 props,但它也可以接收我们创建的自定义 props。让我们深入了解 props,并学习如何在我们的卡片上下文中使用它们。
Props 及如何使用它们
那么,props 到底是什么?到目前为止,我们只使用普通标签来标识我们的组件,比如TransactionCardHeader
。然而,正如我们之前展示不同组件时所看到的,这些组件也可以有props,用于从更大的组件(父组件)传递信息到更小的组件(子组件)。
让我们进入TransactionCardList
并查看我们的代码。据我们所见,就组件的使用而言,有很多重复的代码。因此,我们可以看到这种模式从我们主要的<View />
标签中出现:
图 3.8 - TransactionCardList 组件准备分解为更小的组件
这个模式很容易看出来——我们有四个完全相同的代码片段,但里面写着不同的信息。基本上我们有四个View
组件的实例,里面有两个Text
组件。看到这种重复,我们可以清楚地意识到我们可以为这种特定情况编写一个外部组件。
让我们从在TransactionItem
组件内编写一个静态组件开始,看看我们如何在列表中实现它。
随便写一个这种模式的片段;我们只需要一个——否则,我们会有点违背单个项目的目的。代码应该是这样的:
图 3.9 – TransactionItem 的静态版本
现在,让我们使用这个组件来代替我们在列表中使用的所有模式片段。导入文件并用四到五个TransactionItem
的实例替换所有内容后,我们可以看到数据现在在所有地方都是Starbucks
和$ 10.12
。像疯狂重复一样并不是一个伟大的移动应用程序设计,对吧?
那么,我们如何改变这种情况?我们如何让组件显示不同的信息?通过使用 props。让我改变TransactionItem
组件,并看看 props 需要如何实现。代码应该是这样的:
图 3.10 – 将 props 实现到我们的组件中
现在,你的TransactionCardList
组件包含多个TransactionItem
的实例。如果你现在保存,除了$
符号外,这些组件上什么都不显示。为什么呢?
这一切发生是因为我们的组件在这些变量中没有存储任何东西。为了实际在屏幕上显示一些信息,我们需要从TransactionCardList
向TransactionItem
组件发送一些信息。让我们进入其中,并使用我们新更新的组件来在手机上显示正确的信息。
在TransactionCardList
组件中,找到我们的组件,并为每个组件添加以下 props,就像这样:
<TransactionItem name={"Starbucks"} price={10.12} />
在我们为所有组件添加了 props 之后,下一步是保存。我们将看到我们的模拟器会自动刷新——恭喜!我们成功地从一个父组件向我们的子组件发送了信息。
提示
从一个组件发送到另一个组件的所有信息都将正式地放在花括号内,就像我们通过为价格 prop 编写数字所看到的那样。即使字符串仍然可以放在花括号内,但它们对于发送信息并不是强制的,所以你甚至可以写name="Mircea"
。
现在,让我们试着稍微了解一下我们的代码。那么,我们的应用程序内部到底发生了什么?
当应用程序首次运行时,它直接转到App.js
并开始首先在那里编写的所有组件。对于这一点,那将是我们的TransactionCard
组件。
React 看到我们的组件实际上有两个不同的组件在其中,并开始渲染下一个组件。现在,其中一个组件实际上是我们的TransactionCardList
组件,其中包含所有我们的TransactionItem
组件。
因为第一个组件包含另一个组件,我们将第一个称为父,第二个称为第一个的子。所以,如果TransactionItem
是TransactionCardList
的子,试着想一想TransactionCard
是TransactionCardHeader
的什么。准备好了吗?TransactionCard
是TransactionCardHeader
的父,因为它包含了另一个组件。
现在,当 React 到达TransactionCardList
时,它将通过props向每个TransactionItem
组件发送一些信息。被发送的信息是一个看起来像这样的 JavaScript 对象:{name: 'Starbucks', price=10.12}
。
这就是为什么我们可以在TransactionItem
中将 props 作为函数的参数使用,然后通过点来访问我们对象的键,就像这样:props.name
。你一定想知道 React 如何知道如何处理所有这些过程,因为一个更复杂的应用程序可能有数百个组件嵌套在一起,同时在第一次渲染时向彼此发送 props。
问题是,React 首先渲染表面上的所有内容,当所有信息从一个父组件发送到子组件时,它才会渲染该信息。
现在,为了使我们的组件更加可用和可重用,我们必须使列表中的项目数量更加可变。对于更大的应用程序,我们必须问自己这样的问题:"如果用户进行的交易数量比五个更多或更少会怎么样?"; "如果我将来需要更多的卡片,但设计相同,我该如何重用这个卡片组件?"
使用 map 函数动态更改组件的数量。
首先,让我们看看如何输出尽可能多的TransactionItem
组件。我们将进入TransactionCardList
组件,并在函数外创建一个名为transactions
的对象常量数组。这个变量将包含所有项目所需的信息。让我们看看这是什么样子,如下所示:
图 3.11 - transactions 变量
一旦我们拥有了包含所有所需信息的这个变量,我们就可以对数组进行映射,并为每个项目输出一个不同的组件。如果你对 JavaScript 不太熟悉,这可能听起来有点混乱,但相信我,这其实非常简单。让我们删除<View />
组件内的所有内容,并用map
函数替换,如下所示:
图 3.12 - 在 TransactionCardList 组件内部使用的 map 函数
好了,这可能看起来有点奇怪。别担心,其实很简单。所以,我们在transactions
数组上使用了map
函数。这个map
函数遍历数组的每个元素,并使用其参数内的函数来输出某些东西。这个东西就是你要做的,利用这个很酷的函数。
重要提示
在 JSX 内部使用的所有外部代码必须放在花括号之间,以便 React 能够理解我们正在进行的操作可能会导致其他元素被输出以进行渲染。
基本上,由于map
函数,我们正在取数组的第一个项目 - {name: "Starbucks", price: 10.12}
- 输出一个TransactionItem
组件,并将我们数组中的值作为 props 传递。但我们还看到了key prop,我们都知道我们在组件内部并没有使用 key prop。每个子元素都需要一个 key,以便 React 可以跟踪它们并避免过度重新渲染。这是我们在使用这样的列表时需要理解的 React 规则之一。
但我们说过我们会更进一步,对吧?如果需要,我们需要多次使用这个卡片组件。看到transactions
只是一个随机变量坐在我们的TransactionCardList
组件中,也许我们可以将其作为一个prop发送?
让我们在我们函数的参数中添加props
关键字,并从transactions.map
更改为props.transactions.map
。如果我们现在保存,我们会得到一个错误 - 我们的组件期望一个名为transactions
的属性进来,但没有任何东西发送它。
我们必须从我们的父组件 - 即TransactionCard
发送这个。但尽管如此,这并不真正改变我们仍然无法正确使用这张卡片的事实,所以也许我们需要将这个属性添加到我们的TransactionCard
组件甚至。
让我们复制我们的transactions
变量并将其移动到我们的App.js
文件中。之后,让我们将transactions
属性添加到我们的TransactionCard
组件中,就像这样:<TransactionCard transactions={transactions} />.
现在,我们需要去我们的TransactionCard
组件,并使其能够接受这个属性,并将其进一步发送到我们的TransactionCardList
组件。我们的组件现在需要看起来像这样:
图 3.13 - 我们新创建的 TransactionCard 组件的版本
所以,我们一直从App.js
文件发送这些信息,一直到我们的TransactionItem
组件,最终显示这些信息。这对我们有什么帮助?嗯,现在,我们可以有多个具有不同交易的此卡片实例,或者甚至可以根据我们现在在App.js
文件中声明的常量来增加或减少交易的数量。我们可以完全使用不同的变量;我们可以有一个名为biggerTransactions
的不同数组,并将其传递给另一个组件。也许这个组件将显示你做过的最大交易。
这里重要的是,我们现在根本不需要触碰我们的卡片组件,而且我们仍然可以在显示不同信息的同时使用它。这比为我们需要的每一条信息创建不同的文件要容易得多,或者也许有一天你需要改变特定的信息,你开始浏览每个文件寻找那个特定的东西。现在你不必这样做 - 只需进入你的主文件并从那里更改所有信息。
让我们做一些作业。你会在我们的 GitHub 存储库的第三章
文件夹中找到答案。一直在我们的卡上使用相同的名称可能会变得无聊。通过允许自己使用同一张卡组件的多个实例,但用于不同的用户,可以使这变得更容易。完成后,去检查代码并将其与我的进行比较。看看你是否做了同样的事情!
总结
在本章中,我们更深入地了解了 React Native。我们学到了许多关于道具和 SRP 等新概念。我们应该能够开始思考使用基于道具的 React 方法论,甚至稍后使用状态。但理解所有这些对你成为真正的 React Native 开发人员是一个很大的进步。
关于道具的处理方式以及我们如何利用 React 的组件这一特殊功能来使代码更具可重用性和更清晰,你应该感到更加舒适。代码永远不会太干净,但同时要记住,有时候并不需要多层道具。也许你的组件只需要一层,或者根本不需要道具。只有在觉得这可能会让你的工作更容易时才使用这个功能。
我们还第一次创建了一个列表,并学会了列表的每个项目都需要一个键,有时甚至可以是我们数组的索引,但总会向我们的每个项目发送一个唯一的键。
在本章结束时,我们完成了一点作业,并对下一章充满了希望,届时我们将终于创建我们的第一个小应用程序来展示给朋友们看。
第四章:你的第一个跨平台应用
我们开始学习如何建立 React Native 开发环境。之后,我们继续学习 JSX、组件和属性。我们已经学到了很多,应该对未来有足够的信心。但如果你仍然觉得有些东西缺失,那么你是对的。我们还没有进行样式设计,也还没有构建一个真正的屏幕。
本章将围绕我之前想到的一个应用点子展开,该应用不断跟踪你的游戏历史。我们不会开始讨论服务器和数据库,因为它们超出了我们的学习范围,特别是因为我们有更重要的事情要学习。我们将从详细介绍我们应用的所有信息开始,同时使用我们在前几章学到的一切。
之后,我们将开始创建应用的静态版本,以便你了解在创建应用之前你的大脑需要如何思考。在前几章学到的所有原则将帮助我们更容易地理解我们的第一个真正的任务,所以如果有任何你仍然不确定的地方,回到前几章,看看你觉得哪些地方可以改进。
下一步是学习样式设计。我们将深入了解样式设计以及它在 React Native 中的工作原理。我们将了解 flex 是什么,以及如何在应用中使用它,同时找出我们可以使用的技巧,使开发更容易。
在为应用添加样式之后,我们将重构我们的代码,同时保持我们迄今为止构建的一切。这就是 Galio 将会发挥作用的地方,帮助我们意识到拥有已构建的组件是多么有用。我们将学习如何使用最重要的组件之一来构建布局,而不必担心为我们的容器创建不同的样式。
之后,我们将在手机上安装应用。这是一个单屏应用,所以我们只会用手机进行测试。我们还将学习一些基本技巧,以确保我们的应用在所有屏幕尺寸上运行顺畅。
一切看起来都很简单和容易,对吧?让我们直接开始构建我们的应用。本章将涵盖以下主题:
-
构建我们的第一个应用
-
创建你的第一个屏幕
-
让我们为它添加样式!
-
超级英雄,Galio
-
让我们在手机上安装它
技术要求
您可以通过访问 GitHub github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
查看本章的代码。您会发现一个名为Chapter 04
的文件夹,其中包含本章中编写的所有代码。要使用该项目,请按照README.md
文件中的说明进行操作。
构建我们的第一个应用程序
让我们开始讨论我们应用程序的主要思想以及我们将如何开始构建它。我们将称此应用程序为 MGA,这是 My Gaming History 的缩写。相当聪明,对吧?它只会有一个屏幕,并且将作为用户登录后的欢迎屏幕。我们假装用户已经登录到我们的应用程序中,因此我们只会编写主屏幕,而不会考虑授权,这是一个更高级的概念。
通过清楚地了解我们的屏幕需要看起来像什么,并描述我们组件的目的,我们正在为我们的开发建立一条清晰的道路。最后,如果我们没有做好所有这些准备工作,我们在编程过程中会遇到困难,我们不希望发生这种情况。
我们应该从设计开始,确定其主要目的,以及如何开始将屏幕分成组件:
图 4.1 - 我的游戏历史主屏幕
看起来不错,对吧?好吧,应该是的,因为这一次,我们将完全实现屏幕上的所有内容,甚至包括颜色和元素定位。毕竟,这是我们第一个完全创建的屏幕。
让我们试着考虑如何将所有内容分成更小的部分,这是 UI 创建中最重要的步骤之一。请记住,这是强制性的,因为如果我们只是试图在脑海中编写所有内容,而没有任何策略,我们将会遇到一些问题。
我们将使用方块来轻松识别屏幕上的每个元素,让我们来看一下:
图 4.2 - 组件划分
我在这里将整个屏幕分成了几个部分,并进行了颜色编码,以便您更好地看到它们:
-
主页(红色):我们的容器组件,也称为我们的屏幕。
-
欢迎页(蓝色):这将包含有关用户的所有基本信息,例如他们的姓名、级别和个人资料图片。
-
MostPlayedGame(蓝色):这将是一个容器,将接收关于最常玩的游戏以及图片的信息。
-
LastPlayedGameList(蓝色):这包含一个项目列表。
-
PlayedGameItem(绿色):这是显示最常玩的游戏和每个游戏所花费的时间的单个项目。
正如我们所看到的,我们在三个不同的组件中使用了相同的颜色。为什么呢?因为这三个组件在我们的主要更大的名为Home
的组件中同样重要。它们都位于我们的组件树中的同一级别。即使Home
组件是一个屏幕,它的定义方式与组件相同,当我们开始编码时,你会明白我的意思。
现在我们已经分割了我们的组件,准备继续并开始编写我们的应用程序。
创建你的第一个屏幕
一旦开发计划完成,我们知道每个组件需要放在哪里,以及我们的应用程序将会是什么样子,我们就可以创建一个新项目了。这个项目将为我们成为 React-Native 开发者的创造之路奠定第一块基石。
让我们开始创建一个新项目:
- 进入你喜欢的目录中的终端,并运行以下命令:
expo init mga
-
选择托管工作流的空白模板,并打开项目文件夹。
-
正如我们之前提到的,我们将有五个不同的组件,其中一个将是屏幕本身。所以,让我们创建两个不同的目录,分别叫做
screens
和components
。这样在有多个不同的屏幕时,更容易组织起来。
在开始编码时,始终在脑海中有一个基本结构是一个好的经验法则,因为你永远不知道何时可能想要为你的应用程序添加更多内容。
- 在我们的
screens
文件夹中,让我们创建一个名为Home.js
的文件。这将是我们的主屏幕,所以我们将开始编写组件的最基本代码。这只是一个功能组件的样板。还记得它们是如何创建的吗?我们在第二章中做过,React Native 的基础。现在,试着自己做一遍,一旦你成功做到了,就回到这里来:
图 4.3 - 基本的主屏幕,除了一个视图组件外没有任何内容要渲染
- 完成后,我们必须转到我们的主文件
App.js
。
在这里,我们将从主文件中删除所有不必要的内容。我们不需要所有的样式,也不需要StatusBar
导入,甚至不需要从 React Native 导入的任何组件。
- 删除所有内容后,我们可以在
React
导入之后立即导入我们的组件,并将其放入我们的主App
函数中。
所以,我们的新组件现在应该看起来像这样:
图 4.4-删除所有不必要代码后的 App.js 文件
你可能会想,“嗯,那个奇怪的<>
语法是什么意思?”。那是Fragment的简短语法,这是 React 的一个特性。这样做是为了不向你的组件树添加更多不必要的节点。我们可以使用<View />
组件,就像我们在之前的例子中看到的那样,但是通过使用Fragment,我们创建了一个包装器,而不是一个不必要的包装组件,因为我们不会在我们的主文件中进行任何样式设置。
如果这仍然造成一些问题,你可以很容易地将你的<Home />
组件包装到一个<View />
组件中。
现在我们在这里,让我们看看我们的components
文件夹,并创建我们将要使用的所有必要文件。
-
创建四个新文件,分别命名为
WelcomeHeader.js
、MostPlayedGame.js
、LastPlayedGameList.js
和PlayedGameItem.js
。 -
让我们对我们新创建的每个文件都做与
Home.js
相同的事情。你甚至可以从Home.js
文件中复制代码,然后粘贴到每个文件中;只是不要忘记将名称从Home改为你的组件名称。
现在我们已经初始化了所有的文件,我们准备开始进行代码移植。我们应该能够看到这些组件与上一章的组件之间的某些相似之处。几乎是一样的东西,所以你应该对我们如何继续有一个想法。
我们将从WelcomeHeader.js
开始,然后查看我们的每个文件。如果你现在启动你的应用程序,你会看到一个空白的白屏。我们暂时忽略这一点,只是用一些基本的静态代码来勾勒我们的应用程序,这样当我们开始样式化时,就有一定的基础。
打开你的文件,这样我们就可以开始添加一些新元素。我们从设计中可以观察到什么,我们可能需要在我们的组件内部?嗯,首先,有很多文本,但我们还需要一个个人资料图片(在组件右侧的圆圈)。知道了这些,我们现在可以开始导入所需的组件,所以继续编辑我们导入View
组件的第二行,使其看起来像这样:
import { View, Text, Image } from 'react-native';
还记得我们说过如果它们在同一行上,我们应该将组件分组吗?当我们开始样式化时,这将使事情变得更容易,因为这些组件在同一水平线上。
所以,我首先在我们的主View
组件内添加了另一个View
。之后,我将添加坐在同一行上的组件:我们的欢迎消息和我们的个人资料图片。在这个View
组件下面,我们将添加另一个Text
组件,它将呈现我们的Level
:
图 4.5 – 我们的 WelcomeHeader 的静态版本
在第二章,React Native 的基础中,我们讨论了Image
需要一个源来工作。这就是为什么我们使用了source
属性并传递了一个占位图像链接。使用占位符更容易,因为我们不需要浪费时间搜索图像,当我们的主要目的只是编写一个静态版本时。
让我们继续并开始编写我们的下一个组件:MostPlayedGame
。正如我们在之前的组件中所看到的,我们在这里需要与之前的组件相同的东西。所以,让我们导入一切并在我们的组件内使用它。一旦你做到了,我们将使用我们的组件来显示所有信息。现在,你的代码应该是这样的:
图 4.6 – 我们的 MostPlayedGame 的静态版本
我在我们的占位链接中写了300
而不是75
,因为这会改变图像的宽度。但除此之外,这很容易理解。
在这一点上,我们会注意到一些非常有趣的事情。我们有一个遵循我们习惯的相同模式的列表。这是一个项目列表,每个项目都呈现了我们玩过的游戏以及我们玩了多少。我们可以复制我们以前使用的相同模式,它将工作得非常好:
图 4.7 – PlayedGameItem 组件
我相信你还记得如何从父组件传递props
到子组件是多么容易。如果我们已经知道某些组件应该如何编码,我们就不应该浪费任何时间。现在,是时候创建列表了,就像上次一样,但现在,我们在其中有另一个元素,一个Text
组件作为我们组件的标题:
图 4.8 – 我们完成的 LastPlayedGameList 组件
我们走得很快,但那是因为我们已经经历过这个,所以你应该明白这里发生了什么。我们代码目前的问题是我们没有向我们的项目发送任何信息。我们没有map
函数需要运行的数组。正如你所看到的,数组来自props
,所以我们的LastPlayedGameList
组件期望一个名为games
的prop,带有一个数组,这样它就可以开始渲染我们的游戏列表了。
让我们进入我们的Home屏幕组件并设置一切。首先,我们将开始导入屏幕所需的所有组件。我们只需要这四个组件中的三个,因为其中一个是PlayedGameItem
,它已经被我们的LastPlayedGameList
组件使用和渲染。导入它们很容易,如下所示:
import WelcomeHeader from '../components/WelcomeHeader';
import MostPlayedGame from '../components/MostPlayedGame';
import LastPlayedGameList from '../components/LastPlayedGameList';
在导入我们需要的一切之后,是时候将组件放置在它们将出现在屏幕上的顺序中,放在我们的主View
标签内:
图 4.9 – 我们的 Home 组件与其中的其他组件
正如你所看到的,我已经传递了我们需要的games
数组给我们的列表,在我们的组件之上。让我们创建一个数组,这样我们就有东西可以传递给我们的LastPlayedGameList
。
首先,自己尝试一下 - 记住我们需要一个带有game
和time
键的对象数组。一旦你自己尝试了这个,回到这里看看下面的代码:
图 4.10 – 游戏对象准备发送到我们的列表组件
这并不难,对吧?在这里,我们编写了整个静态屏幕。我相当肯定,如果您回到模拟器,应该能够看到屏幕上弹出了一些东西。如果没有任何错误,那么我们应该可以继续。如果您在屏幕上遇到任何错误,或者仍然看不到任何东西,请尝试重新阅读一切,并确保您没有漏掉任何单词。我会说,70% 的错误是在开发阶段抛出的,因为我们通常会在变量中漏掉一些字符(不要引用我,这只是个人经验)。JavaScript 作为一种弱类型语言,意味着您不必指定变量中将存储什么类型的信息,因此我们不必像Java或C#开发人员那样担心错误地定义变量,但与此同时,变量在任何地方使用时都需要具有相同的名称。
现在,让我们开始让它变得漂亮起来。
让我们来设置样式吧!
在我们开始为应用程序设置样式之前,我们应该了解 React Native 中的样式工作原理。如果您之前有 React 的经验,您会知道样式是通过 CSS 完成的。然而,在 React Native 中,我们不能使用 CSS,所以一切都是通过 StyleSheet 完成的。
StyleSheet 是由 React Native 团队创建的。在这里,您有类似 CSS 的规则,但一切都是通过 JavaScript 完成的。
当将这些样式对象传递给我们的组件时,我们通过一个名为 style
的属性来进行。让我们首先直接为我们的Home屏幕创建一些样式。
我们可以通过两种方式将这些对象传递给我们的组件 - 我们可以直接在组件中编写它们,或者通过一个新的 StyleSheet 实例传递它们。让我们在行内编写,并为屏幕更改我们的背景颜色。通过转到我们的 Home.js
文件,我们可以为包裹其余组件的 <View />
组件添加 style
属性:
图 4.11 - 为我们的组件添加行内样式
在添加这个并保存文件后,您应该能够看到整个背景颜色如何改变为该十六进制颜色。现在,我们的背景颜色与设计图像的相同。这很酷,对吧?这也很容易阅读,因为本质上这就是 CSS,只是写法有点不同。
如果我们要编写 CSS,我们会说,例如,background-color: 'red'
,但因为在 React Native 中一切都是 JavaScript,我们无法用破折号在字符之间写变量或对象键,所以我们使用驼峰命名法。
但是关于内联样式存在一个问题;我们可能会有成千上万种样式,在这种情况下,我们会忘记一些东西在哪里,或者如何在我们的应用程序中更改某些东西。这就是为什么我们应该尝试使用更清晰的方式来编写样式。
让我们删除我们的内联样式,并通过在View
旁边添加StyleSheet
来更改导入,就像这样:
import { View, StyleSheet } from 'react-native';
现在我们已经导入了StyleSheet
,我们准备创建一些样式。为此,我们将使用.create()
方法。这个方法将返回一个带有所有必要样式信息的对象:
图 4.12 - 样式对象
现在,我们可以回到我们的<View />
组件,并通过使用style={styles.container}
将样式注入到我们的样式属性中。现在,一切应该看起来和我们内联样式时一样。我建议使用.create()
方法来添加样式,因为它更清晰,更容易阅读。
现在,你可能会对flex
有一些疑问。我的意思是,你在那里看到了它,但你还没有意识到那个属性到底在做什么。这些问题应该延伸到“我能通过以驼峰命名法编写所有 CSS 规则来在 React Native 中使用吗?”
问题是 CSS 有两种布局选项:网格和flexbox。虽然你无法在 React Native 中使用网格,但整个布局都是基于 flexbox 的,所以你可以使用所有 flexbox 的规则。
你几乎可以以某种形式轻松地使用 CSS 中的所有规则。如果有一些你觉得在驼峰命名法中写不起作用的东西,那就去谷歌一下这个规则。你很容易找到如何使用几乎每一个规则。
flex: 1
规则意味着“让 <View />
组件尽可能占据尽可能多的空间”,所以我们的主页现在是屏幕的全宽和全高。
让我们向我们的容器对象添加一些新规则:
- 添加
paddingHorizontal: 32
和paddingVertical: 64
。这将为我们创建一些美丽的呼吸空间,以便我们继续为我们的组件添加样式。
让我们从我们的WelcomeHeader
组件开始。
-
我们将首先将
StyleSheet
添加到我们的导入列表中,然后创建styles
对象。 -
之后,我们将创建
upperSide
,profilePicture
,welcomeText
和levelText
样式。 -
我们仍然看不到我们的图片,所以让我们给它一个
宽度
和高度
为55
。为了使它变成圆形,我们将给它一个borderRadius
为55/2
。 -
现在,我们将通过
style
属性向我们的图片添加profilePicture
样式。 -
对于我们的
welcomeText
和levelText
,我们需要指定fontSize
和颜色,所以让我们继续做。我会用38
作为welcomeText
的字体大小,18
作为levelText
的字体大小。文本的颜色将设置为'#707070'
。
我们将继续添加规则,直到我们的WelcomeHeader
组件看起来像我们设计案例中的样子。一开始你可以自己试试。一旦你做到了,看看下面的代码,看看你是否得到了与我这里类似的东西:
图 4.13 – 我们完全样式化的 WelcomeHeader 组件
有了这个,我们成功地为我们的WelcomeHeader
组件添加了样式。我使用justifyContent
将图像和文本推向相反的方向,我还指定了flexDirection
,因为默认情况下,所有组件都以列的方式呈现。然而,对于这个特定的例子,我们需要一行。
我们不会在这里进一步查看样式规则,因为你可能需要通过练习自己去发现它们。所以,我现在最好的建议就是继续前进,创造一些东西。从你每天使用的应用程序中获得灵感,并创建一些外观类似于你选择的东西的组件。尽量重现尽可能多的组件,并看看哪些对你有视觉吸引力。过一段时间,这将成为你的第二天性。
如果你记不住某个规则,或者无法想出某种方式来以某种方式对某个东西进行样式设置,不要沮丧。事实上,大多数程序员都会忘记,他们中的大多数人会在 Google 上查找非常基本的东西。你现在最重要的事情不是因为某些事情不起作用而感到沮丧,而是把它看作一种挑战——这将百分之百地提高你作为开发者的能力。
我们将停止样式部分,因为我们已经为一个组件做了,我觉得我可以向你展示一些可能会改变你对样式的看法的东西。这是我们从现在开始创建应用程序时将开始使用的东西:加里奥。
超级英雄,加里奥
我们在本书开头谈到了 Galio。我们讨论了为什么要使用它,以及它如何为您的应用程序带来价值。现在,是时候使用它,看看这个 UI 库到底是什么。
现在,我们需要为我们使用的每个元素编写不同的样式对象。Galio 可以通过使用props
来解决这个问题,这将帮助您在开发应用程序时为您的代码添加样式。
让我们从安装 Galio 到我们的应用程序开始。为此,我们需要打开我们的终端并运行以下命令:
npm i galio-framework
这将安装最新可用版本的 Galio 到我们的项目中。现在我们已经安装了 Galio,让我们从中导入一些组件到我们的WelcomeHeader
组件中。
让我们去我们的import
部分并写下以下内容:
import { Block, Text } from 'galio-framework';
如果你已经写下并保存了你的文件,那么会出现一个错误。那是因为我们从react-native
和galio-framework
都导入了Text
。从react-native
中删除它,一切应该再次正常工作。
哦,好吧,什么都没有改变。这是因为 Galio 的Text
组件只是扩展了您通常的Text
组件。但是,它附带了新的 props,可以让我们删除某些样式。
让我们删除我们两个Text
元素上的style
属性,并改为添加color="#707070"
。现在,我们的文本很小,但它们是相同的颜色,这很酷。这意味着我们的 props 正常工作。如果我们想要改变字体大小,我们只需添加一个 prop。对于我们的第一个Text
元素,我们将添加h3
,代表标题 3,而对于我们的第二个Text
元素,我们将添加p
,代表段落。
现在,如果我们点击保存,我们会看到我们的文本元素突然有了不同的大小,一切看起来都很好。我们现在可以删除未使用的样式对象;也就是welcomeText
和levelText
。
让我们继续看看是否可以删除更多。我们应该用Block
组件替换包裹我们的Text
和Image
元素的<View />
组件。
现在,让我们向我们新实现的Block
元素添加以下 props:row
和space="between"
。因此,我们可以从我们的styles
对象中删除upperSide
对象。现在,一切看起来都一样,但代码更少,更容易注意到。
Block
组件与View
组件相同,但它包含了许多可以简化我们开发过程的 props。
一旦我们替换了它,让我们也替换另一个View
元素。我们还将从导入中删除它,因为我们不再需要它:
图 4.14–我们的 WelcomeHeader 组件,其中包含了新实现的 Galio 元素
我们现在了解了 Galio 的工作原理,我们将看到它将如何帮助我们继续开发这个应用。所以,让我们继续开始修改其余的组件。
让我们进入我们的MostPlayedGame
组件,并开始从 Galio 中导入我们需要的任何内容。同样,我们需要使用Block
和Text
。在导入这两个组件之后,我们可以从react-native
中删除View
和Text
的导入,因为我们不再需要它们。但是在替换View
元素之前,不要立即保存,我们需要将函数内部的View
元素替换为Block
元素,就像之前一样。现在,您可以继续保存文件,您将看不到任何更改。这很完美–我们现在可以开始为这个组件设置样式了。
让我们继续为我们的Text
组件添加以下 props:size={15}
和color="#707070"
。这将改变我们文本的字体大小和颜色。
现在,我们需要从react-native
中导入StyleSheet
并使用它来为Image
设置样式,以便它可以在我们的屏幕上呈现。我们将使用StyleSheet.create
方法创建一个新的styles
对象,并在其中放置image
对象。
之后,我们还将添加一个container
对象,以便我们可以在组件之间创建一些空间。这将在我们的Block
元素中使用。
我们的新styles
对象应该看起来像这样,并具有以下值:
图 4.15–用于我们的 MostPlayedGame 组件的样式
在写下所有这些并将我们的styles.container
和styles.image
对象链接到正确的元素(Block
元素和Image
元素)之后,我们可以看到我们的屏幕开始越来越像我们在本章开头看到的设计。
顺便说一句,我在我们的容器样式中添加了 4px 的paddingBottom
,只是因为我觉得我们的Text
元素需要一些呼吸空间。我们也可以为Text
创建一个新的样式,并在周围创建一些填充。写样式没有正确的方法,只要它的目的,也就是显示你想要显示的内容,得到尊重,那么尽情地玩耍和尝试吧。
不要忘记,我们通过style
属性将样式链接到每个元素。
噢,好吧 - 我想随着我们已经经历了这么多,使用 Galio 和样式变得更容易了,所以我会休息一下,让你来为其余的组件添加样式。一旦你完成了,回到这本书上来,看看我们是否采取了相同的路径,通过比较你的结果和我的结果。也许你的看起来甚至比我的更好,同时代码更清晰,如果是这样的话,你今晚应该奖励自己。
你完成了吗?很好 - 让我们继续吧!让我们跳到我们的LastPlayedGameList
组件。这应该很简单,所以让我们从galio-framework
中导入我们的Block
和Text
组件,同时完全删除我们从react-native
中的导入。没错 - 我们不再需要那些了。
然后,我们将把View
元素改为Block
元素。在这里,让我们也添加一些内联样式;即style={{ marginTop: 32 }}
。我们添加了这个来在组件之间创建更多的空间。
现在,让我们去我们的Text
组件,并添加color="#707070"
和size={18}
属性。就这样,我们完成了。我们创建了这个组件非常快,对吧?嗯,样式并不难,特别是当涉及到 Galio 时。
让我们继续我们的最后一个组件,PlayedGameItem
。这个将和前一个一样。我们将从galio-framework
中移除react-native
的导入,同时添加Block
和Text
的导入。
现在,让我们用我们的新Block
元素替换View
元素,并为其添加row
,space="between"
和style={{ marginTop: 16}}
属性。之后,我们将为我们的两个Text
元素添加color="#707070"
和size={14}
属性:
图 4.16 - 在添加 Galio 和样式后,我们的全新组件
有了这个,我们就完成了。保存你的文件,看看你的模拟器。它看起来就像我们想要的样子。在继续之前,花点时间为屏幕增添更多特色。将图片更改为你想要看到的任何图片 - 也许添加一个个人资料图片和你最喜欢的游戏的图片。
还记得我们是如何使用 props 将信息从父组件传递到子组件的吗?你可以做同样的事情,改变我们的WelcomeHeader
中的名字,甚至可以更模块化,将所有信息从Home屏幕发送到你的组件中。
现在我们已经完成了对我们的应用进行样式设置,让我们看看如何在我们的手机上使用它。
让我们在手机上安装它
我们在第一章中讨论了为什么 Expo 很棒,React Native 和 Galio 的介绍,我认为 Expo 的人们在创建这个框架时做得很好。智能手机的问题在于你不能很容易地在手机上安装应用。
Android 比 iOS 更开放,你可能可以将一个.apk
文件导出到你的手机上。然而,iOS 不允许你这样做。
当然,我们可以使用TestFlight,这是苹果的一个服务,允许你与其他测试人员测试和分享你的应用。但这对我们没有帮助,因为谁会在他们的手机上安装 TestFlight 来看你的一个屏幕应用,特别是当你需要一个苹果开发者账号时?
Expo 为我们提供了一个名为Expo Go的小应用。你可以在App Store和Google Play Store上找到它。下载并登录,或者如果你还没有账号的话就创建一个新账号。在这里,你可以为你的项目创建一个构建,以便以后测试。通过这样做,我们可以向朋友展示我们的应用,而不用太担心其他障碍。
在 Expo 上发布项目很容易;我们只需要按照一些步骤。让我们通过进入终端并按下Ctrl + C来关闭我们的开发服务器;然后,输入expo signin
并按Enter。会出现一条消息,要求你输入用户名和密码。如果你还没有账号,就跳转到 Expo 的网站上创建一个。在输入用户名和密码后,你应该会得到以下回应:成功。你现在以 YOUR-USERNAME 的身份登录。
现在,如果我们想要使用 Expo 发布我们的应用程序,有两个选项可供我们使用。我们将在接下来的章节中讨论它们,因为错误可能随时发生。如果遇到错误,最好尝试另一种方法。
通过 Expo 开发者工具发布
现在您已经登录,让我们通过在终端中输入expo start
并按下Enter来再次打开我们的服务器。
开发服务器已启动,并且应该在您的浏览器中加载一个包含 Expo 开发者工具的新选项卡。请记住,在第一章中,介绍 React Native 和 Galio,我们展示了所有可用的选项;让我们点击发布或重新发布项目到互联网:
图 4.17 - 单击发布按钮时显示的所有信息
现在,您的应用程序应该已经发布,这意味着您可以在手机上的 Expo Go 应用程序中打开您的应用程序。看?很容易!继续向朋友们展示吧!
通过 Expo CLI 发布
现在,第一种选项可能不适用于您,或者您遇到了错误的可能性。有时,错误就会发生,甚至可能不是您的错。在这种情况下,停止我们的开发服务器,并在终端中输入expo publish
命令。将会出现一条大消息,说明它将开始捆绑您的应用程序并准备发布。过一会儿,您将看到它已成功发布到 Expo。
现在,您的应用程序已经准备好向世界展示了。嗯,有点。您可以登录到 Expo Go 应用程序,然后在个人资料选项卡下的已发布项目类别中查看您的应用程序。问题是……来自互联网的其他人可能会在 Expo 网站上看到它,并在他们的计算机上下载它,但您的朋友们无法在他们的手机上下载该应用程序。这是因为我们还没有在官方商店上发布该应用程序。它甚至不在商店上 - 它保存在云端供其他 Expo 用户查看,当然,您随时都可以访问它。
恭喜!我们终于创建了我们的第一个完整屏幕。我希望您感觉良好,因为还有更多的知识将使开发变得更容易,更有趣!
摘要
在本章中,我们经历了为我们的应用程序创建屏幕的过程。我们拿到了一个设计文件,看了一下,然后重新设计了没有功能的设计。这对任何人的职业生涯来说都是一个很大的进步,因为这是你第一次完成一个应用想法。我认为你应该给自己鼓掌,并意识到你在这里所做的并不容易。很多人甚至不会尝试开始学习这个,但你做到了。而且,你甚至创建了一个完全样式化的屏幕。
一旦我们了解了样式,Galio 就出现了。我们学会了如何使用 Galio 构建布局,这让我们的工作变得更容易。我们仍然没有完全摆脱样式的部分,但我们永远不可能不给东西加上样式。毕竟,样式是有趣的。通过使用 Galio,我们看到了如何轻松地排列元素并创建快速原型。
在本章末尾,我们看了两种不同的发布应用想法到 Expo Go 的方法,这是一个移动应用程序,可以帮助我们在不实际推送到商店的情况下玩我们的项目。这很酷,我敢打赌你的朋友和家人会因为看到你取得的进展而感到非常高兴。
现在,是时候进入下一章了,我们将讨论使用 Galio 的好处。
第五章:为什么选择 Galio?
在上一章中,我们创建了我们的第一个屏幕。在用普通的 React Native 代码创建它之后,我们继续导入了一些 Galio 组件,这些组件帮助我们以更加简单和轻松的方式进行样式和布局的创建。
本章将更好地介绍 Galio。我们将学习如何使用它,以及为什么大多数程序员寻找类似 Galio 这样的用户界面(UI)解决方案来解决他们大部分的开发过程。正如我们在上一章中看到的,仅仅使用核心的react-native
组件意味着代码变得非常庞大且难以维护。Galio 组件打包了许多不同的属性,使生活变得更加轻松。
我们还将了解使用开源库的好处,以及如何创建一个愿意互相帮助的人群社区,以及你如何可以根据自己的意愿为库增加价值。
这次对话将打开许多你以前从未想到的新大门。它将创造一种新的心态,扩展你对开发者真正是什么以及他们如何交流的视野。
在本章中,我们将涵盖以下主题:
-
使用 Galio 进行美丽的移动应用开发
-
在你的应用中使用 Galio
-
发现 Galio 的好处
在本章结束时,你应该能够理解人们为什么选择 Galio 来快速开始他们的项目。你将了解如何安装它并在你的应用中使用它,以及某些组件在你的应用中扮演的角色。了解某些组件肯定是有帮助的,但不要躲避——所有程序员都使用 Google 来发现他们的问题的解决方案,我强烈鼓励你在需要进一步解释或在时间过程中发生变化的情况下也这样做。
技术要求
你可以通过访问 GitHub 上的github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
来查看本章的代码。你会发现一个名为Chapter 05
的文件夹,其中包含了本章中我们编写的所有代码。为了使用该项目,请按照README.md
文件中的说明进行操作。
使用 Galio 进行美丽的移动应用开发
我们有很多移动应用程序的例子,它们看起来并不真的好看。许多不同的社交媒体应用程序都是由随机的人创建的,他们认为他们会像 Facebook 一样大获成功。我发现大多数这些应用程序的最常见问题是设计问题,除了你总是会在初学者开发的应用程序中发现的错误。
布局很快就创建好了,他们并没有注意到他们的设计可能会因为用户体验(UX)而受到影响。他们认为只要他们有一个好主意,就不需要注意其他任何事情。
我不同意。我真诚地相信只要设计时尚,用户体验一流,你就可以销售任何东西。我之所以这样认为,主要是因为我通常使用1 分钟规则。这是我为自己创建的一些个人规则。基本上,一旦我安装了一个应用程序,我只需要大约一分钟的时间来尝试看看这个特定应用程序发生了什么。
为什么要等一分钟呢?嗯,我们使用移动应用程序是因为我们希望事情变得快速和易于使用。我们总是在寻找移动应用程序的替代方案,因为我们希望更轻松和更快速地访问我们的网络相关活动。我们希望能够查看一些信息,甚至可能做一些活动。如果我在 1 分钟内无法弄清楚如何使用你的应用程序,那么我会卸载它。
这对我们应该如何构建我们的应用程序有什么影响?我们应该时刻考虑用户,并且只使用足够的信息,让他们不必考虑如何使用我们的应用程序。
Galio 很方便,因为它使用相同的流程——简单,快速和直接。你不必太在意按钮应该有多宽,或者你的应用程序中像素大小应该是多少。它预先装载了你设计和制作最佳想法所需的所有工具。
让我们先看看按钮的外观和我们为它们提供的许多不同样式。我们几乎在应用程序的任何情况下都会使用按钮,所以我觉得这将是一个很好的开始。看一下以下的截图:
![图 5.1 – 应用程序中显示的按钮
]
图 5.1 – 应用程序中显示的按钮
正如你所看到的,我们有很多不同的方式来显示按钮,从明亮的颜色和阴影到无阴影和简单。你可以有方形,甚至——直截了当地——只是一个圆圈和一个图标。
让我们看看这在我们的应用程序中如何轻松实现。这是图 5.1 中显示的按钮的代码:
图 5.2 - 图 5.1 中按钮的代码
因此,就我们所看到的,我们为所有事情都有 props。有些需要我们更多或更少的工作,但与此同时,只需从组件内部编辑一切的便利性每次都是值得的。只是因为我们想要将按钮大写并使其始终大写,我们可以使用capitalize
prop。或者也许我们希望文本始终是大写的;那没问题——我们也有一个 prop。这真的使得开发过程变得非常容易,任何人都可以轻松使用。
我们已经讨论过应用程序应该对我们的用户看起来和感觉良好。但这也应该转化到我们的开发过程中。这就是为什么我真诚地相信,清晰而美丽的代码几乎总是等于一个美丽的产品。
现在,让我们来看看 Galio 包中另一个很酷的组件——Accordion
。你永远不知道何时需要一个外观漂亮的手风琴来为你的内容创建更多的空间。你可以在这里看到它的表示:
图 5.3 - 手机屏幕上显示的手风琴组件
这个组件非常容易使用。它需要一个具有指定高度的View
(或Block
)组件和定义组件内部内容的对象数组。以下是与此组件相关的代码:
<Accordion dataArray={data} />
这基本上对于任何人来说都应该很容易配置和在需要时使用。数组内的对象必须具有特定的键,以便我们的组件可以识别和理解在哪里放置内容。对象可能看起来像这样:
{title: "2nd Chapter", content: "Lorem ipsum dolor sit amet"}
看起来并不那么难,对吧?如果我们想使用一个图标,就像我们在创建第一个项目时看到的那样(在第一章**,React Native 和 Galio 简介),我们所要做的就是在对象内添加icon
键,其中将包含我们想要使用的指定图标的name
、family
和size
值。
这基本上是 Galio 所有组件的构建方式。它们直截了当,外观良好,可以立即用于在几秒钟内创建新应用程序。
在继续之前,我们应该查看另一个组件,看看使用 Galio 自定义和使用复选框有多容易。看一下以下截图:
图 5.4 - 复选框组件在您的屏幕上显示
如果要使用核心 React Native 组件创建这个布局,看起来会很复杂,但我们很幸运,因为 Galio 使它变得如此简单,只需写一行代码,如下所示:
<Checkbox color="primary" flexDirection="row-reverse" label="row-reverse checkbox" />
<Checkbox color="info" initialValue={true} label="initialValue set to true" />
<Checkbox color="error" initialValue={true} label="different icon" iconFamily="font-awesome" iconName="plane" />
<Checkbox color="warning" labelStyle={{ color: '#FF9C09' }} label="labelStyle used here" />
<Checkbox color="success" image="https://images.unsplash.com/photo-1569780655478-ecffea4c165c?ixlib=rb-1.2.1" flexDirection="column-reverse"/>
正如我们所看到的,我们可以在我们的组件内设置颜色、方向,甚至图标。它们在没有任何修改的情况下写入和显示都很美丽。这让我们为我们的库感到自豪,因为我们真的为它的外观感到自豪。
现在,让我们看看使用Block
组件创建基本布局有多容易。我们将仅在布局设计中使用此组件,并为每个方块着色,以便更好地理解每个元素实际显示的内容。
在您的应用程序中使用 Galio
现在,让我们看看 Galio 的实际效果。Galio 最大的特点之一是我们的Block
组件,它基本上是一个带有超能力的View
组件。为什么说是超能力呢?我们可以轻松使用此组件来创建布局,并且只需使用 props 就可以轻松地为所有内容设置样式。
因此,让我们付诸行动,看看使用Block
组件创建基本布局有多容易。我将逐步向您展示使用Block
的最常见方式,并演示布局的最常见排列方式。您可以在我们的 GitHub 存储库中找到该项目,或者可以跟着我一起编码。
我将从创建一个新的 Expo 项目开始。之后,我将通过命令行安装 Galio,写入以下代码:
npm i galio-framework
现在我们已经安装了所有内容,我将跳过整个组织文件的过程,因为我们只是用于演示目的。因此,我们将直接将代码编写到我们的App.js
文件中,即我们的入口点 - App
函数中。
我们将通过import
函数在其他导入项下导入我们的Block
组件,就像这样:
import { Block } from 'galio-framework';
我将删除App
函数内的所有内容,然后开始创建我的第一个Block
组件。这将用于将所有元素放在内部,因为我们知道,在函数中我们不能返回多个组件,所以总之,一个组件将必须将其他组件封装在内部。
我们将在其中使用flex
属性,这将使我们的Block
具有flex: 1
的属性,以便它可以水平和垂直拉伸,覆盖整个屏幕。
现在我们完成了这一点,让我们使用style
属性。正如我们所说,每个Block
元素都将具有backgroundColor
属性,以便我们可以更容易地识别哪个是哪个。在我们的style
属性内,我们将写styles.container
。
请记住,在下面有一个styles
对象,其中包含我们可以通过StyleSheet.create
函数使用的所有样式。我们将删除容器内的所有内容,只写backgroundColor: '#F94144'
。
保存一下,现在,我们的屏幕应该是一种红色。有趣的是,这种颜色叫做红色莎莎。
现在一切都正常运行,让我们继续开始创建我们的盒子布局,看看使用Block
组件在我们的应用程序中排列元素有多容易。
顺便说一句,您还应该删除不必要的导入,比如StatusBar
,Text
和View
。
我们现在将在主Block
组件内创建三个Blocks
。正如我们所知,React Native 中的所有组件都是从上到下排列的,所以我们基本上要创建三行Blocks
。
这些行中的每一个都将在它们上面使用style
属性,从上到下的顺序,样式将被称为styles.row1
,styles.row2
和styles.row3
。现在,我们将进入我们的styles
对象并创建row1
,row2
和row3
样式。它们每个都只有一个属性,那就是backgroundColor
,值的顺序从row1
到row3
,就像这样:#F3722C
,#90BE6D
,#277DA1
。
现在,如果我们保存,我们将看不到任何东西。那是因为我们的Block
元素没有设置大小,所以它不知道需要占用多少空间。还记得我们上次做的吗?我们使用了flex
,所以让我们在所有三个组件上使用flex
属性,如下所示:
图 5.5-我们用来创建三行的代码
点击保存,突然间我们从上到下看到了三种颜色:橙色,绿色和蓝色;更确切地说:橙红色,开心果色和 CG 蓝色。
由于使用flex
属性时应用了flex: 1
属性,每个组件在主Block
组件内获得了相等的空间。现在,关于这个flex
属性的酷炫之处在于我们可以使用它来设置我们需要的空间量。
让我们继续,对于第一行,我们将把它设置为flex={2}
;对于第二行,我们将保持原样;对于第三行,我们将把它设置为flex={3}
。现在,我们可以看到每个框都分配了不同的空间。这都归功于 React Native 使用flex 系统来创建布局;我们只是利用了 Galio 与之搭配使用的便利性。
现在,让我们看看当我们将所有这些数字设置为flex
属性时它是如何进行计算的。因为我们将第二个保持原样,当渲染时它将被转换为flex={1}
。我们将对三个 flex 进行计算,得到以下结果:2+1+3 = 5
。所以,简而言之,我们可以说第一行是五份中的两份,第二行是一份,第三行是三份。这里使用的数字是特定于我们的应用程序,但你可能有不同的数字。主要的想法是要理解这些数字正在分配它们所拥有的空间——一个更大的数字给我们更多的空间,而一个更小的数字给我们更少的空间。
现在,让我们使用第一行来放置另一组Block
组件并使用更多的属性。是的——我们确实有很多属性可以与之搭配使用。
我们先只输入一个组件,并创建一个名为row1el
的样式。将该样式应用到我们的新Block
上,并使用#577590
颜色。嗯,是的——什么都没有显示出来,但让我们使用两个更多的属性来让它显示出来。我们将写width={50}
和height={50}
。这将以像素为单位设置我们的Block
组件的宽度和高度。
让我们通过在父组件上使用middle
属性来使这个元素居中。父组件是我们的第一行。现在你可以看到,我们深蓝色的block
元素位于第一行的中间:
图 5.6 – 我们的代码中包含最新的元素
现在,对于第二行,让我们进入我们的styles.row2
对象并添加填充。我们将添加padding: 30
,我们可以观察到我们的第二行突然变得更高了。那是因为我们整个布局(三行)都是用 flex 构建的,它不是以像素为单位设置绝对大小;组件现在需要更多的空间。
在我们的第二行内,我们将创建另一个具有flex
、middle
和style={styles.row2gal}
属性的Block
。现在,对于我们的row2gal
,我们将有backgroundColor: '#F9844A'
。让我们在其中添加三个Block
组件。它们每个将具有以下属性:width={30}
、height={30}
和style
。样式将按顺序命名,从上到下依次为row2p1
、row2p2
和row2p3
。按照我们的样式的确切顺序,对于每个样式,我们将设置backgroundColor
属性为'#4D908E'
、'#43AA8B'
和'#F94144'
。
现在,如果我们点击保存,我们会看到我们的Blocks
被定位在一列中。让我们通过在父组件中使用row
属性来解决这个问题。现在,我们把它们放在了一行中 - 这很酷,对吧?让我们也使用middle
属性,以及space="evenly"
。保存并查看效果。我们的元素现在居中,并且它们与父组件的左右边距之间有均匀的空间。
现在,让我们进入第二个Block
并使用bottom
属性。这将使第二个元素位于第一个和第三个元素下方。有点有趣 - 看起来像一张脸,对吧?看看你是否同意:
图 5.7 - 我们填充第二行后的代码
你可以看到仅使用Block
就可以轻松创建基本布局。现在,在继续之前,你应该花些时间,而不是使用bottom
,也许在另一个组件上使用top
属性,看看它是如何工作的。或者,而不是使用space="evenly"
,你可以使用space="between"
或space="around"
。
通过使用这些组件,我们实际上可以完全控制创意。最好的部分是你可以创建一个由Blocks
组成的全屏幕,然后只需用你想要的组件填充每个Block
元素。老实说,这些功能本身就足以让我开始喜欢 Galio。幸好我们还有更多功能。
现在我们已经在我们的应用程序中使用了一些 Galio 功能,让我们继续前进,看看 Galio 提供了哪些好处。
发现 Galio 的好处。
现在我们已经了解了使用 Galio 的几个好处,比如编写代码的便利性,它的美观程度,以及使用它创建布局的酷炫之处,我们准备看看使用它的其他好处,我觉得我们应该开始我们的旅程的最佳地点是 GitHub。
你可以在这里看到 Galio 的图标:
图 5.8 - 从 Galio 的登陆页面截取的屏幕截图
正如我所说的,我们很幸运有这样一个伟大的社区,总有人伸出援手帮助你。你也可以帮助其他人,我们总是鼓励这样做。我觉得 Galio 的社区可能最好地用“集体”这个词来定义。在音乐行业,这个词通常用来定义一群有着相似兴趣的人,他们一起合作,互相帮助,因为他们知道更多的人意味着更快更容易的发展。
让我们看看你可以如何帮助并成为这个社区的一部分。
首先,我们有 Discord 服务器,这是我们大多数开发者聚集讨论各种事情,包括错误和如何解决特定问题。这个地方基本上是一个大型的聊天室,每个人都在开心地交谈。
任何人都可以加入并提出问题,甚至报告错误或一些不起作用的东西。也许你觉得设计可以改进,想要给 Galio 和它的社区一个全新的外观。你可以在那里做到这一点,不用担心有人会嘲笑你或不认真对待你。
除了 Discord 服务器,我们还有 GitHub 存储库和网站。GitHub 存储库是我们保存所有与代码相关的东西的地方。这是我们维护代码、回答问题、为未来创建新的开发计划、为某些产品创建热修复,并与pull requests(PRs)一起工作的地方。
PR 是指当有人想要帮助一个库时。所以,他们首先创建一个fork,这是克隆某人的存储库的行为。然后,他们进行自己的修改,然后新的存储库副本被提交为PR。然后由管理员验证并根据代码是否符合规则以及是否符合开发计划的一部分来接受或拒绝。
我们的网站主要是我们想要展示人们的应用程序和有关 Galio 的新闻的地方。这是我们向世界展示 Galio 的地方,但也是我们保留整个库中非常重要的部分的地方:文档。
文档是您随时想要了解有关特定组件或如何使用 Galio 功能的更多信息的首选位置,例如……GalioTheme功能。
与 Galio 相关的所有内容,如颜色、大小和布局规则,都存储在我们的默认主题中。这可以在我们的库内部的theme
文件夹中找到。每个组件都从该文件中继承其样式规则。最酷的是,您实际上可以使用我们的主题组件,仅重写您想要修改的内容来重写我们的主题文件。
例如,假设您想要为primary
设置不同的颜色代码。您可以用自己的颜色覆盖我们的主色,并将其与 Galio 一起使用,就好像它一直存在一样。
要使用 GalioTheme 功能,您需要从我们的库中导入theme
、withGalio
和GalioProvider
。让我们举个小例子:
const customTheme = {
SIZES: { BASE: 18, }
// this will overwrite the Galio SIZES BASE value 16
COLORS: { PRIMARY: 'red', }
// this will overwrite the Galio COLORS PRIMARY color #B23AFC
};
<GalioProvider theme={customTheme}>
<YourComponent />
</GalioProvider>
这将创建一个包含两个键SIZES
和COLORS
的customTheme
对象。如果您只想修改颜色,您可以只使用特定的键。然后,您需要使用我们的高阶组件(HoC)GalioProvider
来封装您的组件。我们还需要通过theme
属性将新的customTheme
对象传递给 Galio。
提示
HoC 是一种高级的 React 功能,可以更容易地定义为返回一个组件并以某种方式改进该组件的函数。假设您是托尼·斯塔克,HoC 就是钢铁侠套装。套装由铁手套、靴子、盔甲和头盔组成,而穿上铁靴的托尼可以飞行。
现在,customTheme
常量将覆盖默认的 Galio 主题常量。
但是等等——也许您不想改变我们的主题,而是想在样式中使用我们的常量。使用我们的设计系统可能有助于更快地设计布局,我们总是在为客户创建的不同产品中使用 Galio 的常量。
使用withGalio
函数导出 React 组件使您的组件能够使用 Galio 的 React 上下文,并将主题作为属性或作为styles
对象的参数传递给您的组件。让我们看看如何做到这一点——我相信您会理解的:
const styles = theme => StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.COLORS.FACEBOOK
}
});
export default withGalio(App, styles);
因为我们使用withGalio
函数来导出我们的组件,Galio 将向我们选择的对象(在本例中是styles
)传递我们库中所有的常量主题变量。因此,我们能够在我们的styles
对象内使用theme
作为参数,并将backgroundColor
属性更改为我们库中的 Facebook 颜色。
您可以在我们的文档网站上找到有关我们常量的所有信息的表格,网址为galio.io/docs
。
正如你所看到的,Galio 充满了许多很酷的功能,将帮助我们极快地开发任何移动应用,并最终使其看起来非常漂亮。那么,为什么不试一试呢?我们将从现在开始使用 Galio 编写所有项目。这将成为本书中每个应用程序开始时的强制导入。我们将使用更多的 Galio 组件,而不是 React Native 的组件。
因此,我们将学会如何使用 Galio,如何设计出色的应用程序,直到我们能够开始编写自己的想法。也许我们中的某个人实际上会创建一个对社会有很大价值的伟大应用程序——这将改变世界。
梦想着我们掌握更多知识后能做多少事情真是件美好的事情。这种白日梦和对目标的持续关注将成为学习编码的最强大武器之一。
总结
在本章中,我们已经通过多个示例说明了 Galio 为何是如此出色的库。到最后,你一定已经意识到 Galio 真的应该成为你的库之一——一个库来统治它们所有。这将成为你的主要包,你将用它来创建令人惊叹的应用程序,无论是视觉上对我们的用户,还是对想要帮助我们编写代码的编程伙伴。
不要害怕查看 Galio 的核心代码。你可能会从体验和理解 Galio 的代码中学到很多东西。你甚至可能能够创建自己的库。
因此,我们发现 Galio 真的很酷,因为代码易于使用。我们只有一些可以改变整个世界的属性,无论是编码速度还是对特定参数的轻松访问。我们还看到了 Galio 开箱即用的优势。我的意思是...这个库很华丽。有时,我甚至不会编辑样式;我只会使用 Galio 样式,因为它们看起来很棒。
我们还看到使用Block
组件创建布局有多么容易,以及只要我们了解与Block
组件配套的一些属性,放置对象在屏幕上就比我们想象的要容易得多。
之后,我们讨论了 Galio 拥有一个多么伟大的社区,以及我们如何参与其中。虽然这超出了本书的范围,我们并没有深入研究 GitHub,但我们确实学到了很多关于这个社区的运作方式以及我们如何参与其中。
最后,我们讨论了 Galio 的一些更高级的功能,或者更准确地说,使用了 React 的更高级功能,因为如果我们想要从 Galio 中使用它们,它们真的很容易使用。
最后,我们可以说 Galio 为每个人进入移动开发世界创造了一条简单的途径,我想可以说我们都对它的存在表示感激。
接下来的章节将涵盖移动 UI 的基础知识。我们将学习如何为我们的应用构建一个清晰的 UI,同时学习一些关于如何为用户提供最佳用户体验的指导方针和规则。
第六章:移动 UI 构建的基础知识
既然我们更了解 Galio 如何帮助我们构建跨平台移动应用程序,现在是时候学习一些关于设计的规则,以便我们可以最大限度地利用这个框架。
本章将浅显地介绍一些设计概念和准则,这些将帮助我们至少在设计技能上更有信心。我希望这一章能给你带来信心和动力,去构建/创建一个美观的用户界面(UI)。
我们将首先探讨清晰设计的重要性以及我们应该遵循的一些基本准则,以确保我们的设计尽可能清洁和简约,同时向用户提供最有用的信息。之后,我们将慢慢地进入对用户体验(UX)的基本解释,以及如何找出对我们的用户最好的内容。
一旦我们讨论完所有这些,我们将找出如何最小化用户输入,以便我们的用户不会感觉在填写表单时中途放弃。我们将讨论到到底是什么可能会阻碍我们的用户完成一个表单,以及我们如何改进我们的表单,以便完成率可能会增加。
之后,我们将看到整理我们的设计理念通常是确保我们的应用程序看起来整洁和良好的最佳方式。我们将发现如何做到这一点的准则以及创建呼吸空间的最合适方式。我们还将介绍我的创意过程,从第一次设计草稿到我认为值得实施的最终屏幕。
整理完毕后,是时候谈谈一致性了。我们将学习一致性在移动应用程序中的重要性以及原因。我们还将介绍三种不同 UI 设计工具的主要思想,这样你至少可以知道要研究什么,并形成自己对于原型设计自己应用程序的意见。
本章将涵盖以下主题:
-
探索清晰设计的重要性
-
最小化用户输入
-
为了更好地组织应用程序而进行整理。
-
保持应用程序的一致性
探索清晰设计的重要性
既然我们已经到了这一步,现在是时候学习一些关于如何为我们的应用程序创建一个好看设计的规则和准则了。现在,美是主观的,我们都知道,但有一些规则可能会在你的移动应用程序甚至你的网站内创造更好的流畅性。
我们并不是试图透过客观的镜头来审视美是什么,但美的某些方面与我们的大脑以及它的构造直接相关。例如,颜色在不同文化中可能有不同的含义,这没关系,因为我们不会去选择黄色而不是黑色。
与此同时,我们可以采用三分法,这是用于创建视觉艺术作品如电影、绘画或照片的经验法则。关于三分法的一点是,我们发现由于某种原因,当我们将屏幕分成两部分,水平和垂直各占三分之二后,我们的眼睛更加关注照片的主题当它位于两条线的交汇处。你可以现在就尝试一下。你的手机很可能已经在相机应用程序中内置了这个功能,所以试着拍一张主题在中心的照片,然后再拍一张主题在交汇线处的照片。
当然,这并不意味着我们所有的图片都必须遵循三分法,但在大多数情况下这会有所帮助。问题在于还有其他因素需要考虑,比如阴影、对比度、亮度等等。
解释这一切的整个目的是让你明白,设计的某些方面可以向我们的用户展现“美”的概念。
其中一个重要的方面实际上是清晰设计的重要性,以及它如何帮助我们直接传达移动应用程序的目的。我并不是在创造一个热爱极简主义的团体,但我确实觉得在当今时代,极简主义变得更加重要。在一个有如此多选择的世界中,用户喜欢信息直接,他们不想要在网站上滚动浏览大屏幕的信息和干扰,才能找到重点。
“设计不仅仅是外观和感觉。设计是如何运作的。”
- 史蒂夫·乔布斯
那么,拥有清晰的设计到底意味着什么呢?还记得那些在 2000 年代甚至 2010 年代充斥着不必要信息的网站吗,比如随机放置在屏幕右上角的时钟?人们不想看到你的应用或网站上挤满了……东西。他们实际上更喜欢更简约的方式,使其看起来更时尚、更酷,并避免让用户通过多个页面的废话来达到他们的目标,也许只是找出你的公司在哪里。
让我们实施一些规则,这样我们就不必处理那种噩梦般的应用程序。
基本要素
专注于基本要素使我们能够保持简洁和直截了当。我们可以通过限制视觉元素和菜单的数量来做到这一点。如果你考虑在你的移动应用中使用下拉菜单,最好现在就停止考虑,也许开始考虑如何将你的应用划分为适用于底部选项卡导航器之类的类别。
配色方案
让我们诚实点。我们都喜欢颜色!我们喜欢!它们很漂亮,每当我们和朋友一起出去玩或者只是一般地玩得开心时,我们总是在纠结该穿什么。那是因为并不是所有的颜色都能搭配在一起,有时如果你选择超过 10 种颜色,人们就不知道该把注意力放在哪里了。
网站和移动应用也是如此。我们应该将我们的颜色使用限制在三种颜色之内——当然,在必要时应用不同的色调——但是在我们的应用程序中只有三种主要颜色,我们可以创造一种连贯性。
假设一个屏幕有一个绿色的“提交”按钮。另一个屏幕有一个紫色的“提交”按钮。一旦用户看到这一点,他们会立刻想:“那是正确的按钮吗?”一旦你为某些事物的外观创建了规则,就要坚持下去!
可用性和可访问性
这实际上非常关键。你的移动应用设计必须能够在你的目标受众感兴趣的所有分发平台上运行。我实际上会说,现在,在今天的市场上,至少在 iOS 和 Android 上拥有你的产品是强制性的。
问题是,因为这些平台是以不同的方式构建的,并且具有不同的用户体验,你必须为每个平台适应你的产品。你做得越好,越多的人会喜欢使用你的应用。
此外,我们已经进入了 2021 年,因此您应该实现对屏幕阅读器的支持,例如 iOS 的VoiceOver或 Android 的TalkBack。这将使您对自己感觉更好,因为您不仅为每个人创造了更好的数字世界,而且通过为您的应用程序提供更广泛的受众,您将有更好的机会将您的想法发展成成功的想法。
简单
我无法再强调这一点了,但你需要专注于重要的事情。你不需要在一个屏幕上列出你的应用程序可以做的每一件事。尽量保持简短。没有人有时间真正阅读页面上的所有信息,因此尽可能少地提供信息,同时使其尽可能有意义,这是简单的关键。
信息架构
每个与您的应用程序交互的用户在首次使用您的应用程序时都会表现出预先构建的行为模式。研究您的竞争对手,并确保这种行为不会妨碍您制作应用程序的创意过程。例如,他们可能期望特定按钮,比如开始按钮在闪屏界面内,总是在屏幕底部。您的工作是确保您会利用这些行为,并且如果您想为用户创建某种新的 UX,花时间教他们如何使用您的应用程序。
一致性
确保您的设计和信息在整个应用程序中保持一致。通过保持一致,您可以确保您的用户永远不会出现不理解发生了什么或如何使用您的应用程序的时刻。通过保持一致,我们实际上教会了我们的用户使用我们的平台的最佳方式,而无需额外的乏味文本。
用户体验
您可能已经注意到了短语用户体验(UX),但我们并没有真正定义它。UX 指的是产品(网站或移动应用程序)如何满足用户的需求。
我们应该区分 UX 和可用性,因为后者是 UI 的质量属性,涵盖了系统学习的容易程度或使用的效率。
在设计移动 UX 设计时,我们可以牢记的一个好的经验法则是问自己以下问题:移动应用程序有用吗?
如果没有,我们可以说对最终用户没有价值。
如果答案是肯定的,但不够直观,最终用户不会花时间去学习它。
移动用户体验设计涵盖了三个重要方面:可访问性、可发现性和效率。这导致了快速、积极和以体验为驱动的最终结果。
Net Solutions的2020 年 B2B 商务状况报告指出65.8%的企业将在未来 12 个月内投资于改善移动用户体验设计。
基于这些数据,我们应该意识到用户体验是一门永无止境的科学。我们永远不会拥有完美的用户体验——随着用户的变化,这将会发生改变。随着时间的推移,我们可能会在使用手机时改变我们的行为,因此您的设计师需要为最终用户设计出一个出色的体验,以满足特定时间的特定用户的期望。
现在自然的问题应该是:我们应该如何处理这个问题,以便我们始终能够为用户提供高质量的用户体验?我认为,对于构建出色的用户体验,以下部分提到的方法将为您带来最佳结果。
研究
花费多天时间与最终用户在一起。了解他们的需求,以及他们对目前工作方式的真实感受。倾听他们,因为他们的反馈是整个过程中最重要的部分之一。
例如,如果你看看家里年长的成员在使用应用时,你会注意到他们对应用的某些部分非常容易感到沮丧。观察他们,看看他们期望什么。他们可能会说类似于“为什么通过这个应用订购东西这么难?”然后紧张地随意在屏幕上点按,因为事情并不完全符合他们的期望。你周围的人,尤其是那些被应用所针对的人,可以为你提供最宝贵的信息。
共情
与用户讨论并了解他们的需求后,就是找到解决方案的时候了。使用任何对你最有帮助的方法来整理这些想法,并尝试找到解决用户遇到的问题的方法。你需要注意,以免制造更多问题,所以在找到解决方案后要测试你的应用。
构建
嗯,这是不言而喻的。一旦一切都经过测试,你已经找到了所有问题的解决方案,就该是构建应用的时候了。问题是……根据你的研究,你应该能够意识到你的应用真正需要哪些技术。有时,即使是 React Native 也不够用,所以你可能需要做一些改变。这是成为一名优秀程序员的一部分,所以不要担心!一旦你了解了一种编程语言和一个框架,你就可以学会任何东西。
现在我们已经经历了这一切,并且对为什么清晰的设计对我们的应用非常重要以及 UX 是如何运作有了一些了解,我们应该对为什么某些应用采用极简主义路线并为所有用户提供直接的学习路径有了相当清楚的想法。
现在,让我们讨论为什么最小化用户输入很重要,这是确保我们有清晰设计的另一个部分,以及我们如何做到这一点。
最小化用户输入
许多人在填写表单时都有所犹豫,特别是当表单很长,充满了涉及他们必须搜索实际文件并填写看似无关紧要的步骤的个人信息时。
了解这一点,我们有责任为我们的用户创建一个良好的表单,这样他们在填写时不会觉得这是一项琐事。任何表单的主要目标是完成。为此,我们首先需要了解有效表单的主要概念。这些内容在这里介绍:
-
复杂性的感知:每当我们面对一个表单时,我们首先要做的是视觉扫描,以便估计需要多少时间才能完成。了解这一点,我们几乎可以立即意识到复杂性的感知在完成表单中起着至关重要的作用。看起来越复杂,用户完成的可能性就越小。
-
交互成本:这是完成表单所需的所有努力的总和。用户付出的努力越多,他们完成的可能性就越小。想象一下一个有 bug 的表单,你无法添加你的出生日期,或者添加起来不直观。你可能会失去注意力,对表单感到生气,以及它有多难以使用。最终,你可能根本无法完成它。这种有缺陷的交互会让用户对应用程序和表单本身产生负面印象。这种缺陷会让用户忘记设计有多漂亮,或者应用程序的其他部分有多有用。
现在我们知道用户实际上会如何考虑我们的表单,让我们看看我们应该遵循哪些准则,以便我们能够创建一个高效的表单设计,所有用户都能够遵循和完成。考虑以下几点:
- 通过提出正确的问题减少用户的努力:表单中的问题应该按直觉的顺序排列,并且从用户的角度看应该是逻辑排序的。在考虑提出问题的顺序时,我们总是从姓名、出生地和个人信息开始。这是因为这就像一次对话。不要只因为你的数据库或应用逻辑有不同的提问顺序而妥协——用户是第一位的。我们作为程序员的工作在用户对应用程序的实际工作方式一无所知时表现得最好。
一个很好的经验法则可能是不断地问自己为什么以及你请求的信息是如何被使用的。
-
单列布局:双列布局对我们的表单来说最大的问题是你不知道用户会如何阅读信息。为了使这更容易,只使用单列应该对用户来说是直观的,他们会明白自己首先必须完成屏幕顶部的任何问题。
-
尽量使用较少的输入字段:想象一下,你想要订一次航班,但它要求你提供关于整个旅程的所有信息。你只是想查看价格,看看自己是否能负担得起下个月去巴哈马的航班,但你看到的表单却和整个屏幕一样大。你会看着屏幕,也许会觉得自己并不真的想去巴哈马,至少不想和这家预订公司一起去。
尽可能少地使用输入字段不仅意味着从表单中删除不必要的问题;你还应该考虑不同的方式来提出这些问题。例如,不是为出发日期(日、月、年)设置三个输入字段,而是使用一个日期选择器和一个输入字段可能更容易。另一个使用其他类型的表单元素的好例子可能是,不是为乘客人数设置下拉菜单,而是使用一个“+”和“-”按钮。这将使内容更具互动性,对于试图快速填写表单的用户来说更少具威胁性。
-
输入框的正确宽度:这种情况经常发生。我正在网上订购东西,然后他们要求我填写街道地址和门牌号码。这显然意味着我应该在一个输入框中写街道名称,然后在另一个输入框中写门牌号码。问题是街道输入框非常大。这让我感到困惑,我在想:“除了街道名称,我还应该写些什么吗?”这种情况不应该发生;如果你知道用户应该写邮政编码,尽量让邮政编码输入框尽可能大。使它比必要的尺寸更大可能会让用户感到困惑,我们不想让用户感到困惑。
-
顶部的标签:将输入标签放在文本输入框的顶部可以更容易地跟踪表单。假设我们将它们放在屏幕的左侧;这会使你的眼睛来回移动,这似乎并不是太大的工作,但我们试图设计尽可能清洁和直接的设计,所以任何能帮助用户感觉我们的表单不会太困难的东西都会对我们有利。
-
可选和必填字段:我们知道,我们应该尽量避免在表单中使用可选字段,因为它们会使表单变得比必要的更长,但有些情况下,如果我们想要为我们的营销团队获取更多信息,或者可能我们只是需要结账表单的第二个地址,那么一些可选字段是必要的。如果它们对我们来说是必要的,那么我们最好让它们真的明显地表明它们是可选的而不是必填的。你可以在标签旁边写上“可选”,但确保它是可见的,绝对不是隐藏的信息。
-
高度可见的错误消息:当我在表单中出错但不知道出了什么问题时,我真的很讨厌这种情况(而且我不是唯一一个)。一切都变成了一个谜题:“是密码吗?”“是邮箱吗?”“我哪里错了?”通过为每个输入表单提供清晰可见的错误消息来避免这种情况。
这些消息必须仅通过扫描屏幕就能看到。为此,您可以使用任何可用的东西,无论是图标、颜色还是文本。
在用户完成表单后通知用户有错误的时间是在他们完成表单之后。不要在他们填写表单的过程中打断他们,告诉他们有错误,因为这可能会让一些用户感到非常恼火。
使用这些准则应该确保我们有一个真正好的表单。但这并不止步于此。每种情况都是不同的,所以不要害怕打破规则或想法。设计应用程序的很酷的一点是,你的想法和任何人的想法一样重要。当你试图与众不同时,最好的做法是始终问自己:“这如何改善我的用户体验?”如果找不到答案,最好还是坚持这些主要想法,或者从设计书籍或心理学书籍中找到新的想法。
到目前为止,我们已经讨论了为用户设计一个干净的设计和一个漂亮的表单。我们应该开始考虑创建我们设计的另一个方面——精简。
为了更好地组织应用程序而进行精简
在向用户显示相关信息和保持界面尽可能清洁和简洁之间总是存在问题。当我们说精简时,我们指的是设计的视觉和可读性方面。
在桌面网站上,混乱是可怕的,但在移动应用上更糟,因为屏幕尺寸要小得多。摆脱任何绝对不必要的信息是至关重要的。
因此,让我们看看如何在我们的应用程序中做到这一点。我们可以参考我们在第四章中创建的第一个屏幕,您的第一个跨平台应用程序,如下所示:
图 6.1-我们创建的第一个屏幕
正如你所看到的,我们的应用程序已经填满了用户所需的唯一重要信息:最近玩过的游戏、最常玩的游戏、他们的名字和等级。但当我开始开发这个屏幕的想法时,实际上是从一个充满信息的屏幕开始的。我的屏幕看起来真的很乱,有很多不必要的信息,但出于某种原因,我认为这可能与我们的用户相关。
让我们看看之前的样子,然后试着注意我是如何将它整理成最终形式的,如下所示:
图 6.2 - 我们清理之前的屏幕
我知道,你的第一反应是“呃”,这是完全可以理解的。这个屏幕看起来充满了太多东西。而且,感觉就像没有呼吸空间,信息占据了所有可用的空间。
让我们一步一步来看看我是如何从最初的想法(图 6.2)到最终的产品的。我们将尝试理解我的创意过程中到底发生了什么,以及我们是如何清理屏幕的。它是如何演变的:
- 空白
屏幕边缘和主要内容区域之间的空间被称为排版中的“边距”。即使你写一个 Word 文档,也总会有一些空白空间;我们不是把东西从纸的一边写到另一边。尽管我有 8 像素(px)的边距,但它仍然感觉不对劲。我觉得需要更多的空间,所以我将边距增加到 32 像素。
这限制了我们的内容,让我们有更少的空间来工作,但一切看起来都有了更多的呼吸空间。这是一个公平的交换;少一些信息并不总是坏事,特别是在评估屏幕内容之后。
- 删除不必要的信息
一旦我们确定了在这个特定屏幕上用户不绝对需要的信息,就是时候将它们移除了。起初,我认为一个漂亮的小图表会很酷,但看到它占据了多少空间,我意识到对于我的假设应用程序来说,最好是将那个图表视为用户可以通过点击他们感兴趣的游戏来查看的东西。
同样的道理也适用于他们上次玩这些游戏的日期。这些在第一眼看来并不需要,因为一旦他们真正对该游戏的统计数据感兴趣,他们可以在另一个屏幕上看到它们。所有这些信息都可以轻松地在另一个屏幕上实现,那么为什么我们要把它放在第一个屏幕上呢?
- 对齐
现在我们已经去掉了一些元素,并且我们选择了距离屏幕边缘到内容区域的 32 像素边距,是时候为对齐创建一些规则了。首先,我想我们应该让所有东西对齐到屏幕的左侧。如果我们打破这个规则,突然在屏幕中央有一个标题文本,我们的用户可能会觉得有些不对劲。
现在我们已经选择了文本对齐的位置,是时候在整个应用程序中保持这一点了。
- 一致性
例如,在图 6.1中,我们的三个主要类别(标题,最常玩的游戏和最近玩的游戏)之间有相等的空间,最近玩的游戏标题下显示的游戏之间也有相等的空间。所以,我们选择了两种不同的大小,赋予它们意义,然后在需要时使用它们。想象一下,如果一个游戏标题距离底部只有 2 像素;也许你不会立刻注意到,但你会觉得有些不对劲。
颜色也是如此。我们选择了三种主要颜色,赋予它们意义,然后保持一致性。这也适用于其他屏幕——相同的边距、颜色和对齐。这就是我们确保我们的屏幕永远不会对某人显得奇怪的方法。
完成创意过程后,我会看一看之前和之后的版本,试着判断哪个版本更好。另一个好的判断标准可能是亲戚或朋友,所以不要害怕与其他人分享你的作品,看看他们的想法。
我的初始设计总是会与我实际实施的不同,因为在我看来,你对某件事的第一印象总是受到当时周围发生的事情的影响。最好退后一步,让所有信息围绕着你。之后,你可以更准确地评判你的作品。
一旦我们确定了设计,我们应该能够在整个应用程序中保持规则。这就是一致性,这是我收到的最好的建议之一。
在应用程序中保持一致性
一致性是一种非常有帮助的东西,无论是在设计还是我们的个人生活中。我已经学会了保持一致对于健康、成功的生活至关重要。保持一致是让我从 A 点到 B 点的关键,我相信这适用于生活的每个方面。保持一致是关于体验的。
从我们的移动应用程序的主屏幕慢慢进展到最后一个屏幕是用户需要享受的体验。只有通过避免混淆和减少用户的学习量,才能享受这种体验。
让我们看看如何在设计中实现一致性以及处理一致性问题的适当方式。
设备用户界面指南和行为
iOS 和 Android 有不同的用户界面和不同的可用性指南。你最好熟悉它们。通过识别平台之间的差异,我们可以确保我们的应用在每个特定平台上都能正常工作和运行。尽管设计必须相似,但用户实际使用这些平台的方式有所不同,因此你要确保你的应用不会让用户学习不同的使用模式。
意义
有些应用程序的某些方面我们不希望被改变。想象一下,在结账过程中有一个蓝色的提交按钮,然后在注册表单中有一个红色的提交按钮。这会造成混淆。
一旦我们赋予颜色和按钮意义,重要的是无论用户使用哪个屏幕或平台,都保持相同的意义。如果你来自网页开发行业,你可能知道 Bootstrap。Bootstrap是 Twitter 创建的一个带有颜色、层叠样式表(CSS)类和网页设计指南的 UI 库。例如,他们确定了一种蓝色作为信息的颜色。这就是他们保持一致性的方式。
另一个很好的例子是,在我们在[第四章](B17074_04_epub_Final_SB.xhtml#_idTextAnchor070)中开发的屏幕中,你的第一个跨平台应用,我选择了屏幕边缘和主要内容之间的 32 像素的边距。如果我们要开发另一个屏幕,我们必须保持相同的约束。
语言
我相信我们都知道收件箱、提交、垃圾邮件和删除这些词的意思。这些词是被所有应用用户普遍接受和了解的。仅仅为了改变而改变这些词需要用户开发另一层理解并学习这些新词。为了保持一致,我们将确保所有这些词在我们的应用中具有相同的含义。
当然,单词列表远不止这些,但一个很好的经验法则是问自己以下问题:“我是否曾经在另一个应用程序的不同上下文中看到过这个单词或图标的使用?”如果答案是“是”,你可能需要重新考虑你设计应用程序的方式,或者至少是语言方面。
在讨论了所有这些一致性和清晰设计的准则之后,我认为我们应该探索一些可能帮助你设计完美移动应用的不同软件产品。众所周知,有 Adobe Photoshop,它几乎在设计的各个方面都被广泛使用,无论是网页、移动还是独立游戏的像素艺术。但我们不会深入探讨 Photoshop 之所以如此重要的原因,因为我们可以使用其他更简单学习和更便宜的产品。
Figma
Figma 是一个几乎兼容所有浏览器的工具。这使它成为一种独特的设计工具,因为它是基于浏览器的。你不需要担心安装它的最新版本或处理兼容性问题或版本问题。它也是一个协作工具,所以你可以加入设计你的项目的团队。
价格是免费的,但为了获得更好的功能,需要进行月度订阅付款。这是一个非常好的工具,很多人喜欢使用它。
Adobe XD
Adobe Experience Design(XD)是 Sketch 的直接竞争对手。因为 Sketch 只在 macOS 上运行,XD 是 Windows 用户的替代选择。当然,它在 macOS 上的运行效果和在 Windows 上一样好。对于初学者来说,它非常快速和易于使用。它拥有 Sketch 的所有功能,比如线框设计、原型设计等等。
这是一个免费工具,但也可以按订阅模式供公司使用。
Sketch
Sketch 是一款非常轻量级的设计工具,被视为设计师的行业标准原型工具。一旦你想要更深入的关于设计的教程,你会发现 Sketch 比你想象的更常见。它与 Photoshop 非常相似,但它的重点是在图形设计上。
Sketch 现在的价格是 99 美元(USD),还有 30 天的免费试用期。我强烈推荐尝试这个工具,因为它是整个行业中使用的标准工具。
我不得不说,我个人最喜欢的是 Sketch,但在不得不在 Windows 上设计之后,我开始尝试使用 Adobe XD。现在,我用 XD 做所有的事情。我甚至在这本书中找到的屏幕和示例图片中使用了它。我喜欢你可以在上面进行原型设计,我完全推荐在选择你最喜欢的工具之前尝试所有这些工具。
总结
本章充满了关于如何为我们的移动应用程序保持良好清洁设计的信息。我希望到最后,你至少理解了一些,因为毕竟,我们不是设计师,我们是程序员。不过,我认为,至少对其他人使用的工具有一点了解,以及一些基本规则和指导方针,将对你成为一个更好、更有准备的程序员有很大帮助。
我们了解了如何最小化用户输入,并创建具有更高完成率的出色表单,以获得出色的用户体验。我们还学习了一些关于如何以更合乎逻辑的方式创建这些表单的规则,以便我们永远不会让用户感到困惑。
之后,我们学习了如何简化我们的设计,使其看起来有更多的空间,我们看到了我从屏幕设计的第一稿到最终结果的创作过程。
在了解一致性以及它的确切含义之后,我们探讨了每个设计工具的主要思想,以便你能够选择最适合你的工具。
希望你对下一章感到兴奋,因为我们正在更接近理解和创建实际的酷小应用程序。我们将开始学习关于我们应用程序的状态以及如何使用它来动态地改变应用程序中的信息。
第七章:探索我们应用程序的状态
在经历了关于如何构建应用的许多不同想法之后,是时候为我们房屋的基础设置最后的石头之一了。在本章中,我们将了解状态是什么,更重要的是,状态在 React 应用程序中是如何工作的。
我们将对状态的基本定义进行讨论,以及它在 React 应用程序中的传统用法。我们还将了解一些新的现代状态使用方式以及它们的工作原理。我们将不得不自己决定哪种状态最适合在我们的特定情况下使用,但当然,我会给出我的建议。
然后,我们将把所有新的信息应用到一个实际的练习中,这将帮助我们巩固我们的大脑中的这些新概念,以便我们能够正确理解我们所涵盖的一切。
在实际练习之后,将是时候看一些不同的钩子以及它们究竟是什么。我们将了解在类组件中使用状态与使用钩子如何帮助我们编写更少的代码之间的区别。我们还将了解另一个处理生命周期函数的钩子。所有这些将帮助我们在 React Native 和 Galio 中创建更复杂的应用程序之前继续学习,并且在创建更复杂的应用程序之前,这对我们来说是必不可少的。
本章将涵盖以下主题:
-
什么是状态?
-
升级我们的屏幕
-
其他钩子及其相关性
技术要求
您可以通过访问 GitHub github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
来查看本章的代码。您会发现一个名为Chapter 07
的文件夹,其中包含我们为本章编写的所有代码。为了使用该项目,请按照README.md
文件中的说明进行操作。
什么是状态?
现在我们已经到了这一步,对于我们继续前进来说,了解状态是什么以及它在 React 组件中是如何工作的是至关重要的。一旦我们学会了这一点,我们将完全能够充分利用 React 的能力。这将解开我们迄今为止一直缺少的环节,更确切地说,它将解开使我们的移动应用程序更加动态的关键。
我们在第三章中学习了props。这是我们用来从一个组件传递数据到另一个组件的技术。将 props 视为组件的第一层。我们需要提升我们的组件创建技能,所以在进入任何实际挑战之前,现在最合乎逻辑的步骤是学习关于状态的知识。
传统上,为了能够在组件中使用状态,我们必须使用类组件。在 React 的后续版本中,我们还可以使用一种叫做hooks的东西,在函数组件中使用状态。在学习状态的基础知识之后,我们将讨论 hooks,而为此,我们必须从类组件开始。
但是什么是类呢?类是创建对象的模板。对象通常用于面向对象编程。尽管 JavaScript 不是基于类的面向对象语言,但它仍然有使用面向对象编程的方式。
让我们看看在 JavaScript 中如何创建一个类,以及在 React/React Native 项目中它需要什么才能正常运行:
图 7.1 - React 中类组件的代码
这与函数非常相似,但它没有任何参数,而且我们可以看到那里的extends
单词。关键字extends
基本上是用来让类知道它应该从另一个类继承属性;在这种情况下,另一个类是React.Component
。所有类都需要从React.Component
继承,以便该类可以作为 React 组件使用。
我们还看到了render()
函数。这个函数是 React 组件所必需的。这是我们编写所有 JSX 的地方。现在,还有另一个函数我们应该使用。这是在使用类创建新对象时调用的函数。
现在我们已经学会了如何创建一个类,是时候进入状态了。让我们看看如何将状态添加到我们的App
类组件中。为此,我们需要在App
类中创建另一个名为constructor()
的函数:
图 7.2 - 添加到我们类中的构造函数
重要提示
在基于类的面向对象编程中,构造函数是一种特殊类型的函数,每当我们创建一个对象时就会被调用。它通常接受参数,以便以任何我们想要的方式自定义初始化一个新对象。
如你所见,这个函数接受一个参数props
,这使我们能够使用这个组件可能接收的 props。构造函数中的super()
函数是一个关键字,用于访问并调用对象父级的函数。这个函数必须在使用this
关键字之前使用。
正如我们所看到的,我们的状态变量前面有一个this
关键字。这个关键字指的是它所属的对象。它基本上指的是状态
变量只与这个对象相关联,所以你不能直接从另一个对象访问它。
现在让我们看看我们如何在render
函数中使用它。这与我们使用props
的方式完全相同:
图 7.3 - 在渲染函数中使用的状态
正如我们所看到的,我们仍然使用this
关键字,以确保状态
变量在渲染时指的是这个特定的对象。现在应该在屏幕上显示的消息是大家好!我今年 24 岁了!。
这与我们一直在使用的props
非常相似,但到底有什么不同呢?
实际的区别在于状态
是局部的,而props
是我们从一个组件传递到另一个组件的东西。另一个区别是,因为状态
是一个局部变量,我们可以在组件内部改变它,唯一需要重新渲染的是特定的组件。而props
的情况是,一旦我们更新了一个 prop,使用该 prop 的所有子组件都需要重新渲染,这会给我们的应用程序带来一些压力。
什么是状态?
在计算机科学中,只要系统被设计为记住先前的信息,就被称为有状态的系统。被记住的信息被称为系统的状态。
这并不是说状态
比props
更好。它们都有各自的目的,当构建应用程序时,你会使用所有这些概念。有时,你需要状态,我们将看一些使用两者的例子,这样我们就可以更好地理解它们的工作原理。
那么我们如何改变这个变量呢?你们中的一些人可能自然地会想到“嘿,这很容易 - 就像平常一样改变变量”,然后你会尝试像这样做一些事情:
state.age = 54;
但这并不会真正起作用。你可以尝试在组件内部进行操作,但你不会看到任何变化。状态将保持在你的屏幕上为24
,组件不会重新渲染。React 状态应该被视为不可变的。在编程世界中,不可变对象是指在创建后无法修改的对象。
实际上,我们有一个 React 函数为我们实现了setState()
。这有助于我们用另一个状态替换状态,因此我们实际上并没有修改主变量;我们实际上是用另一个变量替换了变量。
因此,如果我们想要改变年龄,我们需要写类似这样的东西:
this.setState({ age: 54 });
现在,这似乎相当容易,但我们到底在哪里改变状态呢?嗯,有很多地方可以改变状态,但这取决于你的应用程序以及你希望它如何工作。假设我们想在组件在屏幕上渲染时立即改变年龄。React 为我们的类组件提供了一些称为生命周期函数的特定函数。
这些函数在组件生命周期的特定时刻被调用。我们将讨论其中的两个:componentDidMount()
和componentWillUnmount()
。
这些函数确切地代表了它们的名字所暗示的。第一个函数在我们的组件已经挂载(渲染)到屏幕上后被调用。第二个函数在组件需要从屏幕上移除时被调用。因此,我们在组件生命周期中有这些时刻,我们可以插入代码来确保组件的行为符合我们的期望。
显然,如果我们想要在组件渲染后改变年龄,我们必须使用componentDidMount()
函数:
图 7.4 - 在我们的类组件中使用 componentDidMount
现在当我们打开应用程序时,我们会看到大家好!我今年 54 岁!。但实际上状态在渲染开始时是24
,一旦渲染完成,状态就变成了54
。所以,这真的很酷,我们有很多不同的新功能和属性。我完全建议你阅读更多关于 JavaScript 类如何工作的信息,如果有任何你觉得不太理解的地方。你可以通过访问 Mozilla 的网站来做到这一点,那里充满了有关 JavaScript 的有趣信息:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
。只是让你知道,很多人在this
关键字的工作方式以及状态的确切工作方式上遇到问题或感到困惑。我觉得一旦你更多地了解 JavaScript 的工作原理,这种困惑就会消失。
现在让我们运用到目前为止学到的知识,进行一个有趣的小实践。我们将开始使用状态,使我们的屏幕看起来更加动态,而不仅仅是我们通常的静态屏幕。
升级我们的屏幕
让我们看看我们要创建什么样的应用程序。我在想我们可以有一个屏幕,显示我们当前的年龄,包括月份、天数、小时和分钟。我是说,那挺酷的,对吧?每当有人问起你的年龄,你就可以从口袋里拿出手机,展示你创建的屏幕。让我们开始吧:
- 让我们打开终端,并像往常一样使用以下命令创建一个新的
expo
托管项目:
expo init RealAge
现在让我们打开项目并开始编写一些代码!
-
现在让我们直接打开
App.js
文件,除了导入和样式表之外,删除里面的所有内容。我总是保留样式表,因为我喜欢居中的文本。 -
现在让我们将
App
组件重写为class
组件。
图 7.5 - App.js 重写为类组件
- 现在让我们通过终端内使用以下命令打开我们的 Expo 应用程序:
expo r -c
我总是使用这个命令来清除缓存。这样,我就确保缓存不会干扰我的更改。
-
现在 Expo 服务器已经打开,就像我们学到的那样,打开你选择的模拟器。一旦你的应用程序打开,你应该能在屏幕上看到
我的真实年龄是:
的文字。 -
现在让我们将我们的年龄作为
App
类组件内的状态集成进去。
就像我们之前看到的那样,我们需要在类组件的所有其他内容之上编写我们的constructor()
函数。不要忘记关于super(props)
这一行 - 这很重要!然后我们将在constructor
函数中创建我们的状态:
图 7.6 - 带有我们新创建的状态的构造函数
我已经提到我们将以年、月和日的形式显示我们的年龄,一直到秒,所以我在那里放了一个填满零的对象作为占位符。它实际上可以是任何东西,因为我们将在一些快速的数学运算之后更改它。
现在让我们直接进入如何计算年龄的方法。为了这个小技巧,我们将使用 JavaScript 中的Date()
对象。尽管这个对象对一些人来说可能有点令人困惑,但在你了解更多关于时区的知识之后,它真的只是另一个可以玩耍的对象。别担心,我们不会深入研究 JavaScript 中的日期,因为我们有更好的东西要学习。
因此,我们将创建一个名为getAge()
的新函数,它将接收您的生日日期。这个函数将获取当前时间,并从中减去您的出生日期。所有这些都将以毫秒为单位完成。之后,我们将取得的结果创建一个新的Date
对象。从这个新对象中,我们将提取关于我们年龄的所有信息。
最后,我们将使用setState
来创建一个新的状态,其中包含我们从Date
对象中计算出的所有信息:
图 7.7 - 我们计算当前年龄的函数
现在,你可能想知道为什么我们要减去1970
年和1
天。噢,正如我所说的,Date
对象有点奇怪。我们必须减去1970
,因为 UTC 时间从 1970 年开始,所以为了确保我们得到正确的年份值,它必须从我们的方程中消失。至于天数的值,这可能与我真的想确保时区被考虑进去有关,我的时区需要那个-1
。事实上,即使我们少了 1 天,重要的是要看到这个东西真的起作用。
现在我们有了这个函数,并且我们正在使用setState
函数来正确地改变状态,是时候从某个地方调用这个函数了。正如你所知,一个普通的函数不会自己调用(尽管有一些函数可以做到这一点)。
所以,让我们做和之前一样的事情 - 让我们在componentDidMount()
中调用我们的函数,就像这样:
this.getAge(new Date("June 29, 1996 03:32 UTC+2"));
正如你所看到的,我使用了关键字this
来确保我们的对象知道我们正在引用它的函数getAge
。我还在函数内使用了我的生日,但你可以使用你自己的生日来使这更加个人化。
我们的工作还没有完成!让我们进入我们的render
函数并进行一些修改,以便我们可以正确显示一切:
图 7.8 - 在我们实现状态后的渲染函数
我们render
函数内的第一行可能对你们中的一些人来说有点奇怪。这被称为对象解构。这就是我们之前在导入中已经做过的事情。这是一个非常有用的 JavaScript 特性,用于从对象中提取属性,甚至将它们绑定到变量上。
例如,现在我们可以在引用this.state.age.years
时只说years
。这节省了我们的写作时间,而且看起来也清晰得多。你会经常看到人们像这样解构变量 - 这是一个非常酷的特性!
现在我们已经确保我们将使用state
内的所有变量,我们的componentDidMount
正在调用我们的getAge
函数,并且state
是在那个函数内设置的,一切准备就绪。运行你的应用程序并检查结果。你应该能够看到屏幕上显示出你真正的年龄,甚至可以看到最细微的细节。
但有一些问题 - 秒数不会刷新,所以一切都保持不变。你可能会认为我可能欺骗了你,但相信我,我没有。现在,你的真实年龄没有更新,因为我们的getAge
函数只被调用了一次。正如我们所说,componentDidMount
在组件首次渲染在屏幕上时调用该函数。我们的组件渲染了,函数被调用了,故事就此结束。
我们不知何故必须让那个函数多次调用;我认为至少每秒一次,这样我们才能确保我们的秒数与真实时间同步。现在让我们来做吧!
在我们的componentDidMount
函数内部,我们将调用一个很酷的函数叫做setInterval()
。它接受的第一个参数是一个函数。这个函数将会以一定的时间间隔被调用。它接受的第二个参数实际上是以毫秒为单位的时间,用于执行这个函数的频率。
图 7.9 - 使用我们的 setInterval 函数的 componentDidMount
现在我们创建了这个间隔,getAge()
函数被调用。当我们不再需要它工作时,停止间隔是一个很好的做法。你脑海中现在可能会冒出一个问题:“什么时候我们不需要它运行呢?”嗯...这通常是主观的,但在我们的特定情况下,答案是在组件的生命周期结束时。
记得我们说过还有另一个叫做componentWillUnmount()
的生命周期函数吗?好吧,这正是我们要结束这个函数的地方:
图 7.10 - 在我们的 class 组件中使用的 componentWillUnmount 函数
现在我们已经做到了这一点,我们的应用应该准备好正确显示我们当前的年龄了。保存一切,刷新模拟器,然后检查一下!你的真实年龄现在正确地显示在屏幕上。不过,不要让这些数字毁了你的一天 - 我们都只有自己感觉年轻!
现在我们已经看到了状态在class
组件中的行为,这在某种程度上是状态的传统用法,是时候看看其他使用状态的方式了。在最近,React 给我们带来了一些很酷的小东西,叫做hooks。让我们更多地了解它们,它们与传统状态有什么不同,以及它们为我们带来了什么新功能。
其他 hooks 及其相关性
状态的主要问题在于我们只能在class
组件中使用它。对于一些初学者来说,类组件通常被认为有点丑陋且难以学习,因此 React 团队尝试创建一些新的东西,承诺解决初学者和高级用户在使用传统状态的类组件时可能遇到的问题。这就是hooks诞生的原因。
Hooks 是在 React v16.8 和 React Native v0.59 中引入的。它们基本上让你在不编写类的情况下使用状态和其他 React 特性。
那么,这对我们究竟意味着什么?让我们看一个示例,看看我们如何使用新的钩子功能编写状态:
图 7.11 - 使用钩子的示例
哇!这是什么?这真的是我们迄今为止一直在使用的相同状态特性吗?是的,是的。如果你将这段代码复制到一个全新的项目中,你会发现一旦启动你的应用程序,每次你按下那个按钮,数字都会从 0 更新到你按下它的次数。
让我们看看我们到底写了什么。
正如你所看到的,我们创建了一个名为Example
的函数。只要不是你的主要函数,名字并不重要,主要函数应该总是叫做App
。函数看起来比类清晰得多,显然更容易编写。
然后我们使用useState()
钩子在我们的函数中定义了两个变量。这到底是如何工作的呢?
const [count, setCount] = useState(0);
在这个示例中,useState
是一个钩子。我们在函数组件中调用这个方法,以便为我们的组件添加本地状态。这个函数返回一对值:当前状态值 - count
,以及一个可以更新该值的函数 - setCount
。setCount
函数与类中的this.setState
函数非常相似,只是它不会合并旧状态和新状态。
useState
接受的唯一参数是赋给我们的count
变量的初始状态。请记住,我们的this.state
变量必须是一个对象,而且一切都在那个对象里。count
不必是一个对象,尽管如果你愿意的话它也可以是。
现在让我们直接比较使用this.state
和useState
钩子。我们将看到相同的状态使用这两个特性写成,这样我们就可以清楚地比较这两者。
首先,我们来看一下this.state
。我们想象一下有一个应用程序,需要一些关于用户的信息,一些朋友在用户个人资料上留下的评论,还有这个个人资料有多少个赞:
图 7.12 - 在类组件中编写的状态对象
这很容易理解,对吧?我们的state
有以下值:userInfo
- 一个对象,comments
- 一个字符串数组,likes
- 一个数字。让我们看看使用hooks会是什么样子:
图 7.13 - 我们在功能组件中编写的状态
这和之前的例子完全一样,但我们使用了useState
钩子。所有的值和之前的例子完全一样,不同之处在于我们的状态不再存在于单个对象中。
现在,举个例子,假设我们想改变喜欢的数量。也许有人点击了喜欢按钮,我们想要更新屏幕上显示的数字。让我们看看在类组件中如何改变它:
图 7.14 - 在类组件中改变状态
看起来复杂,对吧?除此之外,与我们一直使用的setState()
函数相比,还有很多新的东西。问题是,因为我们只需要更新喜欢的数量,所以我们使用了一种叫做previous state的东西。这就是prevState
的来源。一旦需要根据先前的状态来改变状态,就像我们在这里需要增加喜欢的数量一样,就必须将一个函数作为参数传递给this.setState
。这为我们提供了先前状态的快照(prevState
)。到目前为止,我们一直使用简化版本,因为我们不需要根据先前的状态来更新它。
现在让我们看看如果我们使用钩子会是什么样子:
图 7.15 - 在功能组件中改变状态
这显然更加清晰和简单。我们知道我们只想改变喜欢的数量,所以我们使用了setLikes
。在这里,我们可以取likes
状态,然后将其增加1
。
正如你所看到的,钩子使我们的生活变得更加容易。它们非常简单易用,需要写的代码也少得多。
现在问题是,如果我们在进入hooks之前创建的应用程序,显示我们真实年龄的应用程序,我们如何能够调用setInterval
函数,因为生命周期函数 - componentDidMount
或componentWillUnmount
- 仅在类组件中可用。
我们很幸运,因为 React 团队为我们提供了除了setState
之外更多的钩子供我们使用。首先,让我们看看什么是钩子。
正如我们所知,React 是关于代码重用的。现在,我们可以编写简单的函数,并在需要计算某些东西时调用它们,甚至编写组件以便在应用程序的任何部分重用它们,但组件的问题在于它们必须渲染一些 UI。这使得组件有点不方便。React 团队提出了钩子的想法,因为他们希望能够共享复杂的逻辑而不必渲染某种 UI。钩子让你可以通过简单的函数调用从函数中使用 React 功能。我们提供的钩子涵盖了 React 的最重要部分:状态、生命周期和上下文。
那么,让我们看看我们可以用什么类型的钩子来替代componentDidMount
函数。
useEffect
useEffect
钩子使我们能够从函数组件中使用副作用。什么是副作用?例如,数据获取或订阅都是副作用。它们被称为副作用,因为它们可以影响其他组件,并且不能在渲染期间完成。
通常,这些操作是在类组件中使用生命周期函数执行的。你可以把useEffect
想象成所有这些生命周期函数合并在一个函数中。就像useState
一样,useEffect
可以在同一个函数组件中多次使用。
通过使用这个钩子,你基本上告诉 React 你的组件需要在渲染后做一些事情。React 会记住你传递的函数,并在执行所有更新后调用它。useEffect
在每次渲染后运行。所以基本上,它在第一次渲染后运行,并在你的组件进行每次更新后运行。
好了,那么componentWillUnmount
呢?我们如何确保我们的函数只在移除组件时才起作用?useEffect
就足够了,我们不需要另一个钩子。如果我们从我们的 effect 中返回一个函数,React 会确保在组件卸载时调用该函数一次。
Hooks 是 React 的一个非常重要的部分,需要大量的解释,我觉得你最好的学习方式就是阅读文档。还有其他的 hooks,比如useMemo
、useRef
和useReducer
。所以,阅读文档对所有程序员来说都是救命稻草,特别是因为你会在里面找到很多很酷的信息,我敢保证你在任何书中都找不到。当学习一门新技术时,你的第一步应该是阅读文档,然后研究其他更具体、更关键的方法来深入研究你真正想学习的东西。就像这本书一样,我们在这里学习如何构建一些 React Native 跨平台应用,所以让我们继续前进,当我们在下一章节开始使用 hooks 时,我们会更详细地解释。
总结
这一章已经涵盖了我们继续前进所需的大部分关于状态的信息。到现在为止,我们应该能够理解状态在类组件和函数组件中是如何工作的。
在学习了关于状态以及状态的确切含义之后,我们了解了一些生命周期函数以及它们的工作原理。学习这些非常重要,因为我们现在明白了一个组件会经历不同的阶段,并且在不同的时刻,我们能够干预一些 JavaScript 代码。
整个冒险给了我们一个想法,真实年龄应用。我们现在能够创建一个动态数字随时间变化的应用。我们学会了如何实现我们到目前为止学到的关于状态的一切,并创造一个展示我们年龄的绝妙想法。
因为类组件看起来有点像需要写太多代码,我们开始学习关于 hooks 的知识。经过仔细分析它们的区别,我们了解了一个叫做useEffect
的 hook。
长期来看,学习所有这些将会非常有益,特别是在接下来的章节中,那些都是关于实际挑战,我们将学到很多技巧,并创建许多不同类型的 React Native 应用。
第八章:创建您自己的自定义组件
经过所有这些课程的学习,我们现在准备好迎接更多实际挑战,这将使我们为创建完整的 React Native 应用做好准备。我们已经接触了所有基本和一些更高级的信息,所以我们准备好迎接更困难的挑战。
在本章中,我们将进行四个不同的练习。第一个将是一个简单的练习,我们将使用 Galio 的组件来创建新的组件,以适应我们想象中的应用程序。这样做将再次向我们证明 Galio 对于几乎所有编程需求都有多大帮助。
之后,我们将创建自己的个人资料卡。这个练习将主要关注布局和样式,因为我觉得这是任何应用程序创建的一个非常重要的部分。学会这个将使我们离梦想中的应用程序更近一步,因为如今几乎每个应用程序都在某个地方包含个人资料屏幕或卡。
下一个练习将涉及受控输入。除了创建一个简单的注册表单并尽力进行样式设置之外,我们还将了解在处理输入或一般表单时状态是必要的。
我们的最终挑战将是创建一个电子商务卡。这将证明几乎任何东西都可以通过弄清楚它与您已经创建的某些东西的相似之处来创建。这是我们可以理解在一个领域有经验肯定会在另一个领域有所帮助的时刻。没有经验是无用的;一切都帮助我们成为更好的人。
本章将涵盖以下主题:
-
创建您自己的组件!
-
创建您自己的个人资料卡
-
创建您自己的注册表单
-
构建您的电子商务卡
技术要求
您可以通过访问 GitHub github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
查看本章的代码。您将找到一个名为“第八章”的文件夹,其中包含本章中编写的所有代码。为了使用该项目,请按照README.md
文件中的说明进行操作。
创建您自己的组件!
现在我们已经了解了 React 和 React Native 的基本知识,是时候通过创建许多不同的组件来测试我们的技能了。不用担心,我们也将创建一个更大更复杂的应用程序。但是你知道,一个 React 应用程序是由许多不同的组件组成的,所以通过创建组件,我们实际上是在准备创建应用程序。
我在想,对于我们的第一个组件,我们应该从一个新闻卡开始。这通常会直接进入新闻动态,如果我们要创建一个新闻应用程序,我们会使用多个类似的组件来显示不同的文本。那么,我们该如何开始呢?
就像我们通常做的那样,用以下命令创建一个应用程序:
expo init chapter08
在本章的所有练习中,我们将使用相同的应用程序,因为这比为每个练习创建一个项目要容易得多。因此,在创建项目后,让我们打开它,然后打开我们的App.js
文件。
现在,我们要在根文件夹内创建一个新的components
文件夹。在这里,我们将开始开发我们自己的组件。在本章结束时,你应该在这个文件夹里有四个文件。
因为我们将使用 Galio 来创建我们组件的布局,所以我们现在应该通过终端安装它。记住我们用于安装外部包的命令吗?我们将使用以下命令:
npm i galio-framework
现在,让我们在components
文件夹内创建一个名为NewsCard.js
的新文件。因为我们正在创建一个新闻动态类型的组件,我们需要考虑在这个组件内部究竟需要使用什么。
我们确定需要StyleSheet
来设置样式和来自Galio的Block
组件。但是我们还需要一个Text
组件来渲染文本,以及一个Icon
组件,这样我们就能够拥有某种图标。我觉得每篇帖子也应该有一个头像,所以还需要一个Image
组件。
所以,我们的导入现在应该是这样的:
图 8.1 - 用于我们的 NewsCard 组件的导入
现在我们知道了在组件内部要使用的东西,让我们一步一步地开始构建它。我们将首先创建一个名为NewsCard
的函数组件。这个函数暂时只会返回一个Block
元素和一个Text
元素,以便有一些东西可以被渲染出来。
我们还将在文件末尾创建一个styles
对象。还记得我们应该这样做吗?很好!让我们为我们的主Block
组件创建一个名为card
的样式。在样式方面,我想添加一些新的东西,这是我们到目前为止还没有讨论过的:阴影。
阴影实际上并不难使用,但我觉得有些人可能真的不明白它是如何工作的。在添加样式之后,让我们来看看我们的组件到目前为止是什么样子的:
图 8.2 - 我们第一个组件的开始
所以,到目前为止,一切看起来应该相当简单易懂。这里的阴影是我们真正探讨过的唯一主题,但样式应该是不言自明的。首先,我们有shadowColor
,我们已经分配了#000
,也就是黑色。然后我们有shadowOffset
,它告诉我们的阴影如何从我们一直分配给它的对象上落下来。如果事情看起来仍然有点混乱,我们应该像这样考虑width
和height
的值:width
是x轴,height
是y轴。说width: 0
意味着我们的阴影预计会直接落到对象下面的地面上,但将其与height: 2
结合使用将告诉我们的阴影从中心向下掉落 2 个像素(px)。然后,我们有shadowOpacity
,它做你期望它做的事情:计算我们阴影的不透明度。你可能已经注意到了elevation
;这是你用来为 Android 设备设置阴影的,它只支持 Android 5.0+。
现在我们已经设置了新组件的基础,让我们将其导入到App.js
中,这样我们就可以实时看到我们的更改。因此,让我们打开文件并删除main
函数内除了主View
组件之外的所有内容。保留样式 - 我喜欢一切都居中。
现在,让我们导入我们新创建的组件并在屏幕上呈现它。我们将在主要导入的下方编写以下代码:
import NewsCard from './components/NewsCard';
现在我们已经导入了组件,将其放在View
组件内,像这样:<NewsCard />
。启动 Expo 服务器,打开模拟器,你应该能够看到带有文本News Card的卡片。太棒了!现在,我们可以开始工作了 - 保存文件并实时查看更改。
最终,我们将在我们的App.js
文件中添加我们创建的每个组件。这应该是一个非常简单的工作流程,可以用来测试我们的组件视觉效果。
现在,让我们回到我们的NewsCard.js
文件,并开始创建基本布局。
我们将从使用Block
组件来安排布局开始,所以我们将使用两个。第一个是用于卡片的标题,其中将包含卡片的最右边的“书签”图标,左侧将有头像和关于作者的信息。第二个是用于新闻文章的标题和文本摘要。让我们看看通过实践它看起来如何:
图 8.3 - 编写基本布局
所以,就像你看到的那样,对于title
、summary
、author
和date
,我们将使用props
。至于Avatar
,现在我们将使用一个Text
组件作为占位符。所以,让我们保存并转到我们的App.js
文件,以完成将所有 props 发送回我们的NewsCard
组件,如下所示:
图 8.4 - App.js 文件已完成我们的 NewsCard 组件的 props
好了,现在,我们要保存App.js
文件,并切换到我们的模拟器。我们应该能够看到我们的NewsCard
组件正在成形。有标题、摘要、日期,甚至作者。是的,我在摘要中使用了lorem ipsum
,因为这比实际创建摘要文本更容易更快。我们甚至可以用我们的组件开始一个新闻订阅。但现在,让我们回到我们的NewsCard
组件,并添加我们还缺少的东西。
当然,我们需要用实际的Image
组件替换我们一直在使用的占位符。所以,让我们用以下行替换那段文字:
<Image style={styles.avatar} source={{uri: props.avatar}}/>
你可能记得,图像需要一些样式才能渲染。让我们转到styles
对象,并做好我们图像所需的所有样式。我想设置width
和height
值为30px
,borderRadius
值为15px
。
现在,我们唯一缺少的就是回到我们的App.js
文件,并向我们的组件添加avatar
prop。在网上搜索一张图片并粘贴链接进去。现在,刷新一切,恭喜——我们有一张图像渲染了!
我想说的是,现在我们唯一缺少的就是给文本添加一些颜色,但我会让你自己来做。如果你没有和我同时编写代码,不用担心 - 只需转到 GitHub 并搜索Chapter 08
文件夹。这将包含我们到目前为止所做的所有代码,你还将看到我如何给文本上色。我还解构了props
对象。
现在,让我们看看这在我的模拟器上是什么样子,这样你就可以确保一旦在 GitHub 上克隆存储库,事情看起来和我们描述的一样。你可以在这里看到结果:
图 8.5 - 模拟器显示我们完成的组件
看起来不错,对吧?我会让你在未来的应用中尽情使用这个组件,所以不要害羞地重复使用你的组件。下一个应该会更酷,所以让我们继续,开始构建我们的第一个个人资料卡。
创建你自己的个人资料卡
个人资料卡是任何用户在具有用户系统的应用程序中需要看到的东西。所以,我想创建一个简单的个人资料卡,用于显示我们用户的一些基本信息。我觉得应该显示的主要元素是个人资料图片、用户的姓名、电子邮件和电话号码。
这将在一个应用程序中对我们非常有用,也许我们有一个电话联系人列表,我们想要单独查看每个联系人。现在,让我们开始创建我们的个人资料卡组件。
继续在我们的components
文件夹中创建一个名为ProfileCard.js
的新文件。现在,正如你之前所读到的,我已经说明了这个组件将由哪些元素组成。基于此,让我们考虑我们需要什么类型的导入。
你猜对了!和我们上一个组件中使用的相同的导入。既然我们确定了需要什么类型的导入,让我们写一个基本的函数,这样我们就可以在开始工作在组件上时在屏幕上看到一些东西。
正如你在组件的文件名中所看到的,我们的主要Block
组件应该是一个卡片,所以让我们应用与上一个组件相同的样式。我们会改变背景颜色和一些值,但这个style
对象应该大部分与上一个相同。
让我们看看我们到目前为止写了什么:
图 8.6 - 我们的 ProfileCard 组件的开始
看起来非常相似,对吧?有一些值已经改变了,但这是因为我觉得不同的颜色可能更适合这张卡片。根据我们为width
属性分配的"80%"
值,它应该与我们以前的组件具有相同的宽度。
现在,让我们转到我们的App.js
文件,注释掉我们的<NewsCard />
组件,并导入我们的新组件,就像我们以前做过的那样。
现在,我们应该能够在模拟器屏幕上看到这张没有内容的小卡片。让我们回到我们的卡片,继续添加布局的其余部分。
我们的组件左侧应该有一个图标,用户可能想要按下该图标以修改组件的内容。我们暂时不会创建功能,但在那里放一个指向该功能的图标应该已经足够好了。
在这个图标下面,我觉得我们应该有一个头像和联系人的名字居中显示在卡片上。
就在这些下面,电话号码和电子邮件应该可以供我们查看。在这两者之间,我想放一条线来分隔信息。为什么?在我看来,这样看起来更好。因此,让我们继续下一步,添加我们这种布局所需的所有基本组件,如下所示:
图 8.7 - 我们的 ProfileCard 组件的基本布局
使用 Galio 很容易实现这种布局原型。正如你所看到的,我们只是使用Block
组件,我们已经可以居中、创建行,并定义每个组件所需的空间。同样,我们正在使用props
,因为你现在的工作是回到App.js
,将props
传递给我们的组件,以便它可以渲染更多信息。
完成了?太棒了!你可能现在想知道我们在创建的两行之间的<Block />
组件是什么。好吧,那将充当分隔线。因此,让我们为它编写样式,以及我们的avatar
图像的样式。在这一点上,你甚至可以为每个Text
组件添加颜色,这样你就可以使其看起来更有趣。我可能会使用白色的文本,但只要你喜欢,任何颜色都可以。让我们看看我们的样式是如何的,如下所示:
图 8.8 - 我们的分隔线和头像的样式
现在我们已经创建了样式,让我们深入一下。分隔线应该是我们的“电子邮件”和“电话号码”之间的一条白线。因此,我们使用了一个Block
组件来创建一条直线。这应该让你意识到你可以用Block
组件有多少种方式。不过,hairlineWidth
是怎么回事呢?这是由 React Native 定义的特定平台上细线的宽度。它主要用于在两个元素之间创建分隔。
现在,让我们保存一切,看看在模拟器上的效果。输出应该与我这里的类似。也许你改变了一些颜色,但布局应该是相同的:
图 8.9 - 我们组件的最终渲染
这真是一次真正的冒险!我们已经创建了两个不同的组件,而且我们还没有停下来。我希望你玩得开心,并且在你面前有一些代码。从记忆中重新创建一切通常是一个好主意,大约 2-3 天。这只是一个很酷的小练习,可以确保你学到了你所阅读的一切。现在,让我们继续前进,因为接下来的内容将会非常酷。
创建您自己的注册表单
几乎每个应用程序中都使用注册表单。你可能需要一个,所以让我们看看创建注册表单时发生了什么。这非常酷,因为除了创建一个漂亮的小注册卡之外,我们还将学到有关输入的新知识。
让我们像往常一样开始 - 注释掉App.js
中的先前组件,并在我们的components
文件夹中创建一个名为RegisterForm.js
的新文件。
我们已经创建了两个组件,所以让我们看看你是否可以开始自己创建这个。以下截图中的表单将是我们注册表单的最终渲染版本。我选择让你在实际开始创建之前先看一下,因为我认为你应该能够在没有我的帮助下自己实现类似的结果。当然,我仍然会帮助你,但这是一个很好的机会,花点时间,关闭书本,然后自己开始创建。看看以下截图,然后开始自己创建吧!
图 8.10 - 我们注册卡的最终渲染版本
看起来相当整洁,对吧?根据我们到目前为止所做的事情,这并不难创建。所以,既然你已经看过了,也许你已经在考虑如何开始处理这个组件了。太好了!如果你还在阅读,也没关系,因为我们将立即开始创建这个组件。
就像我们到目前为止所做的那样,我们将开始考虑我们需要有什么类型的导入。我们不再需要图像,但我们确实需要一个Input
和一个Button
组件。不要担心输入框内放置的图标-你可以直接从Input
组件中做到这一点。Galio 让在输入框内添加图标和样式变得非常容易。
我觉得我们的输入框应该看起来像这样,特别是对于这个特定的组件:
图 8.11-用于我们的注册表单的导入
你已经想到我们应该如何为这个创建布局了吗?我们这里不需要任何行,因为所有的元素都是直接垂直排列的。我们将使用的唯一的Block
元素是用于创建卡片本身的元素。
让我们开始写我们的主要函数,就像我们以前做过的那样。我们需要一个带有卡片样式的Block
组件,如下所示:
图 8.12-我们的 RegisterForm 组件的开始
现在,让我们进入我们的App.js
文件,并注释掉以前的组件,以便我们可以导入我们新创建的组件。到目前为止,我们已经做过这个多次了,所以这应该很容易。
现在,让我们继续我们的组件,快速浏览一下布局。由于我们已经做过多次了,这应该不难理解。
显然,我们首先使用一个Text
组件,然后是三个Input
组件和一个Button
组件。所以,让我们写下来,如下所示:
图 8.13-我们几乎完成的组件
好了,所以,一切基本上都是我们到目前为止一直在做的事情。让我们来解决这里发现的新东西。所以,在我们的第三个Input
组件上,我们可以看到两个 props:password
和viewPass
。第一个是为了确保您在输入时看不到密码;它会将您的输入转换为我们在输入密码时经常看到的那些点。第二个是为了在右侧显示那个图标,用户可以按下它以查看他们刚刚输入的密码是否有问题,基本上是将点转换为字母,反之亦然。
我们的Button
组件也有shadowless
属性,它的作用正如你所想的那样:使按钮没有阴影。
现在,有趣的部分来了。当然,我们想知道用户在输入什么;否则,我们怎么能验证信息是否正确,或者是否按我们希望的方式输入?也许你要求用户输入电子邮件,但如果输入的是一些随机单词,只是为了不注册就进入应用程序呢?因此,我们必须有一种方法来确保我们知道用户输入了什么,并在用户按下立即注册或提交按钮后验证文本。
这种技术称为受控组件。受控组件通过 props 获取其当前值,并通过回调发送任何更改。父组件通过处理回调并管理自己的状态,然后将新的状态值作为 props 传递给受控组件来“控制”它。
在大多数情况下,甚至是所有情况下,当我们处理表单时,应该使用受控组件。
因为我们在一个函数组件中,我们将使用钩子来处理我们的状态。不要忘记导入useState
钩子,如下所示:
图 8.14 - 在我们的函数组件中使用的钩子
这很容易,因为我们已经学习过钩子和组件的状态。现在,让我们将我们的状态应用到我们的Input
组件中,如下所示:
图 8.15 - 应用于我们的输入组件的状态
那么,这里到底发生了什么?一旦用户按下输入并开始输入他们的姓名或电子邮件,例如,我们的onChangeText
属性会触发我们的setName
属性,这将把name
状态变量设置为我们输入的当前值。这样,我们确保我们的RegisterForm
组件控制着输入,并且始终更新有关我们输入状态的信息。
对于一些人来说,可能有点难以理解为什么我们需要它。事实上,这就是 React 确保我们的输入状态不会出现任何错误的方式,同时还让我们完全控制和了解我们输入的当前状态。
现在,让我们为我们的表单编写一个简单的验证。我们至少需要在用户没有输入任何内容并按下立即注册按钮时弹出一条消息。
因此,我们将创建一个名为registerButton
的函数。您可以按自己的意愿命名,但我称它为这个是因为对我来说有意义。这个函数将验证我们输入值的长度。现在,如果我们没有这个受控组件,我们将无法通过普通变量访问这些值。我们可能需要使用一种叫做refs
的东西。
这在我们不打算学习refs
的情况下并不重要,但重要的是要知道有一种叫做refs
的东西。让我们来看看这个registerButton
函数,如下所示:
图 8.16 - 我们的表单验证函数
现在我们有了这个函数,我们只需要在用户按下立即注册按钮时调用它,所以我们将在我们的Button
组件上使用onPress
属性。继续并将此属性应用到我们的Button
组件中,就像这样:
onPress={() => registerButton()}
现在,保存并刷新应用程序,然后试一下!很酷,对吧?每当您尝试按下没有输入文本的按钮时,会弹出一条消息,同时在我们使用输入中编写的值时,还会出现另一条很酷的消息。
这并不是一个难以创建的组件,但我们学到了一些真正酷的东西,那就是受控组件。我希望这个小练习成功地教会了你一些新东西,从现在开始在处理表单时你会经常使用它。
现在我们已经完成了这个组件,让我们继续开始处理与电子商务移动应用程序相关的另一个组件。
构建您的电子商务卡
你知道,每当你在网上购物时,总会有一个装满你选择的产品的购物篮。篮子里的每件物品通常都是一张卡片,上面有关于价格、物品名称、图片以及增加或减少相同类型物品数量的信息。
所以,这也是我们要创建的东西。让我们来看看这里,因为我们已经变得如此先进,现在应该能够想出只用到目前为止学到的东西来创建功能的方法:
图 8.17 - 我们电子商务卡的最终渲染版本
看起来不错,对吧?老实说,甚至构建起来也不那么难,所以我们将快速通过布局。让我们在components
文件夹中创建一个名为CommerceCard.js
的新文件。
现在,让我们想想我们需要哪些类型的导入 - 显然,需要一个Block
和Text
组件。我们还需要导入Icon
组件,因为正如我们在图 8.17中看到的,那里有一个减号按钮和一个加号按钮。为了使这些按钮可点击,我们将使用一个名为TouchableOpacity
的react-native
组件,所以也让我们导入它。除此之外,正如我们都能看到的,我们还有一个Image
组件。让我们看看我们所有的导入是什么样子,如下所示:
图 8.18 - 我们将用于创建 CommerceCard 组件的导入
我们还导入了useState
,因为数字将根据我们按下的图标而改变。所以,让我们现在开始创建我们的功能组件,如下所示:
图 8.19 - 我们组件的布局
看起来并不难读,对吧?让我们解释一些,因为我们现在走得有点快。但这是因为我觉得你已经进步了很多,所以你应该尝试挑战自己,看看你的想法是否与我写的组件和我们在图 8.17中看到的相匹配。
据我们所见,我们可以有一个包含所有内容并使内容在一行中的大Block
组件。行中的第一个元素是我们的图像。之后,我们有另一个具有flex
prop 的Block
组件,基本上告诉我们的组件尽可能占据空间。
在Block
组件内部,我们有一个Text
组件,它接收项目名称作为名为itemName
的 prop。然后我们有另一个Block
组件,应用了row
prop,它将用于分隔价格和数量,这两者都将是状态变量。
现在,让我们看看样式是什么样子的 - 相信我,样式很简单。在这里:
图 8.20 - 组件的样式
如您所见,我们在这里使用的样式实际上并不复杂。因此,让我们继续讨论这个组件的工作原理。
您可能还记得,我说过我们将使用状态来存储我们的价格和数量,所以让我们初始化我们的状态,如下所示:
图 8.21 - 初始化组件状态
现在,我在想,我们可以通过 prop 传递价格;这样,这个组件对于将来的情况更具重用性。因为这是通过 prop 完成的,所以我们应该使用生命周期函数,就好像我们在编写类组件一样,因为这是一个函数组件 - 正如我们记得的那样,我们可以使用useEffect
代替生命周期函数。因此,让我们在导入useState
的地方同时导入useEffect
。
现在,让我们看看我们应该如何编写useEffect
函数,如下所示:
图 8.22 - useEffect 函数用于初始化价格状态变量
因此,当调用useEffect
时,其中的setPrice
函数将被调用,这将设置我们的price
状态变量为 prop 发送的任何数字。但是作为useEffect
函数的第二个参数使用的[props.price]
参数是什么意思呢?
这告诉我们的useEffect
函数仅在props.price
变量发生变化时才会被调用。
既然我们已经初始化了我们的price
变量,让我们根据数量来改变价格。我们应该如何做呢?我写了一个名为quantityMath
的函数,它接收一个名为action
的字符串变量,这将告诉我们的函数数量是应该下降还是上升。
众所周知,当我们在线购物时,篮子中的每件物品都有一个加号和一个减号,每当按下时,要么增加一个数量,要么减少一个数量。基于此,我们计算该物品的总价格。
现在,让我们来看看这个函数,如下所示:
图 8.23 – 用于计算最终价格的 quantityMath 函数
既然我们已经创建了这个函数,让我们确保当用户按下按钮时,这个函数被调用。TouchableOpacity
是一个用于使其他组件可按压的组件。所以,让我们去其中一个TouchableOpacity
组件,将onPress
属性改为{() => quantityMath("minus")}
。当然,我们将使用minus
作为quantityMath
函数的参数,用于减号图标,plus
用于加号图标。让我们来看看这在我们的代码中是什么样子的:
图 8.24 – 实现 quantityMath 函数
现在我们的组件已经完成,让我们进入我们的App.js
文件并测试一下。注释掉之前的组件,然后导入我们新创建的组件。现在,让我们将这个组件添加到我们的主函数中,就像这样:
图 8.25 – 主应用程序函数,其中包含我们的 CommerceCard 组件 inside of it
保存所有文件,刷新应用程序,你应该能看到我们的卡片。继续玩加号和减号按钮,你会看到一切都根据你想要的数量准确地改变。
这很酷,对吧?现在我们有了一个很酷的小组件,我们可以在想要为电子商务应用程序进行原型设计时使用。
总结
在学习了关于 React 和 React Native 的工作原理之后,我们终于到了更多实际挑战的阶段。我们首先创建了一个简单的组件,主要关注样式和布局。
那是容易的部分,我们下一个组件的第一步是看到一个不同的创建布局的例子,我们加强了大脑肌肉,这样我们就可以更容易地开始自己原型化组件。
接着,我们开始涉及更严肃的组件,那就是注册表单,我们学到了一个叫做受控输入的新概念。这真的很有趣,因为我们学会了如何在 React Native 中实际解决表单问题。
我们的下一个组件甚至更酷,因为我们使用useEffect
函数来初始化我们组件接收到的一个 prop 变量。现在,这真的很酷,我希望你和我第一次发现这个函数时一样兴奋。
现在我们已经完成了更多的实际挑战,是时候考虑调试的工作原理了,这样我们就可以确保知道如何正确地找出组件的问题所在。当涉及到 React Native 时,我们还会了解一些调试的限制。让我们继续这个酷炫的冒险,更接近创建我们自己的跨平台移动应用程序。
第九章:调试和寻求帮助
我们已经经历了很多。我们已经学会了如何创建不同类型的组件;我们已经了解了 props 和 state 以及它们在组件创建中的重要作用。我们还了解了生命周期函数。到目前为止,我们已经获得了很多知识,但我们仍然没有办法测试我们的组件,以查看它们是否具有我们期望的行为。
在本章中,我们将学习调试,并了解最流行的调试选项,如 React DevTools 和 React Native 调试器。我们还将学习一些其他调试替代方案,以便在需要时确保我们使用正确的工具。
我们将学习有趣的概念,如类型检查和 linting。我们还将了解开发者菜单以及 React Native 为我们提供的一些功能,以快速发现我们的应用是否存在任何类型的问题。
在本章结束时,我们应该对调试有一些了解,以便在某些东西不按我们的预期工作时做好准备。这将是在创建更复杂的应用程序之前的最后一步。
本章将涵盖以下主题:
-
调试的不同方式
-
React Native 调试器
-
在需要时寻求帮助的地方
不同的调试方式
众所周知,开发人员是人类,人类会犯错误。坦率地说,我觉得软件开发人员犯的错误比普通人类要多得多,所以当然,必须有一些方法来解决由于我们的错误而产生的错误。
在计算机编程中,查找和解决错误的过程称为调试。在解决错误时,您可以使用许多调试策略,因此我们将尝试并在本节中介绍其中一些。了解它们肯定会在我们的 React Native 之旅中解锁新的成就。
我们将开始这个有趣的探索,找出如何在开发阶段使用不同的格式化工具来确保错误越来越少。
Linting,类型检查和格式化
作为开发者,我们大多数时候都想把注意力集中在业务逻辑、代码模式和最佳实践等方面。通常情况下,我们不想花时间确保每一行都正确缩进,或者检查某个函数需要接收什么类型的参数。为了简化我们的生活和代码编写过程,我们可以确保所有自动化工作都委托给我们的代码编辑器。我个人非常喜欢Visual Studio Code,但在之前的章节中我们已经讨论过,你可以使用任何你喜欢的代码编辑器。
类型检查
验证和强制类型约束的过程称为类型检查。这一切都是为了确保类型错误的可能性尽可能地降低。在 JavaScript 中,我们不必指定变量中将存储什么类型的信息,这都是因为 JavaScript 是一种弱类型语言。但对我们的代码加上约束或限制将使我们编写更加深思熟虑的代码,让我们更加小心地思考我们正在编写的代码。
在类型检查方面有两个很棒的工具:TypeScript和Flow。这两者之间的主要区别在于 Flow 只是一个类型检查器,而 TypeScript 是 JavaScript 的超集,基本上意味着它将包含更多 JavaScript 的下一代特性。
Linting
Linting 是执行程序以分析潜在程序语法错误的过程。JavaScript 最著名的 linting 插件有ESLint、JSHint和JSLint。我个人使用 ESLint,现在甚至有一个官方的 TypeScript linting 插件。
你会发现大多数人选择 ESLint,但这并不意味着它就是最好的;你需要弄清楚哪种工具对你来说最有效,所以尝试花几分钟去搜索它们。我通常选择拥有最大社区的工具,因为这样更容易找到如何修复某些错误的方法。
格式化代码
作为程序员,你大部分时间都将花在阅读代码上,所以你必须确保你正在阅读的代码是可读的。假设我们想快速编写一个类组件;我们已经知道如何做了,所以也许我们甚至不再看屏幕。
因此,我们并不真的关注代码的外观,但这并不重要,因为我们已经是优秀的程序员,我们知道它能工作。这就是未格式化的代码的样子:
图 9.1 – 未格式化的类组件
我的意思是…是的。这看起来不太好。它能工作,但是…我们甚至从何处开始理解这个大香肠中发生了什么?现在让我们看看一旦我们保存文件后我们的代码会发生什么:
图 9.2 – 格式化的类组件
哦!看起来好了十倍,对吧?我们可以很容易地跟踪这里写的代码。当代码格式良好时,阅读和理解代码就变得更容易了。
有多种不同的代码格式化工具,但其中最常用的一个,也是我最喜欢使用的一个是Prettier。这很容易与您喜欢的代码编辑器集成和配置。
顺便说一句,您甚至可以配置您的 linter 来使用它来格式化代码,所以也许,如果您真的不喜欢 Prettier,您实际上可以配置 ESLint 来为您执行这项任务。
应用内开发者菜单
我们可以从模拟器内部访问一堆不同的工具,React Native 为我们提供了这些工具。这些工具非常酷,所以让我们看看如何在模拟器中测试应用时访问应用内开发者菜单。
访问开发者菜单的第一种方法是摇动设备或在 iOS 模拟器的硬件菜单中选择摇动手势。
第二种方法是使用键盘快捷键。对于 iOS 上的 Mac,快捷键是Cmd + D,对于 Android 则是Cmd + M。对于 Windows,Android 模拟器的快捷键是Ctrl + M。另外,对于 Android,我们可以运行以下命令来打开开发菜单:
adb shell input keyevent 82
一旦我们使用了上述方法之一,将打开以下菜单:
图 9.3 – 开发者菜单
正如我们所看到的,这里有一堆选项,所以让我们谈谈每一个。首先,对于调试目的,我们真正感兴趣的是调试远程 JS,显示性能监视器和显示元素检查器。让我们从第一个开始。
调试远程 JS
点击此按钮将在我们的 Chrome 浏览器中打开一个新的标签,其中包含以下 URL:localhost:8081/debugger-ui
。
从 Chrome 菜单中选择工具 | 开发人员工具,以打开开发人员工具。React Native 还建议启用捕获异常时暂停以获得更好的调试体验。您可以通过转到源选项卡来执行此操作,并且您会在右侧的某个位置找到此复选框,紧邻用于断点的常规按钮。
显示性能监视器
这个实际上相当酷。一旦您点击此按钮,它将启用性能叠加,以帮助您调试性能问题:
图 9.4 - 性能叠加
让我们看看在前面的截图中我们看到了什么。我们将从左到右开始,解释每一列:
-
RAM - 应用程序正在使用的 RAM 量。
-
JSC - JavaScript 代码管理堆的大小。它只会在垃圾收集发生时更新。
-
视图 - 顶部数字是屏幕上视图的数量,底部数字是组件中视图的总数。底部数字通常较大,但通常表示您有一些可以改进/重构的内容。
-
UI - 每秒主要帧数。
-
JS - JavaScript 每秒帧数。这是业务逻辑所在的 JavaScript 线程。如果 JavaScript 线程在一帧内无响应,它将被视为丢帧。
显示元素检查器
就在这里!我们开发菜单中的最后一个选项。让我们点击它,看看会发生什么。我们的屏幕有点改变了:
图 9.5 - 一旦我们启用元素检查器
现在我们已经点击了它,我们可以看到它要求我们点击某些内容以便检查它。与此同时,我们还可以看到下面有四个不同的选项卡,分别称为检查,性能,网络和可触摸。
这些都可以像使用 Chrome 开发人员工具一样使用,但有更多限制,因此您可能更喜欢使用开发人员工具。让我们至少点击一个元素,看看我们点击后它是什么样子:
图 9.6 - 一旦我们点击了商务卡,我们的元素检查器
一旦我们点击了商务卡,我们就可以看到它顶部有一个蓝色的覆盖层,周围有一个绿色的边框。那个绿色的边框代表了填充。但让我们把注意力集中在屏幕上部,我们的检查器现在已经移动到那里。
在检查器的上部,我们可以看到组件树,它基本上告诉我们我们点击了哪个组件。所以,我们点击了Block
组件内的View
组件,它位于Context.Consumer
组件中。我想我们甚至可以进一步阅读,看到这都是我们在上一章中创建的CommerceCard
的一部分。
在组件树下面,我们有应用在我们点击的 View 上的样式。在它的右边,我们有关于大小、填充和边距的信息。
实际上学习如何使用 React 和 Expo 团队为我们提供的所有这些内部工具的最佳方法是实际操作它们。您可能不会像使用以下工具那样经常使用它们,但我非常确定您会想要尝试它们。以下工具是最常用于调试的工具之一。
React Native Debugger
React Native Debugger 包含了几乎所有调试 React Native 应用程序所需的工具。这就是为什么我完全推荐使用这个,因为它里面包含了您需要的一切。
这基本上是一个基于官方远程调试器的独立应用,但实现了更多功能。它还包括React 检查器、Redux 开发工具和Apollo 客户端开发工具。我们现在并不真正关心 Redux 和 Apollo,但您很可能会偶然遇到Redux,因为它是最常用的状态管理库之一。
您可以通过以下命令在 macOS 上安装 React Native Debugger:
brew install --cask react-native-debugger
如果这个命令不起作用,您应该确保您已经安装了Homebrew。Homebrew 是一个模块管理器,您肯定会在不同的编程工具中继续使用它。要安装 Homebrew,请访问brew.sh
。
要在 Windows 上安装 React Native Debugger,我们必须转到以下网址:github.com/jhen0409/react-native-debugger/releases
。下载.exe
文件并打开它。
现在软件已经打开,按下 Windows 上的Ctrl + T或者 Mac 上的Cmd + T。这将打开一个新窗口,您将被提示指定端口。在那里写入19000
,然后点击确认:
图 9.7 - 打开用于更改端口的窗口
现在我们可以使用expo start
或expo r -c
来运行我们的项目。之后,打开开发者菜单,选择调试远程 JS。调试器现在应该会自动连接。
现在,您应该能够看到元素树,以及您选择的任何元素的 props 状态和子元素。在右侧,您将看到 Chrome 控制台:
图 9.8 - React Native Debugger 连接到我们的模拟器
如果您在 React Native Debugger 中的任何地方右键单击,您将看到我们有一些很酷的小快捷方式,可以用来重新加载我们的应用程序,启用元素检查器或网络检查器,同时还可以清除我们的AsyncStorage内容。
我们甚至可以使用这个工具来检查我们的网络流量,所以在任何地方右键单击,然后选择启用网络检查。这将启用网络选项卡,并允许我们检查fetch
或XMLHttpRequest
请求。由于使用 React Native Debugger 检查网络存在一些限制,您可能想寻找一些替代方案。它们都需要代理,但是这里有一些您可能想了解的替代方案:Charles Proxy,mitmproxy和Fiddler。
正如我们所知,React Native Debugger 内部实现了 React DevTools,所以也许您不想一次使用所有工具,而是真的很希望看到带有一些属性的组件树。
尽管我们已经安装了 React Native Debugger,但我真的建议至少要记住,我们也可以单独使用其中包含的每个工具。
React DevTools
这个工具非常适合查看组件树和每个组件的 props 和状态。首先,如果我们想安装它,我们需要使用以下命令通过npm
进行安装:
npm install -g react-devtools
这将在您的计算机上全局安装 React DevTools,但您可能只想将其安装为项目依赖项。如果是这种情况,您可以通过以下命令进行安装:
npm install –-dev react-devtools
现在我们在计算机或项目上安装了 React DevTools,让我们使用通常的expo start
命令启动我们的项目。在打开项目后,让我们打开一个新的终端窗口,并运行以下命令:
react-devtools
这将打开一个新窗口。现在我们需要在模拟器内打开开发者菜单,然后点击调试远程 JS。这与之前的过程相同,但我们不需要使用 React DevTools 设置端口,因为它会自动连接到我们的项目。我们可以通过查看以下截图来看应用程序的外观:
图 9.9 – 用于调试远程 JS 的 DevTools 独立应用程序
据我们所见,这与 React Native Debugger 中的左下窗口完全相同。我主要会使用这个,因为这样更容易查看我的组件,但随着应用程序变得更大,你可能会看到我切换到 React Native Debugger。
总的来说,这是一个非常好的工具,我强烈建议如果您对 Chrome 的开发者工具没有太多经验的话,可以尝试一下,因为这些工具与网页开发者所熟悉的非常相似。
现在我们已经了解了一些用于调试 React Native 应用程序的工具,让我们看看如果问题无法通过这些工具进行调试,我们还能做些什么。或者,也许有些人甚至认为这些工具太麻烦,所以让我们看看我们可能会遇到的一些问题的其他解决方案。
当您需要帮助时可以寻求帮助的地方
我知道事实上几乎所有程序员在开发产品或已有产品的功能时都会遇到困难。因此,当错误出现时,我们应该知道该怎么办。
有时,您可以通过堆栈跟踪准确地知道出了什么问题,但其他错误可能一开始会更难理解。堆栈跟踪是指每当代码出现问题时,在模拟器上弹出的大红色错误消息。
首先,我认为您应该知道,因为我们使用的是 React Native,而且社区如此之大,几乎所有的错误消息都可以在 Google 上搜索到。总会有人有解决您错误的办法。
另一个很好的解决方案是隔离引发错误的代码。您可以通过发现确切引发错误的行,然后注释掉该部分来做到这一点。通过隔离代码,我们可以开始单独尝试该部分,并通过反复试验,找到可行的解决方案。
您应该开始养成的一个非常好的习惯是使用console.log
。您可以使用它来发现您的代码是如何工作的。例如,通过在我们对状态变量进行操作之前和之后使用它,我们可以通过不断跟踪代码内部的变量来看到变量的确切变化。使用console.log
而不是调试器中的断点的唯一问题是,当我们有任何类型的async
代码时,我们可能意识不到一些代码在不同的点上发生了变化,这可能超出了我们的控制。
如果您能尽可能简化您的代码,您可能会比通常更容易地追踪错误。因此,您会发现 GitHub 上的一些存储库要求在其错误报告中提供最小可重现演示。这使他们能够看到您正确地识别了问题并将其隔离。因此,如果您正在开发的应用程序有点过大和复杂,请提取功能并尝试识别特定错误。
当然,您可能也会遇到一些生产错误。一些错误和漏洞可能只会在生产模式下出现。因此,不妨偶尔以生产模式运行您的应用程序,通过运行以下命令来测试一下:
expo start --no-dev --minify
了解生产错误的最佳第一步是在本地重现它。之后,只需隔离问题并找到一个好的解决方案。
Expo 团队推荐使用 Sentry 等自动化错误记录系统。这个工具将帮助您跟踪、识别和解决生产应用程序中的 JavaScript 错误。它甚至为您提供了源映射,这样您就可以获得错误的堆栈跟踪。这个工具每月免费提供 5000 个事件。
让我们想一想,如果我们的生产应用程序崩溃了,我们会怎么做。可能的原因是什么?乍一看,这是一个非常令人沮丧的情景之一。事实是这很容易理解和解决。
您应该做的第一件事是访问本机设备日志。您可以按照您使用的平台的说明来做到这一点。我们将在接下来的部分中看到如何在每个平台上检查日志。
iPhone/iPad 的日志
按照以下步骤检查您的 iPhone 日志:
- 打开终端并使用以下命令:
brew install --HEAD libimobiledevice -g
- 现在,安装了这个软件包,插入您的 iOS 设备并运行以下命令:
idevicepair pair
-
在设备上点击接受按钮。
-
返回计算机并运行以下命令:
idevicesyslog
恭喜!现在你可以查看你的 iPhone 日志了。
Android 日志
确保你的 Android SDK 已安装。确保 USB 调试已启用。如果没有启用,你应该能在developer.android.com
找到如何做的信息。你要找的信息应该在用户指南 | 构建和运行应用程序 | 在硬件设备上运行应用程序下。
现在插入你的设备并在终端内运行adb logcat
。
恭喜!现在你可以查看你的 Android 日志了。
太棒了!我们已经找到了如何检查我们的日志,这应该能指引你在解决错误的冒险中朝着正确的方向前进。用“fatal exception”这几个词搜索日志,这应该能迅速指出错误。现在重现这些错误!通过重现它们,我们确保我们对它们的行为方式的假设将得到验证。
好的,但如果我的应用程序只在特定或较旧的设备上崩溃怎么办?这有 90%的可能性表明存在性能问题。在这种情况下,你最好的做法是通过性能分析器运行你的应用程序,看看到底是什么导致了你的应用程序崩溃。嗯,我们知道一个好的性能分析器吗?是的,React DevTools 或 React Native Debugger 都包含了性能分析器。我真诚地建议你阅读reactnative.dev/docs/profiling
,因为里面有大量关于如何准确识别哪些进程占用大量内存并可能导致应用程序崩溃的信息。
仍然无法弄清楚应用程序出了什么问题?
现在是认真考虑休息的绝佳时机。我知道这听起来有点奇怪,但在某些情况下,10 分钟的休息是救命的。有时我甚至会把问题搁置到第二天,一旦我打开 Visual Studio Code,解决方案就会迎刃而解。
你也可以再次尝试谷歌搜索。在 GitHub 的Issues部分、Stack Overflow、Reddit 和 Expo 论坛是找到解决方案的最佳地方。
总结
这一章不像其他章节那样详尽,但我希望你能找到所有必要的信息,以便开始解决在使用 React Native 和 Galio 开发过程中可能遇到的所有问题。
我们已经学习了一些工具来防止我们编写代码时出现错误。我强烈建议你去了解所有这些工具,并进行更多的研究,因为众所周知,知识就是力量。你了解的工具越多,当你找到适合你的完美匹配时,你会感到越好。
经过这些工具的学习,我们了解了 React Native 内置的调试和性能分析工具。我们学会了如何使用开发者菜单中的功能,希望你明白,尽管这里提供的信息很简要,最重要的是让你去尝试所有这些工具。
我们还了解了 React DevTools 和 React Native Debugger。现在我们知道如何安装和启动这些工具,应该很容易地尝试我们的应用程序,以更多地了解 React Native 的工作原理。
我们还学会了一些找出错误来源的方法和策略。我真的希望我能很好地解释这些话题,因为它们通常伴随着你的编程经验。尽管我明白调试并不是最令人兴奋的体验,但它是工作的一部分,当你真正需要它的时候,学习它确实很酷。
现在让我们继续前进,因为是时候进行一些实际挑战了!我们将从为我们将在本书后面创建的秒表应用程序构建引导屏幕开始。我真的希望你已经准备好了一些巧妙的技巧,因为引导屏幕将教会我们很多关于FlatList
以及如何使用引用来通过另一个组件控制组件的知识。现在,准备好,开始吧!
第十章:构建入职屏幕
我们经历了很多,可以说现在终于是时候开始构建不仅仅是组件了。我们将首先创建任何应用程序中非常重要的部分,即入职屏幕。
我们将详细介绍入职屏幕到底是什么,以及它在应用程序中的目的。我们将了解到有许多类型的入职屏幕,但我们将专注于创建其中的一种。
通过学习如何创建这种类型的屏幕,我们将学到许多我们到目前为止还没有接触过的新概念。这些新概念将对你未来构建许多不同类型的屏幕非常有帮助。通过学习许多新东西,我们可以超越我们的创造限制,并为未来的挑战做好更充分的准备。
我们将学习动画以及如何为我们的屏幕创建一个酷炫的动画。这将为我们的客户打开创建更流畅用户体验的大门。我们将了解插值和外推的含义,以及如何使用它们来构建动画。
我们还将更深入地了解 Hooks 以及如何使用useRef
。是的,我们还将学习一个新的惊喜 Hook,它将帮助我们比以往更快地找到屏幕的大小。
最后但并非最不重要的是,我们将学习如何使用一个比我们到目前为止使用的任何工具更高效的酷组件。这个酷组件叫做FlatList
,它将帮助我们为我们和我们的用户创建一个酷炫的入职体验。
在本章结束时,我们将拥有一个很棒的入职屏幕,我们将把它用作下一章应用项目的主要开屏。
本章将涵盖以下主题:
-
什么是入职屏幕,我们在哪里可以使用它?
-
创建一个新项目
-
分页器
-
添加自动滚动功能
技术要求
您可以通过访问 GitHub github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
查看本章的代码。您将找到一个名为Chapter 10
的文件夹,其中包含我们在本章中编写的所有代码。要使用该项目,请按照README.md
文件中的说明进行操作。
什么是入职屏幕,我们在哪里可以使用它?
我们应该从理解入职屏幕的确切含义开始这一章。入职屏幕就像是在应用使用之前对应用的简短介绍。这是欢迎用户的第一个屏幕。
您的应用的入职应该在欢迎用户方面有一个具体的目标。您必须确保您的入职将帮助用户了解他们应该如何使用应用,同时也让他们对他们将能够使用的功能感到兴奋。
如果您确保入职屏幕对用户来说是一次很棒的体验,那么您可以期待在用户开始使用应用的最初几天里获得更多的参与度。高参与度意味着用户很满意,这意味着您的应用正在创造一个非常好的用户体验。
入职屏幕应该只出现在首次使用的用户面前。我们都知道在游戏中重新做教程会有多烦人。即使只需要大约 30 秒到 1 分钟来完成,这也可能让回头用户对体验感到恼火。
我建议阅读更多关于入职的内容,可以去谷歌的 Material 网站,他们推荐了不同的设计理念和指南,用于为安卓手机创建一个好的入职屏幕:material.io/design/communication/onboarding.html
。当然,大部分规则也适用于 iOS。
现在我们已经弄清楚了入职屏幕是什么,以及何时何地使用它,是时候弄清楚对于我们的应用来说,这个入职屏幕究竟会是什么样子了。同时,这也是我们讨论这将是什么类型的应用的好时机。
下一章将专注于我们应用的逻辑部分,而本章将专注于为我们的应用创建所需的入职屏幕。所涉及的应用将是一个秒表/计时器应用。鉴于此,我决定设计一个入职体验,让用户了解应用的实用性。
让我们来看看我们为这个应用设计的入职屏幕会是什么样子:
图 10.1 - 我们将创建的入职屏幕
这看起来很酷,对吧?它将由四个不同的屏幕组成,每个屏幕都有不同的图像、标题和描述。我们将能够向左/向右滑动以前进并阅读所有屏幕,同时也可以按下“下一步”按钮,它将为我们滑动。文本后面的四个小点将被动画显示,以告诉我们当前在哪个屏幕上。
老实说,我真的很喜欢它的外观,我迫不及待地想完成这个入门屏幕,这样我们就可以开始创建完整的应用程序了。在本章结束时,你将准备好开始创建入门屏幕。
现在,让我们开始工作在我们的屏幕上。
创建一个新项目
现在我们知道了我们的项目将是什么样子,以及为什么需要这个屏幕,是时候开始工作了。
在编码之前,让我们收集我们将在这个项目中使用的图像。我使用了来自undraw.co
的图像,该网站提供开源的.svg
和.png
图像。我下载了四个不同的.png
图像,并将它们放在assets/onboarding
文件夹中。onboarding
文件夹是我专门为这个屏幕在assets
文件夹中创建的新文件夹。
让我们从打开终端窗口并移动到你通常用于项目的目录开始。现在,让我们写下我们通常的命令,开始吧:
expo init chapter10
现在我们有了一个新项目,让我们安装 Galio。我们可以使用以下终端命令来做到这一点:
npm i galio-framework
现在我们已经设置好了一切,让我们打开我们的项目并开始编码。
首先,我们将从做我们一直在做的老把戏开始,那就是进入我们的App.js
文件,并删除App()
函数中View
内的所有内容。我故意略过了<StatusBar />
组件,因为你可能希望对其进行样式设置或隐藏。
现在,在我们的主文件夹中,让我们创建一个名为components
的文件夹。这是我们将放置为这个美丽的应用程序创建的每个组件的地方。在本章结束时,你将在这个文件夹中有三个文件。
现在我们的文件夹已经创建好了,在其中创建一个名为Onboarding.js
的新文件。这将作为我们的主屏幕。现在,让我们快速创建并导出这个组件,以便我们可以将其导入到我们的主App.js
文件中。
我们已经做过很多次了,但这一次我们将使用SafeAreaView
而不是Block
或View
组件作为屏幕的父组件。我们使用这个是因为我们希望确保一切都被照顾到,以防我们应用的用户有一个带有刘海的手机:
图 10.2 - 准备好导入的入职组件
现在我们已经完成了编写这个函数,让我们继续将其导入到App.js
中。一旦导入,我们就可以在我们的主函数中使用它。现在,主函数应该看起来像这样:
图 10.3 - 导入 Onboarding 的主 App 函数
现在,我们准备继续开发我们的应用。因为我们已经在App
函数中导入了Onboarding
组件,所以每次保存文件时,我们都可以看到我们要修改和添加我们的Onboarding
组件。
让我们回到Onboarding.js
,考虑一下我们应该如何开始处理我们的入职屏幕。
通过查看图 10.1,我们知道这个屏幕有三个主要可识别的部分。还记得我们讨论过需要始终从更大的容器的角度来看待屏幕,以了解如何在开始编码之前创建布局吗?好吧,这就是同样的道理,所以让我们将我们的屏幕分成这三个主要部分:
-
OnboardingItem:这个区域是屏幕上部,四个点之前的部分。它将包括一张图片,一个标题和一个描述。这个区域需要被设置为一个大区域,因为它将随着每次滑动而改变。
-
分页:四个小点显示了我们在这个大滑块中的位置。这样,我们总是知道还有多少内容要阅读,直到我们到达最后一张幻灯片。
-
下一步按钮:这个按钮将移动幻灯片,而无需滑动,同时也是我们需要按下的最后一件事,从入职屏幕转到主屏幕。
知道有三个部分应该会让事情更容易绘制。首先,我们应该从主区域开始,这也是最大的区域。让我们进入我们的components
文件夹,创建一个名为OnboardingItem.js
的新文件。
OnboardingItem
正如我们之前提到的,这个组件应该渲染屏幕顶部,其中包含一张图片、一些文本和描述。让我们就这样做。继续从'galio-framework'
中导入Block
和Text
组件,以及从'react-native'
中导入Image
和StyleSheet
。
一旦我们导入了所有需要的东西,就是开始构建我们的组件的时候了。我们将首先有一个主要的<Block />
组件,它将被放在我们的<Image />
中,并且另一个<Block />
组件。第二个<Block />
组件将有两个<Text />
组件作为子元素 - 一个用于标题,一个用于描述。这个第二个<Block />
组件将被用来将屏幕分成主要区域,它将占用更多的空间,因为它将是一张图片,和次要区域,它应该更小,因为它只包含文本。
图 10.4 - OnboardingItem 组件
正如你所看到的,这里有一个小惊喜。首先,我导入了useWindowDimensions
。这是一个返回屏幕宽度或高度的hook
函数。这是一个简单的方法,可以确保你的组件的宽度等于屏幕的宽度。
我还导入了一个名为item
的属性。因为这是一个入职屏幕,我们将至少有四个屏幕,每个屏幕都有不同类型的文本或图像。我们将通过这个名为item
的属性传递一个对象,以及所有必要的信息。这样,我们可以确保一切都会到达我们想要到达的确切位置,而且我们不必再浪费时间为组件的每个部分编写属性。
所有的样式已经应用了,但我们还没有讨论过它们。所以,让我们看一下这些样式:
图 10.5 - 我们的 OnboardingItem 组件的样式
正如我们所看到的,容器有一个flex: 1
的属性,告诉我们的主要<Block />
组件尽可能占据空间。我们给我们的image
一个flex: 0.7
的属性,因为我们希望它占据 70%的空间,而title
和description
只需要占据 30%的空间。其他样式只是通常的文本样式,我们设置了fontSize
和fontWeight
。
每日提示
我无法再次强调看图片来理解代码的重要性。我相信你应该先看图片,尝试在脑海中建立所有的连接,然后看看你是否幸运。不过我不会称之为运气,我会称之为更多的是一个经过深思熟虑的猜测。
现在我们已经创建了OnboardingItem
组件,我们准备将它导入到我们的Onboarding.js
文件中,这是我们的主屏幕所在的地方。我们都知道如何导入一个组件,但为了确保,我们必须写下以下行:
import OnboardingItem from './OnboardingItem';
现在我们已经做到了这一点,我们可以开始使用FlatList
在屏幕上呈现所有的项目。
FlatList
正如我们之前提到的,我们不想重复自己,所以我们不会在我们的主要 onboarding 组件内写四次相同的代码。你脑海中可能首先想到的是使用.map()
函数。这是一个好猜测,但 React Native 有一个组件通常被使用,因为它的性能更好。它还有一些内置的 props,在这种情况下可以拯救生命。这个组件叫做FlatList
。
要使用这个组件,我们需要用一个元素数组来证明它,这些元素必须映射到我们一直在创建的组件上。我之前提到过,我们的OnboardingItem
组件将接受一个名为item
的 prop。这个 prop 将是数组中的一个对象。
如果我们看一下图 10.4,我们可以从我们在组件内部使用它的方式来确定我们的对象应该是什么样子的。我们知道它需要有一个title
,description
和image
,但它还需要一个id
。
让我们在我们的根(主)文件夹中创建一个名为slides.js
的新文件。这是包含我们 onboarding 需要的所有对象信息的数组所在的地方,如下面的截图所示:
图 10.6 - 包含 FlatList 组件所需信息的数组
不要忘记你可以拥有任何类型的信息。标题、描述或图片不必与我的相同。记住,当我们开始创建我们的应用程序时,我告诉过你要下载一些图片并将它们放在./assets/onboarding
文件夹中。这些就是我选择的图片,我使用require
关键字导入它们。
require
关键字的使用方式与import
一样,它告诉 JavaScript 引擎需要将文件定位到指定的位置。
现在我们已经准备好了用于FlatList
的数据数组,是时候回到我们的Onboarding.js
文件,并像这样导入这个新文件了:
import slides from '../slides';
现在,让我们确保当我们需要时,我们已经准备好其余的导入,因为我们需要一些更多的组件。首先,我们将删除Text
导入,并从'galio-framework'
导入Block
和Button
组件。其次,我们将将FlatList
添加到导入组件的列表中:
图 10.7-添加到 Onboarding.js 文件的新导入
现在一切都已经导入,我们准备开始开发屏幕。我们将从<SafeAreaView />
组件中删除我们的<Text />
组件,并改用带有flex={3}
属性的<Block />
组件。
在这个<Block />
组件内部,我们的<FlatList />
组件将开始自己的生活。让我们先看看我是如何实现这个组件的,然后再解释它是如何工作的:
图 10.8-在我们的引导屏幕中实现的 FlatList
正如你所看到的,实现这个似乎非常简单。顺便说一句,如果你现在将这段代码复制到你的编辑器中(假设你之前也一直在跟着做其他事情),并尝试在模拟器上查看你的应用程序,你会看到一个完全工作的引导屏幕。是的,它看起来不像我们在本章开头展示的那样好,但它是工作的-我们可以左右滑动并查看我们在slides.js
文件中编写的所有信息。
现在,让我们看看这个组件是如何工作的:
-
首先,我们将从
data
属性开始。这是我们向FlatList
组件提供数组以开始渲染每个元素的地方。 -
然后,我们有
renderItem
属性,这是我们使用函数来渲染我们需要的项目的地方。在这种情况下,我们需要多个<OnboardingItem />
的实例。
记得我说过我们将向这个组件传递一个叫做 item
的 prop 吗?这是因为我们只需要从数组中传递一个对象。我们的 FlatList
组件将把每个对象传递给不同的 <OnboardingItem />
组件。一旦我们做到了这一点,我们就可以捕获那个对象,并以任何我们认为合适的方式使用它。
keyExtractor
prop 用于提取特定项目在相应索引处的唯一键。
这些键用于缓存,以便 React 可以单独跟踪每个项目,并且只重新渲染必须重新渲染的项目。你还记得我们在使用 .map()
函数渲染项目时是如何使用 key
属性的吗?这是一样的,但所有的工作都是由这个属性完成的。这就是为什么我们需要在 slides
数组的对象中有一个 id
键。
- 其余的 props 主要用于布局目的。我强烈鼓励你们通过打开和关闭这些 props 来玩耍。例如,
horizontal
prop 使我们的列表,嗯,水平。
现在我们已经成功构建了我们的元素列表,这是创建一个出色的入门体验的第一步,让我们开始构建分页器。
这四个小点在屏幕上显示分页器。它的主要目的是向用户显示他们当前正在查看的幻灯片,同时显示进度的感觉。这个小组件实现起来并不那么困难,但我们要使用的功能确保这个东西能够正常工作对我们来说是新的。
分页器
我们将为这个组件工作的最重要的对象之一是 Animated
对象。这是因为我们要动画化我们的点的宽度和不透明度。这也很重要,因为我们希望确保动画发生在正确的时刻。当然,正确的时刻是当用户与 FlatList
交互时。如果你的手指从右向左移动,我们希望动画也以与你的手指相同的速度移动。
我们还将使用一个很酷的新 Hook,叫做 useRef
。当我们需要一个在组件整个生命周期内持续存在的可变对象时,就会使用这个 Hook。useRef
不会导致组件在其值更改时重新渲染,因为它不是一个状态变量,但这确实是一种确保您在每次渲染时都会得到相同 ref
对象的好方法。
所以,让我们开始这个很酷的小组件,我相信你会发现它对未来的应用程序很有帮助和可重用性。我们将从Onboarding.js
开始。让我们首先从'react'
中导入useState
和useRef
。我们还将从'react-native'
中导入Animated
。导入所有这些之后,我们就准备好继续了:
图 10.9 – 我们 Onboarding 组件的全新导入
现在,让我们开始实现我们Paginator
组件所需的一切。我们将首先创建两个新的引用对象,然后在我们的FlatList
组件中实现它们:
图 10.10 – 我们新创建的引用
让我们解释一下这是如何工作的。首先,我们将从scrollX
开始。这个变量有很多事情要做,所以让我们从头开始。我们使用useRef
Hook 创建一个新的引用对象,并用Animated.Value(0)
初始化这个新变量。
Animated.Value
创建一个可以被动画化的值。如果我们只用一个数字比如0
来初始化这个变量,React Native 在处理动画时就不知道该怎么处理了。
useRef
Hook 返回一个像这样的对象:
{ current: … }
要访问current
中存储的值,我们必须写scrollX.current
。一个解决方法是让 JavaScript 知道我们想要通过在useRef
Hook 后面链接.current
来访问那个值。
viewConfig
变量的工作方式就像你期望的那样。在这里,我们必须创建一个新的引用对象,并用图 10.10中显示的对象{ viewAreaCoveragePercentThreshold: 50 }
来初始化它。现在,让我们在我们的FlatList
组件中使用这两个新变量:
图 10.11 – 在 FlatList 中实现我们的新变量
现在可能看起来有点复杂,但实际上比看起来要简单得多。有了这个,我们为我们的<FlatList />
组件添加了两个新的 props,分别是onScroll
和viewabilityConfig
。
viewabilityConfig
属性在这里支持我们的pagingEnabled
属性,它告诉我们的组件列表根据用户滑动的距离移动到下一个或上一个幻灯片。通过将viewabilityConfig
的viewAreaCoveragePercentThreshold
值设置为 50,我们告诉我们的组件只有在用户已经滑动了当前幻灯片的 50%或更多时才会转到下一个幻灯片。
onScroll
属性在用户滚动我们的引导屏幕的幻灯片时触发一个函数。你可能想知道Animated.event
是什么?它将一个动画值映射到一个事件值。我同意,这个函数看起来很混乱,但如果我们学会如何阅读它,就很容易理解。所以,我们将我们的scrollX
值映射到nativeEvent.contentOffset.x
事件值。这个事件值通常传递给onScroll
回调函数,所以记住你可能会经常看到或使用它。
Animated.event
函数接受两个参数。第一个参数是我们要映射到我们的Animated值的值数组。这个event()
函数通过在映射的输出上调用setValue()
函数来实现这一点;在这种情况下,是在scrollX
上。第二个参数是一个configuration
对象,我们在这里告诉 React Native 我们不想使用本地驱动程序来进行动画。
你可能会认为通过使用本地驱动程序,我们可能会有更好的性能,这是正确的。我们之所以不想在这种特定的用例中使用本地驱动程序的原因是因为我们将要动画化我们的点的宽度,而现在,React Native 不能使用本地驱动程序来动画化宽度或一般的布局属性。
现在我们知道为什么我们需要scrollX
和viewConfig
,我们应该开始构建我们的新组件。在components/
文件夹内创建一个名为Paginator.js
的新文件。现在我们已经创建了一个新文件,我们应该开始构建我们的功能组件。
我们将从'react-native'
中导入所有必要的内容;即StyleSheet
、View
、Animated
和useWindowDimensions
。下一步是构建我们的函数:
图 10.12 – Paginator 组件几乎完成
这里有很多新东西,所以让我们从上到下开始解释一切。
这个组件,我们称之为Paginator
,接受两个名为data
和scrollX
的 props。data
是我们传递给FlatList
的对象数组,而scrollX
是我们在Onboarding.js
文件(我们的父组件)中定义的Animated
值。
我们已经讨论过useWindowDimensions()
Hook 返回屏幕的width
和height
属性,所以这应该很容易理解。
我们给<View />
组件的样式设置了flexDirection: 'row'
和height
为64px
,这个组件包含了我们组件的灵魂。我们这样做是为了确保我们将要创建的点会很好地排列在一行中。
之后,我们使用.map()
函数来映射数组。正如你所看到的,map()
函数接受一个回调函数,该函数接受两个参数。第一个参数_
将是我们的元素,而第二个参数i
将给出该元素的索引。
因此,对于数组中的每个元素,我们都在创建一个点。我们如何做到这一点呢?让我们直接跳到我们的return
语句来找出答案。在这里,我们返回一个应用了styles.dot
的<View />
组件。我们之所以称它为<Animated.View />
是因为我们想要动画化这个组件。但在我们开始动画化它之前,这可能只是一个普通的<View />
组件:
图 10.13 – 我们的点的样式
这些是我们用来创建点的样式。正如你所看到的,没有width
,这是因为我们想要动画化点的宽度。然而,如果我们永远不想要动画化它,我们本来可以直接给它一个width
为10px
。
那么,让我们回到如何动画化我们点的宽度。正如你所看到的,我们有一个名为inputRange
的变量,它是基于屏幕宽度和我们点的索引的值数组。我们知道,幻灯片占据了屏幕的整个宽度。知道这一点,我们可以理解当contentOffset.x
等于屏幕宽度时,幻灯片已经改变了。它被称为contentOffset
,因为它给出了两个元素之间的偏移量。当第一张幻灯片在屏幕上时,它从0
开始。一旦该幻灯片移出屏幕,下一张幻灯片进入时,最后一张幻灯片和下一张幻灯片之间的差值等于屏幕的宽度。了解contentOffset
的工作原理使我们能够考虑一种开始创建动画的方法。
到底是什么构成了动画? 我觉得这是一个很好的地方,我们可以定义动画是如何工作的。让我们想象屏幕上有一个盒子,每当有人按下按钮,我们希望那个盒子出现。当然,它可以突然出现在屏幕上,但那看起来不太好。这就是动画发挥作用的地方。与其突然出现在屏幕上,不如我们有一个更平滑的过渡。如果盒子在一段时间内过渡到存在状态呢?那看起来更像是一个动画,对吧?
这与我们在这里应用的概念相同:我们希望我们点的移动完全与幻灯片的移动同步。因此,我们需要我们点的宽度在我们在屏幕上移动手指的同时增长,因为这会为我们的用户创造更流畅的体验。
牢记这一点,我们已经将scrollX
的动画值映射到我们的nativeEvent.contentOffset.x
事件值。现在,我们可以通过scrollx
访问水平列表中两个元素之间的确切变化量。根据这个量,我们需要改变宽度。
但是有一个问题:我们点的高度
是10px
,所以如果我们希望我们的点是一个点,那么我们也需要宽度
是10px
。问题是我们的scrollX
会远远超过10px
,因为我们屏幕的宽度更大,那么我们如何让 React Native 知道我们希望我们当前的点具有更大的宽度,而其余的点的宽度为10px
呢?通过插值。
插值
所以,让我们简要回顾一下。我们希望与我们正在查看的幻灯片对应的点具有比我们视野之外的幻灯片对应的点更大的宽度(假设为20px
)。我们唯一能做到这一点的方法就是插值。
插值是我们根据提供的输入来估计函数输出的方式。
假设我们有一个函数,我们只知道f(0) = 0
和f(10) = 20
。你能猜到f(5)
将等于多少吗?
根据这个表格,我们可以建议10
作为我们问题的答案,因为5
介于0
和10
之间,而我们知道答案应该介于0
和20
之间。这种直观的方法就是插值所做的事情。
所以,现在我们知道我们的值需要如何行为,我们可以看一下点宽度的插值函数:
图 10.14 - 插值函数
所以,我们希望这个函数根据用户当前位置返回一个介于10
和20
之间的值。我们的inputRange
变量,正如我们之前提到的,是由特定幻灯片的索引和屏幕宽度定义的。inputRange
变量中的第一个值由前一个幻灯片表示,第二个值由当前幻灯片表示,第三个值由下一个幻灯片表示。基于这个输入,我们创建了一个outputRange
,在这个范围内,我们知道前一个幻灯片的点应该有10px
的宽度,当前幻灯片的点的宽度应该是20px
,下一个幻灯片的点的宽度应该是10px
。
根据inputRange
猜测应返回哪个值是 React Native 的工作,但我们真正感兴趣的是值本身。现在,我们可以去我们的<Animated.View />
组件,让每个点的宽度等于dotWidth
,这是插值给我们的值。现在,宽度将随着用户滑动手指而改变。
外推
我们还有另一个很酷的小东西叫做extrapolate
。所以,我们知道我们的inputRange
只考虑了前一个、当前和下一个幻灯片,但第四个呢?因为我们没有为第四个指定任何值,React Native 可以开始猜测宽度应该是多少。
如果我们在没有外推的情况下运行代码,可能会看到一些奇怪的结果。我鼓励你删除extrapolate
行,看看发生了什么。
我们可以通过将extrapolate
添加到我们的interpolate
函数中来解决这些奇怪的结果。这将告诉 React Native在我们提供的范围之外应该发生什么,以及外部值应该遵循什么样的模式。当我们不知道范围的边界时,这非常有用。在这种情况下,解决方案将是夹紧你的范围。这意味着无论范围之前或之后发生什么,我们都将保留最后给定的值。
通过使用extrapolate: 'clamp'
,你将夹紧范围的两侧,但如果特定情况需要,你也可以只夹紧你需要的范围的一侧。这意味着你可以夹紧范围的左侧或右侧。
提示
外推的默认模式是extend
,这是 React Native 猜测我们范围的值。
太棒了!现在我们已经解释了如何插值和外推,我们已经理解了dotWidth
变量是如何改变的(以及基于什么)。因为这一切都是用scrollX
动画值完成的,我们已经将dotWidth
变量放在了<Animated.View />
中。现在,我们的宽度根据我们的滚动行为而改变。
还剩下什么?嗯,我觉得看到不透明度也在变化会很酷。当前的点应该具有等于1
的不透明度,而其他点应该具有0.4
的不透明度。根据这些信息,试着自己做一下。
如果你做不到,不要担心!这比看起来要容易得多。让我来演示给你看!
图 10.15 – 动画化我们的点的不透明度
看起来并不难,对吧?我们做了和dotWidth
一样的事情,但这次我们创建了一个名为opacity
的新变量。我们知道元素的不透明度在0
和1
之间,所以我们改变了outputRange
以适应我们的需求。
之后,我们在<Animated.View />
组件的style
属性中引入了我们的不透明度值。
现在我们已经完成了Paginator
组件,我们应该在Onboarding.js
文件中实现它。我们已经知道如何做了:导入组件,然后将其放在具有flex
为3
的<Block />
组件下面。不要忘记传递必要的 props。
通过构建这个Paginator
组件,我们学到了很多关于动画应该如何工作的东西,对此我必须向你表示祝贺!在本章中,我们取得了一些令人印象深刻的进展。现在,是时候开始为我们的屏幕添加一些功能了。让我们学习如何做到这一点。
自动滚动
为了完成这个项目的构建,我们需要创建一个按钮,当我们按下它时移动幻灯片。我们已经从'galio-framework'导入了<Button />
组件,所以让我们在<Paginator />
组件下面实现它:
图 10.16 – 将按钮组件添加到我们的引导屏幕上
正如你所看到的,我在<Paginator />
下面实现了Button
。我添加了与我们的图像和点相同的颜色,并通过shadowless
属性去除了阴影。现在我们知道我们的函数需要在按下按钮时被调用,我们需要创建一个函数,然后将其链接到我们的onPress
属性。
但在这之前,我们需要确保我们已经准备好让我们的按钮在需要时起作用。
首先,我们需要考虑如何在不通过滑动列表的情况下到达下一张幻灯片。嗯,我们需要一个对FlatList
组件的引用。拥有对该对象的引用允许我们在需要时从外部函数控制它。
其次,我们需要跟踪我们的幻灯片,因为我们需要始终知道我们在哪张幻灯片上。我们可以通过一个状态变量来实现,该变量跟踪当前在屏幕上显示的索引。
现在,让我们先解决这些问题,然后再看看我们需要做什么来确保这个工作。
让我们使用我们已经导入的useState
Hook 来创建一个状态变量:
const [currentIndex, setCurrentIndex] = useState(0);
这里是我们将存储当前显示幻灯片的索引。
现在,让我们创建一个 ref 变量:
const slidesRef = useRef(null);
一旦我们完成创建我们的 ref 变量,我们应该将它应用到我们的<FlatList />
组件上。我们可以使用ref={slidesRef}
来做到这一点。
接下来,我们将使用FlatList
已经提供给我们的一个属性,叫做onViewableItemsChange
。每当你滚动FlatList
时,FlatList
上的项目也会发生变化。当这些项目发生变化时,将调用这个函数,告诉你当前有哪些viewableItems
。这个属性应该始终与viewabilityConfig
一起使用。当满足viewabilityConfig
的相应条件时,onViewableItemsChange
函数将被调用。
这将帮助我们确保我们始终有正确的索引来显示幻灯片。因此,在函数内部,我们必须确保将当前索引设置为正在显示的索引:
const viewableItemsChanged = useRef(({ viewableItems }) => {
setCurrentIndex(viewableItems[0].index);
}).current;
看起来有点奇怪,但正如我们之前讨论的那样,该函数将返回当前的viewableItems
。
问题是...一次只能显示一个项目,所以viewableItems
数组将只有一个元素。因为我们感兴趣的是该元素的索引,所以我们设置currentIndex
状态变量,使其等于viewableItems[0].index
。
现在我们知道当前显示的幻灯片是哪一个,下一步就是滚动到currentIndex + 1
。例如,如果我们正在查看第一张幻灯片,那么我们的currentIndex
应该等于0
。自然而然,下一张幻灯片将是currentIndex + 1
,也就是1
:
图 10.17 - 最终的 Onboarding 组件
现在我们已经完成了viewableItemsChanged
,并且使用了我们的onViewableItemsChange
属性的变量,让我们解释一下scrollTo
函数是如何工作的。
正如你所看到的,我们创建了一个名为scrollTo
的函数,每当我们按下按钮时就会调用它。这个函数检查currentIndex
,因为我们希望基于是否显示最后一张幻灯片来采取不同类型的行为。如果这是最后一张幻灯片,我们暂时不做任何事情,但如果是前三张幻灯片,我们希望它滚动到下一张幻灯片。
正如你所看到的,滚动到下一张幻灯片非常容易 - 我们所要做的就是使用我们对<FlatList />
组件的引用,并使用scrollToIndex
函数。该函数需要一个参数,告诉它要跳转到哪个索引。
现在,我们可以点击保存,重新加载我们的应用程序,然后我们就拥有了一个漂亮的入职屏幕,带有一些很酷的小动画,以及一个很好的功能,可以在我们除了按钮之外什么都不触摸的情况下滚动幻灯片。这是一个漫长的旅程,但我相信你会觉得这是值得的,现在我们已经看到了我们的能力。
在下一章中,我们将构建我们应用程序的其余部分,但为了获得良好的体验,我们将在我们的应用程序中使用这个入职屏幕。这将确保在幻灯片的末尾,我们的按钮将直接跳转到应用程序。
摘要
这一章是我们迄今为止克服的最艰难的挑战之一。我们经历了很多新概念,但最终,我们可以高兴地说,我们成功地为我们的用户创建了一个很棒的入职体验。更好的是,我们创建了一个很好的入职体验,每当我们吹嘘我们的应用程序时,我们都会享受到它。
我们首先发现了这个应用程序的外观,然后经历了所有必要的步骤来制作该应用程序。我们看到了创建一个漂亮的元素列表需要什么,这让我们接触到了FlatList
。我们在我们的入门屏幕的核心使用了这个组件,将来当你遇到大量元素的列表时,你肯定会继续使用它。
我们还学会了如何创建动画,以及插值是如何工作的。通过这样做,我们成功地创建了一个很酷的小分页器,显示我们的用户正在看到的当前幻灯片。
最后,我们甚至发现我们可以通过按按钮而不是左右滑动来使事情运转。为此,我们使用了一个引用对象,每当我们按下按钮时,它就会从另一个函数中调用。
这一章可能有点多,但我觉得你已经准备好了。我希望你也为下一章做好了准备,因为我们将完成这个移动应用程序!
第十一章:让我们构建 - 秒表应用
在上一章中,我们通过创建引导屏幕构建了我们酷炫的秒表应用的开头。现在,是时候通过构建用户将要使用的其他功能来完成我们的应用程序了。
我们将学到很多新东西,到本章结束时,我们将拥有一个相当酷的应用程序,我希望它能激励你为世界其他地方创建更多有用的应用程序。到目前为止,我们学到的东西以及将继续学习的东西应该为你提供创建简单小型应用程序所需的所有工具,而无需另一个教程。即便如此,有时你会发现自己在互联网上四处寻找解决问题的方法,这没关系。我们都会这样做,所以每当你找到解决方案并使其起作用时,都要感到高兴。
为了构建这个 React Native 移动应用程序,我们将首先通过使用 React Navigation 库将我们的引导屏幕链接到我们的实际应用程序。这将帮助我们轻松地构建屏幕的导航。
之后,我们将开始着手应用的秒表部分。创建秒表功能非常直接,但并不像你想象的那样直观。
一旦我们创建了秒表屏幕,我们将开始着手应用的另一部分,即计时器屏幕。这将教会我们如何播放声音,以及如何利用我们已经通过创建秒表应用学到的知识,但加入了一些小的变化。
最后,我们将学习关于本地存储以及如何使用它来确保我们的引导屏幕不会在每次打开应用程序时出现,因为这有点违背了有引导屏幕的初衷。所以,让我们准备好,享受编码的乐趣吧!
本章将涵盖以下主题:
-
链接到 React Navigation
-
创建秒表
-
创建计时器
-
完成我们的应用程序
技术要求
您可以通过访问 GitHub github.com/PacktPublishing/Lightning-Fast-Mobile-App-Development-with-Galio
查看本章的代码。您会发现一个名为Chapter 11
的文件夹,其中包含本章中编写的所有代码。要使用该项目,请按照README.md
文件中的说明进行操作。
链接到 React Navigation
我们将通过使用与第十章中先前使用的相同项目来开始这个挑战,构建一个入职屏幕。你为什么问?嗯,那是因为创建入职屏幕的目的正是这样 - 为我们的主要应用程序提供一些介绍。
所以,打开文件夹,准备好编码。我们将从导入我们需要连接我们的入职屏幕到任何我们将来创建的新屏幕所需的所有必要软件包开始。让我们打开我们的终端并移动到我们的项目文件夹。在那里,我们将开始编写以下命令:
npm install @react-navigation/native
这将安装我们导航系统的基础。我们还需要安装这个软件包需要的所有依赖项,我们可以通过以下命令来做到这一点:
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
现在我们所有的依赖都已经安装好了,让我们谈谈React Navigation库。
对于试图使用 React Native 创建导航系统的人来说,有几个选项,但最常用的是 React Navigation。你可能想知道为什么会这样,我的答案是,这是它们中最受关注和功能齐全的库。我强烈建议深入研究他们的文档,你可以在reactnavigation.org/
找到。
除了作为 React Native 的一个很好的导航库之外,它还有一种非常简单和直接的设置路由的方式,我们将在本章后面讨论。所以,除了易于使用之外,它还是完全可定制的,并且具有对 iOS 和 Android 的本地支持。你还能从一个导航库中要求什么呢?
让我们继续我们的应用程序,并考虑一切应该是什么样子。我想,一旦用户完成我们的入职屏幕,并且他们最后一次点击下一步按钮,我们的用户将被传送到另一个屏幕,直接到秒表屏幕。这个屏幕将有两个标签页:一个用于秒表,这是我们应用程序的主要用例,另一个用于计时器屏幕。
为了使其工作,我们需要从@react-navigation
中获得两个新组件:stack
和bottom-tabs
。让我们用以下命令导入它们:
npm install @react-navigation/stack
现在,是时候安装我们将要使用的下一个软件包了:
npm install @react-navigation/bottom-tabs
现在一切都已经安装好了,是时候重新构建我们的项目,以便更好地控制我们的文件放在哪里了。
我们将在项目的根目录内创建一个名为screens
的新文件夹。接下来,我们必须从components
文件夹中复制并粘贴我们的Onboarding.js
文件。
一旦您将该文件移动到正确的目录中,就是时候检查我们的文件,确保它们都链接到我们引导屏幕的新路径。我们还需要查看Onboarding.js
内部是否有任何需要修改的导入。
我们在Onboarding.js
内部需要更改的导入是我们在此屏幕内使用的组件:OnboardingItem
和Paginator
。因为这些组件不再在同一个文件夹中,我们必须确保它们以正确的路径导入。在我们的情况下,路径更改为"../components/OnboardingItem"
:
图 11.1-我们引导屏幕的新导入
因为我们已经在这里了,所以去scrollTo()
函数。在else
语句内部的console.log()
行的位置,写入以下行:
navigation.navigate('Tab Navigator');
这告诉Button
一旦它到达引导屏幕的末尾,下一步是导航到下一个屏幕,名为'Tab Navigator'
。当我们创建我们的路由系统时,我们将介绍这个屏幕。因为我们使用了一个名为navigation
的变量,我们还应该让我们的组件知道从哪里获取它。在我们定义Onboarding
函数的地方上面直接,并在括号之间,我们将允许我们的函数接收这个属性,称为navigation
,就像这样:
export default function Onboarding({ navigation }) {
现在,如果我们想要一个正常运行的应用程序,我们需要去App.js
并且也更改引导屏幕的导入为正确的路径。一旦我们用正确的导入完成了更改,我们可以保存并运行应用程序。什么都不应该改变;我们所做的只是添加了一个新的目录,这样我们就有了更好的文件夹结构。一些文本编辑器或 IDE 会自动为您更改导入,所以请确保您始终阅读可能弹出的任何消息。
每日提示
我经常刷新我的应用程序并检查更改或错误消息,特别是当应用程序内的所有更改不应该在视觉上改变任何东西时。这样,我就可以确保我始终了解应用程序在重新渲染时发生的任何事情。
现在我们有了一个新的文件夹结构,我们可以开始创建应用程序所需的路由。但首先,我们需要为即将使用的屏幕创建一些占位符。因此,让我们在我们的screens
文件夹中创建两个新文件:Stopwatch.js
和Timer.js
。
对于这两个文件,除了我们的函数名称之外,我们将拥有相同的代码,这些函数将被写在<Text />
组件内。在我们开始深入了解应用程序的功能之前,我们需要这些文件来测试我们的路由是否正常工作。
让我们看看那个占位屏幕是什么样子的:
图 11.2 - 用于测试路由的占位屏幕
这个示例是专门为Stopwatch.js
文件创建的。您还需要为Timer.js
创建第二个文件。正如我已经指定的,这一个和 Timer 的区别将在函数的名称和<Text />
组件内的内容上。其余部分应该是相同的,因为我们只是使用这些文件来测试我们的路由。
现在我们在screens
文件夹中有了这两个新文件,我们可以继续在我们的根目录中创建一个名为routes.js
的新文件。这是我们将为我们很酷的小应用程序创建路由系统的地方。
创建了新文件之后,我们可以打开它并开始编码。我们将首先导入所有我们将在这个路由系统中需要的必要包和文件。您可以通过查看以下截图来看到我正在导入哪些包:
图 11.3 - 用于 routes.js 的导入
现在,正如你所看到的,我们已经从@react-navigation
中导入了所有主要的包。我们首先导入了 React,因为我们需要它来创建这个基于组件的路由系统。接下来是NavigationContainer
组件,它是从@react-navigation/native
中导入的。这个组件处理管理应用的导航状态,并在顶层导航器和应用环境之间创建连接。
在此之后,我们导入了createStackNavigator
和createBottomTabNavigator
。要理解堆栈导航器的工作原理,我们必须开始将我们的屏幕视为一叠卡片中的卡片。您总是将新卡片放在旧卡片的顶部,以便您可以创建一叠卡片。这基本上就是 React Navigation 的工作方式,总是将一个新屏幕放在另一个屏幕的顶部。
底部选项卡导航器创建了通常在应用程序希望您更轻松访问主要功能时遇到的底部栏。这样,我们可以让用户快速在计时器和秒表之间切换,每个屏幕都可以从底部栏轻松访问。
一旦我们导入了创建应用程序路由系统所需的依赖项,就是时候导入我们将在此系统中使用的屏幕了。当然,入门屏幕非常重要,因为这必须是用户在看到的第一个屏幕,之后我们需要秒表和计时器屏幕。
现在我们导入完成了,是时候看看我们如何使用 React Navigation 来创建我们的路由系统了。我们将使用createStackNavigator
和createBottomTabNavigator
来创建我们将用作定义屏幕和导航器的组件的变量,所以现在让我们来做吧:
图 11.4 - 从我们的导航函数创建变量
拥有这些变量使我们能够创建易于阅读的路由系统。
让我们首先编写我们主要屏幕的函数;也就是秒表和计时器。这应该是一个普通的 React 函数,返回一个底部选项卡导航器的系统。因此,我们会使用Tab
变量。让我们看看我们的函数是什么样子的:
图 11.5 - 秒表和计时器屏幕的主屏幕路由
这看起来很容易理解,对吧?我们有一个<Tab.Navigator />
组件,它有两个使用<Tab.Screen />
组件作为子级的屏幕。Navigator
组件就像胶水,让 React Native 知道这两个屏幕需要成为底部选项卡导航器的一部分。
对于像这样的每个路由系统,我们需要一个Navigator
组件,然后一些Screen
组件,让Navigator
知道哪些屏幕是它的一部分。
我觉得这很简单易懂,任何人都可以开始为他们的应用程序创建路由系统。我鼓励你尽可能多地在应用程序中使用路由,看看你可以改变多少选项和东西。React Navigation 是非常可定制的,所以我相信你会对使用这个库的可能性感到惊讶。
现在,下一步是设置我们的主屏幕堆栈。我们将以与设置AppTabs()
函数组件相同的方式来做,但这次,我们还将使用<NavigationContainer />
组件,因为这将是我们的主路由组件:
图 11.6-我们应用程序的主路由系统
看着我们的主函数代码可能会让你想知道这里发生了什么。不要担心-这并不难理解。因为这将是我们的主路由系统,我们使用了<NavigationContainer />
组件。在它里面,我们有一个<Stack.Navigator />
组件创建了一组可以叠加在一起的屏幕,就像一叠卡片一样。在这里,我们有两个屏幕:Onboarding
屏幕和AppTabs
屏幕。
正如我们之前看到的,我们已经将AppTabs
屏幕组件定义为底部选项卡导航器屏幕,其中包含我们的两个主要屏幕:Stopwatch
和Timer
。
我们还有一个叫做options
的prop
应用在我们的两个<Stack.Screen />
组件上。这个prop
允许我们对我们的屏幕应用自定义特性。因为 React Native 默认在堆栈中的每个屏幕上启用了一个标题栏,我们必须摆脱它,所以我们给它一个值为false
。如果我们没有指定这个,每次你进入这个屏幕,你会看到屏幕顶部的默认平台标题。
现在我们已经导出了这个函数,我们可以进入我们的App.js
文件并应用我们的路由系统。但是这个文件里充满了我们不需要的东西,所以让我们清理一下。删除App.js
中的所有内容,这样我们就可以开始以最佳方式重新编写它以适应我们的用例。
在清空文件内的所有内容后,我们可以开始导入 React。之后,导入我们在routes.js
文件中之前定义的AppStack
组件。现在,我们只需要创建一个名为App()
的函数,它返回我们的<AppStack />
组件,如下面的截图所示:
图 11.7 - 在进行所有必要的修改后的 App.js 文件
现在,我们的App.js
文件看起来干净多了,我们已经成功地将我们的路由系统连接到了我们的 React Native 应用程序。你应该测试你的应用!保存所有内容,启动 Expo 服务器,然后打开你喜欢的模拟器或物理设备。
因为我们已经通过navigation.navigate()
函数将引导屏幕链接到 Tab 导航器屏幕的scrollTo()
函数,现在我们有了一个完全功能的路由系统。
现在你应该能够首先看到引导屏幕。点击下一步按钮,直到你到达最后一个屏幕。一旦你到达那里,再点击下一步一次,哇!你现在在AppTabs()
Tab 导航器中了。这就是我们在routes.js
文件中定义的组件。你可以点击底部标签导航器按钮快速切换秒表和计时器应用程序。
我们的 React Navigation 实现很成功!现在,是时候开始编写我们的秒表屏幕的功能了。
创建一个秒表
一些已经在 JavaScript 上有一些经验的人可能会认为创建一个秒表就像调用setInterval()
函数并在每次迭代时减去一个数字那样简单。但实际上并不是这样,但不用担心 - 我们会尽可能地让每个人都能轻松理解,无论你在 JavaScript 方面的经验如何。
所以,让我们从打开我们的Stopwatch.js
文件开始,我们可以在screens
文件夹中找到它。现在,里面只有一些带有单词 Stopwatch 的文本,因为我们对主要的<View />
组件进行了样式化。
老实说,我会先从这个文件中删除所有内容,然后从头开始导入。我们将从'react'中导入React
、useState
和useEffect
。之后,我们将从'react-native'中导入StyleSheet
和SafeAreaView
。最后,我们将从'galio-framework'中导入Text
、Block
和Button
组件。
在导入我们将用来创建这个屏幕的组件之后,现在是时候为我们建立一个静态屏幕作为起点了。让我们看一下以下代码,并尝试解释一下,因为这将成为我们的主要布局骨架:
图 11.8 - 我们秒表组件的基本布局
嗯,这是一大块代码,让我们直接深入并解释一下。所以,在导入我们需要的一切之后,我们将开始编写我们的Stopwatch()
函数组件。在其中,我们可以看到有一个大的<Block/>
组件,然后是一个<SafeAreaView />
组件。这些只是为了包含所有内容,并确保如果我们遇到有刘海的手机时不会出现任何问题。
到目前为止,一切都很容易,接下来会发生什么呢?我们必须将屏幕分成两个<Block />
元素,一个具有flex
属性为0.32
,另一个具有flex
属性为0.68
。这样我们就可以确保屏幕的上半部分包含所有按钮和功能,然后屏幕的下半部分将显示所有的圈数。
在屏幕的上方,我们可以看到一个带有大字体大小的<Text />
元素。这将是我们的时间,在添加所有功能后将会改变。之后,我们有另一个带有 row 属性的<Block />
元素。里面有两个按钮。我们将使用这些按钮来启动/停止秒表,并在有人完成一圈时创建圈数。
之后,我们有另一个<Block />
元素,其目的是使我们的布局对用户更直观一些。它将指出圈数将显示在该行下方。我们已经为这些线条创建了一些样式,你可以在divideLine
下的样式对象中找到。
以下截图显示了这在我们的设备上的样子:
图 11.9 - 秒表屏幕的基本布局
很好!现在我们已经编写了基本布局,是时候开始工作屏幕功能了。我们应该首先定义一些状态变量,这些变量将在整个屏幕上使用。但在此之前,让我们回到一开始,思考一下为什么我说我们不能使用setInterval()
函数来增加时间。
使用 setInterval
所以,setInterval
是一个函数,它确切地做了你期望它做的事情。你设置一个时间间隔,比如 1,000 毫秒,也就是一秒,然后每秒都会调用一个你要定义的函数。你可能会认为在理论上,我们可以为我们的秒表屏幕功能做如下的事情:
图 11.10 – 在基本示例中使用的 setInterval
这将非常有效。在这里,每 10ms,我们都会触发一个增加 10 的变量的函数。理论上,这很好,因为我们现在有了一个基本的用五行代码构建的秒表。但问题是,setInterval()
并不那么可靠。
为什么我这么说呢?嗯,如果你看一下前面的函数,我们可以看到我们已经指定了 10ms 作为定时参数,所以我们的函数应该每 10ms 启动一次。然而,它不会在指定的时间执行代码。相反,它会等待至少10ms 才执行。因此,我们不能说我们的时间函数是准确的。
我尝试了不同的解决方法,我发现处理时间的最佳方式是使用Date
对象。
现在我们已经弄清楚了,让我们编写我们的状态变量:
图 11.11 – 在 Stopwatch 组件内部使用的状态变量
我已经解释了每一个,但基本上,我们将有五个控制状态变量,称为startTime
、laps
、started
、elapsed
和intervalId
。然后,我们有时间状态变量,我们将使用它们来查看屏幕上的时间变化。这些被称为minute
、seconds
和ms
。
现在,让我们使用时间状态变量,并在屏幕上显示它们。让我们看看应用时间状态变量后,<Text />
组件现在是什么样子:
<Text style={{ fontSize: 72, marginTop: 32 }}>{minute < 10 ? `0${minute}` : minute}:{seconds < 10 ? `0${seconds}`: seconds}.{ms}</Text>
因为在某个时间点上我们可能有个位数的数字,通过这样编写我们的变量,我们可以确保如果它们是个位数,我们将在开头添加一个0
。我们将对minutes
和seconds
变量都这样做。
保存文件并刷新屏幕应该不会显示任何变化。这是好事,因为这意味着我们已经正确地在Stopwatch
组件内部实现了时间状态变量。
现在这些变量已经就位,让我们构建一个函数,一旦我们在屏幕上按下开始按钮,它就会被调用。这个按钮需要做几件事情;首先,它需要作为开始和停止按钮的功能。其次,它需要用一个新的Date
对象初始化我们刚刚定义的startTime
控制状态变量。现在让我们来看看这个函数:
图 11.12 - startAndStop()函数
因此,这个函数可以做两件事情。首先,如果我们的started
状态变量是false
,我们将把它设置为true
,以宣布秒表的开始,然后将startTime
变量设置为new Date()
对象。通过在开始时设置日期,我们可以在以后使用它来计算每次迭代之间经过了多少时间,从而使我们能够显示更准确的时间。
现在,一旦started
变量被改变,我们需要启动setInterval()
函数。让我们来看看下面的函数,并讨论它的工作原理:
图 11.13 - 用于启动 setInterval()函数的 useEffect()实现
我们实现了这个useEffect()
函数,因为 React 给了我们这个很酷的小函数,它在组件重新渲染时被调用。最酷的是,我们可以让它在第二个参数中的状态变量改变时才工作。每当started
变量改变时,这个函数就会被调用。
一旦started
变量改变,被调用的函数将成为我们秒表功能的核心。这个功能将在setInterval()
函数内部。在该函数内部,我们将我们的经过的时间变量设置为new Date()
每 10ms。之后,我们获取我们的setInterval()
函数并将其应用到intervalId
状态变量中。
useEffect()
函数内的return
函数在副作用之后进行清理。这意味着每次started
变量改变时都会调用此函数,只是为了清理之前的渲染。它还在组件卸载时调用。因为我们正在使用setInterval()
,我想要确保每当我们的started
变量设置为false
(秒表停止)时,我们的间隔都会被清除,以免过重地占用用户的 CPU。
如您所见,清除间隔就像调用clearInterval()
函数并传递我们要清除的间隔一样简单。
现在我们在 elapsed 变量中有了最新的时间,我们所要做的就是查看elapsed
时间和startTime
之间的差异。我们可以使用useEffect()
来做到这一点。每当elapsed
状态变量被更改时,另一个useEffect()
函数将被触发。然后,我们可以在那里进行所有的数学运算。让我们看看我是如何做到这一点的:
图 11.14 - 第二个 useEffect()函数,它依赖于 elapsed
因此,这个useEffect()
函数在elapsed
改变时被调用,这是每 10ms 一次。我们在这里做的是检查elapsed
是否存在(不是未定义),并且started
为true
。如果这两个条件都为真,我们可以使用elapsed
,其中包含最新的Date
值,来计算差值并将其转换为毫秒。继续进行,我们对分钟和秒进行数学运算。一旦我们有了所有这些值,我们可以将它们设置为我们之前定义的时间状态变量:minute
,seconds
和ms
。
等等...我们完成了吗?有点,但不完全。让我们去我们的“开始”按钮,稍微改变一下,以便可以使用它。我们将像这样改变它:
<Button size="small" color={started ? "#6c757d" : "#c9a0dc" } onPress={() => startAndStop()} shadowless>{started ? "Stop" : "Start"}</Button>
这样,我们可以拥有不同的颜色,我们的按钮将根据其当前状态显示不同的文本。这一切都基于我们的started
状态变量,它告诉我们秒表是否已启动。我选择了这些颜色,因为我们在入职屏幕上也使用了它们,我认为它们很合适,但您可以使用任何您想要的颜色。
现在,我们可以保存和重新加载我们的 JavaScript,并查看我们一直在创建的东西。通过按下“开始”按钮,您将看到它将其文本更改为“停止”按钮的文本,并且其颜色现在是灰色。时间开始增加,我们的秒表正常工作,但如果我们甚至不能注册任何圈数,这是什么类型的秒表呢?
让我们创建一个忙于注册圈数的函数。我们还需要一种方法来显示这些圈数,这是我们完成该功能后将要做的事情。我在想,我们可以像使用startAndStop()
函数一样使用这个函数,我们应该能够使用同一个按钮注册圈数并清除所有圈数。所以,让我们看看我是如何做到这一点的:
图 11.15 – 使用 setLap 函数来注册和清除所有圈数
这是一个直接的函数;我们的函数可以根据我们的 started 状态变量值做两件不同的事情。如果计时器已经开始,我们可以注册新的圈数,但如果计时器不再工作,我们应该能够清除所有圈数并准备好进行新的计时。
现在我们有了这个函数,让我们将它链接到我们的Lap按钮上,就像我们在Start按钮上做的那样:
<Button size="small" color="#f4d1dc" onPress={() => lap()} shadowless>{started ? "Lap" : "Clear laps"}</Button>
现在,让我们来展示屏幕上的圈数。我们将通过从'react-native'
导入FlatList
来实现这一点,所以只需滚动到文件的导入部分并添加FlatList
。我们的新导入应该是这样的:
import { StyleSheet, SafeAreaView, FlatList } from 'react-native';
显示圈数
我们应该在<Block flex={0.68} />
组件中使用FlatList
组件,而不是当前存在的<Text />
组件。因此,删除<Text />
组件,看看我对<FlatList />
的实现:
图 11.16 – 使用 FlatList 代替我们的 Text 组件
这里没有什么新东西。我们已经使用了FlatList
组件来构建引导屏幕,你可能已经注意到我们在那里有一个新组件叫做<LapItem />
。我已经在我们的主计时器组件下定义了这个组件。你可以随时将它移动并在components
文件夹下创建一个单独的文件,但我觉得让它靠近主组件对我来说更容易查看。让我们来看看这个组件:
图 11.17 – 在 FlatList 中使用的 LapItem 组件
这个组件接受一个叫做item
的prop
,它是一个包含显示圈数所需信息的数组。
有了这个,我们完成了这个漂亮的屏幕。保存并重新加载 JavaScript,然后试一试。以下截图显示了我们的应用现在应该是什么样子的:
图 11.18 – 完成的计时器组件
它正在工作!看起来很酷,我们在构建过程中度过了愉快的时光。现在,让我们开始在计时器屏幕上工作。
创建一个计时器
现在我们已经完成了秒表屏幕,是时候打开Timer.js
文件,开始处理我们的计时器屏幕了。我们应该立即开始,清空所有内容,并开始导入我们需要的一切。
首先,我们都知道在计时器周期结束时,总会播放一个声音,让你知道它已经停止了。为此,我们需要一个名为expo-av
的新包。这是一个用于处理音频的 Expo 包。它非常容易使用,所以让我们通过以下命令将其安装到我们的项目中:
expo install expo-av
现在我们已经安装了这个,我们可以开始导入我们需要构建这个组件的一切。我们将需要一个计时器组件,它与秒表非常相似。我们还需要使用间隔和日期对象来计算一切,因此useEffect
和useState
对我们的屏幕至关重要。
不同之处在于,我们需要让用户精确输入他们希望计时器工作的时间。我们可以使用'react-native'
中的<TextInput />
组件来实现这一点。因为我们使用了输入,我们还需要一个<KeyboardAvoidingView />
组件,它可以帮助我们重新组织布局,以便我们的输入永远不会被键盘的打开所隐藏。让我们来看看我们的导入:
图 11.19 – 计时器屏幕的导入
正如你所看到的,import
语句与秒表屏幕非常相似。这是因为这些屏幕是相似的。然而,通过让它们做同样的事情,我们可以学会始终通过查看我们过去所做的来激励自己。你编写的所有代码将帮助你解决可能遇到的其他问题。因为我们已经创建了秒表屏幕,现在我们知道了setInterval()
的不准确性以及如何应对它。
现在,让我们开始为我们的计时器屏幕创建基本功能,以及一个我们可以使用的布局。对于布局,我们将以与我们开始秒表屏幕相同的方式开始一切;也就是说,使用附加了styles.container
的<Block />
组件。之后,我们将使用<SafeAreaView />
,然后使用一个附加了flex: 1
样式的<KeyboardAvoidingView />
。
在那个<KeyboardAvoidingView />
组件内部,我们将有两个<Block />
元素。第一个将有一个<TextInput />
组件作为子元素,它将是计时器的标题。我们在这里使用<TextInput />
是因为你可能希望更改计时器的标题;这只是一个很酷的小功能。第二个将有两个<TextInput />
元素 - 一个用于分钟,一个用于秒。这样,用户可以输入他们需要的计时器的任何数字。第二个<Block />
元素还将包含计时器的开始/停止按钮。让我们看看它是什么样子的:
图 11.20 - 为计时器屏幕创建的布局
正如我们之前解释的那样,这并不复杂,但你会注意到我已经为我们的<TextInput />
组件填写了值。我还确保了一个状态变量适用于我们的开始/停止按钮。这是因为我们已经经历了秒表屏幕,这意味着我们已经体验到我们需要某些状态变量以便我们可以改变按钮内的文本。
正如你所看到的,我们还在我们的<TextInput />
组件上使用了editable
属性,因为我们只希望在计时器不工作时才能编辑这些值。我们还可以看到另一个新属性,称为returnKeyType
。这个属性允许我们告诉设备我们希望为用户提供哪种类型的键。我选择了done
键,因为一旦他们添加他们想要的数字,他们就可以按下那个键并继续向前。
我们还从之前的章节中知道<TextInput />
是一个受控组件,这意味着它需要一个状态变量来适用于value
属性,同时也需要一种通过onChangeText
属性来改变状态的方法。知道了这一切,我建议你多读几遍那段代码,看看你是否能理解它。我们不会再对它进行任何更改,因为这已经足够让我们能够直接进入计时器的功能。
让我们来看看我们为这个计时器定义的状态变量:
图 11.21 - 为计时器屏幕创建的状态变量
因此,在计时器屏幕的功能组件开始时,我们将编写所有这些状态变量。就像以前一样,我们有一些时间状态变量和一些控制状态变量。我们已经看到了intervalId
和startTimer
(在秒表屏幕中我们称其为started
)。
让我们简要解释一下我们正在使用的其他状态变量。countdownMinutes
和countdownSeconds
变量严格用于显示计时器的分钟和秒钟。final
变量是因为我们将根据用户的输入知道我们希望我们的计时器持续多长时间。一旦我们启动计时器,我们将使用它来计算时间量。
正如你所看到的,我们还有timer
和timeDisplay
变量。timer
变量的作用就像我们在工作台时elapsed
变量的作用一样。timeDisplay
变量是为了让我们始终拥有计时器的秒数值。这样,我们可以确保在计时器达到0
时停止它。
我们的title
变量是屏幕标题,用户可以随时更改。声音变量是因为我们需要知道声音是否已在屏幕上加载。这将帮助我们使用另一个useEffect()
函数,以便我们可以在自己之后清理。
太棒了!我喜欢现在我们可以更快地移动,因为我们已经完成了秒表屏幕。这意味着我们正在学习,而经验是最好的老师!让我们来看看start()
函数,每当我们按下开始按钮时我们将调用它:
图 11.22 - start()函数用于启动或停止计时器
正如我们所看到的,我们正在遵循我们在秒表屏幕上使用的相同模式。这使我们能够将此函数用作按钮的启动或停止函数。因此,如果startTimer
状态变量为false
,那么我们将使用Date
对象初始化一个新变量。然后,我们将从屏幕上的两个<TextInput />
组件中获取的分钟和秒钟设置为该日期,将这些添加到当前日期的分钟和秒钟。这意味着我们已经获取了当前日期并添加了用户输入的时间。这是我们试图达到的最终日期,因此下一步是使用我们刚刚计算的日期设置我们的final
状态变量。
然后,我们必须将startTimer
变量设置为 true,这将通知我们的组件计时器已经启动。在这一点上,我们还将加载声音。让我们为此定义loadSound()
函数:
图 11.23 - 带有新关键字 async 的 loadSound()函数
你现在可能已经想到了,这个函数有一个叫做async
的新关键字。不用担心 - 这是为了确保我们的函数在尝试加载声音时不会停止整个应用程序。如果你没有可以使用的声音,你可以在 GitHub 上找到我在这个项目文件中创建的声音。你也可以创建自己的声音,甚至使用一些没有版权的在线资源。我遵循了'expo-av'
文档来加载声音。这就是为什么我总是强调,每当有你不理解的东西时,你的第一步应该是查看特定包/库的文档。
现在我们已经加载了声音并启动了计时器,我们应该能够看到我们屏幕背后的所有逻辑存在的地方。就像我们之前做的一样,我们使用useEffect()
函数来确保它们只在特定状态变量改变时触发。我们要使用的第一个useEffect()
函数将依赖于final
状态变量。这是因为这个变量对于所有的数学运算都是必需的,所以自然地,在做任何其他事情之前,我们会检查它:
图 11.24 - 第一个 useEffect()函数取决于最终变量
所以,这就像我们之前做的一样。在这里,我们调用了一个setInterval()
函数 - 这一次是每 250 毫秒一次 - 但只有在最终变量被初始化的情况下才会调用。也就是说,如果用户按下了开始/停止按钮。这里没有什么奇怪的事情发生,所以我认为我们应该能够继续并查看下面的useEffect()
函数:
图 11.25 - 第二个 useEffect()函数取决于计时器变量
因为我们在第一个 useEffect()函数中设置了 timer 状态变量,它将作为第二个 useEffect()函数的触发器,以便调用它。这是我们进行计时器所需的所有数学运算的函数。我们正在计算最终日期和从 timer 变量接收到的新日期之间的秒数差。
在计算出秒数差之后,下一步是检查 timeDisplay 变量是否与新计算出的差异不同。如果不同,我们将 timeDisplay 设置为这个新值。我们这样做是为了确保我们始终使用新值计算所有内容。
之后,我们只需按照通常的数学方法计算分钟和秒数。接下来,我们必须通过 countdownMinutes 和 countdownSeconds 变量将我们的
现在,让我们看看下一个依赖于 timeDisplay 状态变量的 useEffect()函数:
图 11.26 - 第三个 useEffect()函数取决于 timeDisplay 变量
只有当 timeDisplay 为 0 或更少时,这个函数才起作用。一旦达到 0,我们应该停止并重置到目前为止使用的所有变量。在这里,我们要确保我们的间隔将被清除,我们的 timeDisplay 变量回到 0,并且倒计时变量回到 0。这也应该是我们播放声音的地方,所以我们必须检查该声音是否已加载,然后使用 playAsync()函数来启动它。
因为我们在计时器应用程序启动时加载了声音,所以在屏幕卸载时也应该卸载它。实际上,如果我们不使用声音,就没有理由将其保留在设备的内存中。我们将通过在另一个 useEffect()函数中使用清理函数来实现这一点。让我们看看它是什么样子的:
图 11.27 - 仅用于清理函数的第四个 useEffect()函数
所以,再次强调,这直接受到他们文档的启发。一旦组件卸载,我们必须调用这个函数,它会检查声音是否已加载。如果是,那么我们必须在我们的sound
状态变量上调用unloadAsync()
函数。
恭喜!我们已经完成了计时器屏幕!让我们看看它的外观和它是否工作:
图 11.28 - 完成的计时器屏幕
看起来很棒!在这里,我们可以更改标题;我们还可以更改计时器的值,并且在我们点击开始后它可以工作。等待 30 秒后,会播放一个很酷的小声音!
有了这个,我们就完成了这个应用程序!等等...其实不是 - 底部选项卡导航器看起来有点空,所以我们应该添加一些图标。我们还可以做一些小的事情来增强用户体验。让我们继续并开始添加所有这些小的增强。
完成我们的应用程序
在这一点上,我们需要向底部选项卡导航器添加一些图标。但我们应该如何做呢?幸运的是,React Navigation 有一种非常直接的方式来修改其默认组件。
因为我们已经在这里,我们还应该改变我们目前使用的选项卡的焦点颜色。
所以,让我们回到我们的routes.js
文件。因为我们想要向我们的选项卡添加图标,我们应该从'galio-framework'
中导入Icon
组件。在所有导入之后,我们应该写下以下内容:
import { Icon } from 'galio-framework';
现在我们已经导入了用于显示图标的组件,让我们看看应该如何做。搜索你的AppTabs()
函数,并找到<Tab.Navigator />
组件。在这里,我们将添加两个新的属性,称为screenOptions
和tabBarOptions
。让我们快速检查它们,并看看我们如何使用它们在底部选项卡导航器中实现图标:
重要提示
截至 2021 年 8 月 14 日,React Navigation 已更新到 v6,并且tabBarOptions
已被弃用,而是使用了options
属性,它可以在每个屏幕上进行设置。关于版本和 React Navigation 库的更多信息,我建议阅读文档,可以在reactnavigation.org/
找到。
图 11.29 - 我们的新 AppTabs()功能组件已实现图标
因此,我们可以看到,screenOptions
属性与一个函数一起使用,该函数接受每个屏幕的navigation
和route
属性。现在,我们正在使用route
,因为我们想要检查哪个屏幕等于每个路由。这样,我们就可以为底部选项卡导航器中的每个屏幕设置一个情况。该函数返回一个带有名为tabBarIcon
的键的对象,其值设置为一个正在收集有关用户当前关注的屏幕的大量信息的函数。
这就是我们检查用户是否关注特定屏幕的地方。根据这一点,我们可以呈现不同类型的图标。因此,如果用户关注秒表屏幕,那么我们将显示填充的图标,而如果他们没有关注,我们将只显示该图标的轮廓。这是一个小细节,可以帮助用户知道他们认为自己在的屏幕。
现在,设置我们图标的颜色就容易多了。为此,我们将使用tabBarOptions
属性。我们将向其传递一个包含两个键的对象:activeTintColor
用于当用户当前关注特定屏幕时,以及inactiveTintColor
用于当用户不当前关注特定屏幕时。
让我们保存这个并检查我们的应用程序!我认为我们都可以同意现在看起来好了 10 倍:
图 11.30 – 在底部选项卡导航器中添加图标后的最终应用程序布局
但是不要关闭routes.js
文件!我们还有一件事要做。正如我们在第十章中讨论的那样,构建入门屏幕,入门屏幕应该只在您第一次打开应用程序时出现。没有理由总是看到那个入门屏幕。您的许多用户会说,“好的,我们知道了,这是一个秒表应用程序,让我直接进入秒表部分!”
然而,我们该如何做到呢?这就是AsyncStorage
派上用场的地方!为了能够使用这个包,我们需要安装它。让我们再次打开我们的终端并输入以下命令:
expo install @react-native-async-storage/async-storage
既然我们安装了这个很酷的小包,让我们在routes.js
文件中导入它。在我们的Icon
导入之后,就像这样:
import AsyncStorage from '@react-native-async-storage/async-storage';
现在,我们可以在我们的AppStack()
函数中使用这个Icon
导入。一旦找到了那个函数,我们应该创建一个名为viewedOnboarding
的状态变量。这个变量将帮助我们知道用户是否已经看过引导屏幕。
在定义了那个变量之后,我们需要在我们的移动应用程序的开头立即运行一个函数。你还记得我们应该怎么做吗?没错 - 另一个useEffect()
函数。我打赌你已经厌倦了这些函数,但它们真的很棒!
这个useEffect()
函数应该调用另一个名为checkOnboarding()
的函数,其目的是检查用户是否已经看过引导屏幕。根据这一点,我们将设置我们的状态变量viewedOnboarding
为true
或false
:
图 11.31 - 为我们的 AppStack()函数编写的逻辑
现在,我们需要另一个async
函数。但我们只能在async
函数中使用这个包。我们将尝试看看本地存储是否存储了该项,如果是的话,我们将把viewedOnboarding
状态变量设置为true
。
也许你会想知道我们什么时候会将该项添加到我们的本地存储中。嗯,当用户在引导屏幕内最后一次按下下一步时,我们应该这样做。所以,让我们继续前进到Onboarding.js
文件并让它发生。
现在我们在Onboarding.js
文件中,我们应该先再次导入AsyncStorage
包。之后,我们应该直接跳转到scrollTo()
函数。首先,我们会将这个函数改为async
。之后,我们有一个if-else
语句。我们将改变else部分,在那里我们有一个console.log()
没有真正原因存在,并且使用navigation.navigate()
函数代替。让我们看看我们将如何改变它:
图 11.32 - 我们修改后的 scrollTo()函数
在这里,我们再次尝试使用 try-catch。正如你所看到的,我们正在使用setItem
将该项设置为本地存储中的true
。这就是这个库知道这个项目在存储中被设置为 true 的方式。
现在,让我们回到routes.js
文件。我们已经准备好了,但我们需要确保只有在我们的用户还没有看到入门屏幕时才显示该路由。我们将使用条件渲染来实现这一点,这是我们自本章开始以来一直在使用的技术。让我们看看它是什么样子的:
图 11.33 – 条件渲染应用于我们的<Stack.Screen />入门组件
如你所见,我们正在检查我们的状态变量viewedOnboarding
。如果这个变量被设置为false
,那意味着我们的用户还没有看到入门屏幕,所以我们的路由应该显示出来。如果设置为true
,那意味着我们不会显示任何路由,实际上使得入门变得不存在。
至此,我们完成了这个应用程序!保存所有文件,重新加载 JavaScript,然后查看你的应用程序。一开始,你会看到入门屏幕。点击下一步直到它消失,然后在秒表和计时器屏幕上玩耍。之后,再次打开应用程序,你会看到一些惊人的事情 – 入门屏幕不再显示了!相反,你将直接进入秒表屏幕 – 更确切地说,是选项卡导航屏幕。
恭喜!你现在拥有一个相当酷和功能齐全的应用程序。向你的朋友和家人炫耀吧;让他们看看你取得了多大的进步!
总结
这一章对我们两个人来说是一段漫长的旅程。不要害怕!挑战越大,回报就越好。你已经完成了一段相当漫长而有趣的旅程。现在你有一个完全功能的应用程序,可以向朋友展示。这些是你成为一名优秀的 React Native 开发人员的第一步。
我们从 React Navigation 开始了这一章。创建路由并将它们链接到入门屏幕是我们做过的最酷的事情之一。这也非常容易,再次证明了 React Native 社区是多么伟大。
将我们的应用程序链接到 React Navigation 库后,我们开始着手处理秒表屏幕。我们发现setInterval()
函数并不那么准确,因此我们开始使用日期对象,这对于计时来说更加高效。
完成秒表屏幕感觉像是一次重大胜利,因此,创建我们的计时器屏幕进展顺利得多。但是,我们又学到了新的东西,那就是在计时器完成运行后如何播放声音。我希望做这件事为你打开了许多创造力的大门。
在本章末尾,我们专注于用户体验,并确保用户在查看底部选项卡导航器时会看到一些图标。除此之外,我们还使用了一个叫做AsyncStorage
的库,这样我们就可以让已经有经验的用户远离入门屏幕。
学习这么多东西真是令人耳目一新。是的,这是很多信息,但我希望你意识到尽可能应对多种挑战有多么重要。就像在现实生活中一样,它们帮助我们积累经验,这有助于我们成为优秀的程序员。
现在,让我们为下一章做好准备,我们将讨论作为 React Native 开发人员可以选择的其他路径,以成为一名优秀的程序员。
第十二章:接下来该去哪里?
在过去的 11 章中,我们学到了很多关于 React Native 以及如何使用它构建跨平台移动应用程序。我们还看到了使用 Galio 等库可以帮助我们快速构建看起来很酷的应用程序,从而节省了大量时间。
在我们的 React Native 之旅中,我们遇到了许多不同的情况,我们学到了用户界面,用户体验,Expo 等等。我希望你喜欢我们学到的一切,并且你会对你可以继续学习和研究 React Native 的许多方式感到兴奋。
本章将重点讨论我们如何开始积累更多的知识。我们将讨论我们应该如何应对未来可能面临的任何新挑战,无论我们想学习新东西还是只是为我们的应用程序开发使用小型库。
我们还将讨论 Galio 社区以及您可以加入和帮助发展 Galio 的内容。我们还将了解开源对程序员社区的重要性,以及它如何可以帮助您的职业发展。
我还会给你一些技巧和窍门,当你开发跨平台移动应用程序时,生活并不容易时,你应该使用这些技巧。我希望你经常回顾这一部分,以获得一些对你的工作有启发和动力。
最后但并非最不重要的是,我们将讨论书籍和阅读的重要性。更具体地,我们将讨论为什么尽可能多地从多种来源尝试学习和了解许多事情是如此重要。
我希望你喜欢这一章,并且它能给你所需的士气提升,让你继续学习和享受 React Native。
本章将涵盖以下主题:
-
始终阅读文档
-
Galio 社区
-
技巧和窍门
始终阅读文档
每当您开始新项目或面对新的编程挑战时,重要的是确保在实际着手编码之前做好准备。这种规划和准备可以采取许多形式,但你可以做的最重要的事情之一就是阅读你正在使用的技术的文档。
现在,阅读文档的关键在于学会如何理解它。随着时间的推移,随着经验的积累,你将了解什么是优秀的文档,以及你究竟希望从中获得什么。
阅读技术文档可能并不总是容易的,特别是对于初学者程序员来说。某种语言中使用的一些术语可能与另一种语言中的不同。文档还包含许多术语,这些术语可能在你的学习旅程开始时让你感到不舒服。
不要害怕!我已经列出了阅读和解决你将来面临的挑战的一些提示:
-
放松!阅读文档需要时间;就像阅读任何一本书一样。不要急着直接跳到书的中间去读所有的打斗场景;有时候,最好先读一下介绍,这样你就可以熟悉每个角色的能力。所以,准备好享受这段酷炫的旅程,并享受其中的每一部分。如果你累了,就休息一下,看看窗外,稍后再回来,头脑清晰些。
-
阅读多个来源,就像一名真正的记者:有时候,你正在阅读的文档可能对你来说有点太高级,或者简单地不完整。有时候,你甚至可能遇到一些你无法理解的段落。因此,你需要学会如何超越官方文档。从不同的来源阅读更多关于该主题的文章。这将通过看到不同的观点和例子来帮助你刷新自己对你正在学习的概念的理解。
-
复习术语:你总会看到一些以前从未听说过的新术语。你应该列出所有你不理解的术语,并花些时间复习它们。这将在长远来看对你有所帮助,特别是当你试图学习更多的库时。
-
版本检查:你要学习的库总是有多个版本,所以确保你正在阅读正确的文档!
你应该总是先去阅读技术文档,而不是先去看视频教程的原因是,通常后者对解释概念的深度很肤浅。当然,有很棒的视频教程,但直接从源头学习总是更好的,正如我之前提到的,然后再查看其他来源。
在线文档通常分为两部分:入门或指南和文档。第一部分通常会有一些小而简单的示例,说明如何使用包或库。这部分的目的是让你尽可能多地了解包的上下文。文档部分更像是电话簿。它很直接,你总是可以找到关于特定事物的具体信息,这意味着这部分不是指导你如何确保正确安装或使用它的指南 - 它更像是一个充满了你需要的每个关键字定义的词典。
既然我们已经谈论了阅读文档,我们应该讨论阅读书籍。一本书有点像一个大型文档项目的入门部分,但是有更多的例子和实际挑战。
书籍真的很有帮助
书籍的目的是指导你学习特定技能的过程,或者是讲述不同超级英雄为了更美好的世界与坏黑客作斗争的故事。但我们都知道这里说的是技术书籍,所以让我们暂时忽略超级英雄。
通过阅读一本书,你并没有取代文档,因为信息有时可能过时或者太主观。然而,正如我已经提到的,这并不意味着一本书对你没有帮助。
重要的是尽可能多地阅读书籍或文章,特别是你最感兴趣的主题。获取尽可能多的观点和工作流程将使你成为更好的程序员。
为什么我说更好的程序员?我相信程序员生活中最重要的方面是他们通过研究、练习和尝试不同的技术或库积累的经验水平。
书籍可以帮助你实现目标。有很多著名的程序员对你编写代码的方式或者思考方式有着强烈的看法。他们都将自己的知识写入了书籍,因此我们应该确保利用他们的知识来使自己受益。
这正是任何科学领域取得进步的方式。通过吸收他人的想法并在此基础上建立,你将有机会创造出新的东西。
这就是为什么书籍对我们程序员来说真的很有帮助 - 它们是知识的快照。我们用它们来保持与技术和思维方式的联系。
但这并不意味着我们可以通过阅读书籍获得所有必要的知识来推动我们的进步。它们更像是额外的指导。重要的是要尝试从尽可能多的来源获取信息,而书籍是除了原始文档之外可能会激励你的其他来源之一。
盖利欧的社区
如果你曾经觉得自己无处容身... 那么你应该尝试一下盖利欧的 Discord。它会帮助你解决任何你可能有的特定问题。
我们在这里不仅仅是讨论 Discord - 我们在这里讨论盖利欧的社区如何帮助你学习帮助开源项目的基础知识,以及帮助盖利欧的整个经验对你所属的社区有何益处。
有多种方式可以参与盖利欧。在这一部分,我们将讨论你可以了解盖利欧的所有方式,与社区互动,以及研究它的工作方式。让我们从讨论盖利欧的网站开始。
盖利欧的网站
我们从盖利欧的网站开始,因为这通常是人们在寻找盖利欧时找到的第一件或第二件事情。你可以通过导航到galio.io/
找到他们的网站。
首先,这个网站看起来相当酷:
图 12.1 - 盖利欧的网站
正如你所看到的,盖利欧的配色方案也出现在网站上。这是一切的开始,也是关于盖利欧的所有链接的起点。这是人们开始了解盖利欧是如何运作的,以及为什么它被创建的地方。
在导航栏中,你可以找到不同的链接来帮助你的旅程。第二个是入门套件。这将带你直接进入一个 GitHub 存储库,你可以在其中尝试一个由盖利欧构建的包含不同屏幕的项目。这将帮助你对盖利欧能够如何帮助你的项目产生灵感。你可以学习很多示例屏幕,甚至在你的项目中使用,因为一切都是开源的,可以自由使用、重用和修改。
第三个链接,组件,直接带你到文档,这是我们将在本章后面讨论的内容。然后是第四和第五个链接:示例和高级主题。第一个的目的是展示其他人用 Galio 库构建了什么;它联系社区并通过展示他们的工作来帮助他们。第二个是为了帮助其他开发人员购买使用 Galio 构建的主题,以最大化他们的工作流程并提高他们的生产力。这是为那些已经使用过 Galio 并想要快速构建高质量内容的开发人员。
你应该访问该网站并四处看看,看看你还能学到什么关于 Galio 的东西。现在,让我们继续阅读 Galio 的文档。
Galio 的文档
这是每个人都来学习和了解如何使用 Galio 的地方,以及了解 Galio 的每一个细节。尽管 Galio 有一个指南,但这份文档更像是直接的技术文档:
图 12.2 – Galio 的文档网站
当你前往galio.io/docs/
时,你会注意到一个包含有关 Galio 的一些信息的大着陆页面。你需要向下滚动以查看导航栏并开始接受 Galio 的文档。
你应该注意导航栏,因为这里你可以找到所有 Galio 的组件,以及关于每个组件的大量信息。
让我们来查看<Block />
组件的文档:
图 12.3 – 块组件概述
正如你所看到的,每个组件都有一个描述,让你更多地了解它。然后,你会看到如何使用它并将其导入到你的项目中的示例。还有一个包含你可以与感兴趣的组件一起使用的属性的表格。
在这里,你可以找到关于这些属性的信息。还有一个描述告诉你它适用的样式类型或者它的作用。
通过直接深入 Galio 的 GitHub 存储库中的代码,你可以找到关于 Galio 组件的大量信息。
Galio 的存储库
你可以在github.com/galio-org/galio
找到 Galio 的 GitHub 存储库。你可以在这里找到很多东西。在这里,你可以做的最强大的事情就是查看 Galio 的源代码:
图 12.4 - Galio 的 GitHub 存储库
通过这样做,你将了解 Galio 是如何创建的,也可以调试你的代码,以防某些东西不按照你的期望工作。这是汲取 UI 库或开源项目灵感的完美地方。
还有一个Wiki标签。这是你可以找到很多额外关于 Galio 的信息的地方。额外的信息,我指的是开发的状态,如何使用它,以及如何为这个开源项目做出贡献的指南。
有很多方法可以为 Galio 做出贡献,我支持你发现自己帮助图书馆的途径。
社区是真正支持 Galio 的力量。没有社区,我们就无法取得我们现在的进展,我们总是在那里欢迎你,如果你需要帮助。
这就是成为社区的一部分的意义。这意味着帮助和得到帮助。这意味着对一个项目有如此大的信心,以至于你愿意尽可能地支持它。如果你觉得 Galio 值得你的支持,那就加入我们的船,让我们一起工作吧!
Galio 的 Discord
与人们互动的一个很好的地方,除了Issues标签外,当寻求帮助或解决错误时,是 Discord。你可以在我们的网站上找到 Discord 链接。
在 Discord 上,每个人都在分享有趣的图片或询问如何使用 Galio 的问题。这就像有一个小的在线家庭,总是在帮助你解决 Galio 的问题。
既然我们已经经历了所有这些,让我们来看一些关于你的 React Native 项目的技巧和窍门。
技巧和窍门
React Native 很棒,但所有伟大的事物都有一些小缺陷。因为你永远不知道你可能会遇到什么类型的错误,我决定创建一个最常见的错误和修复方法的列表。让我们开始吧!
导入错误
每当你混合了默认导入和命名导入时,通常会出现这个错误。让我们来看看错误信息:
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of 'App'.
正如我们所看到的,这个错误是由于一个组件被导入到主App.js
文件中引起的。不幸的是,错误消息没有告诉你是哪个组件或哪一行破坏了应用程序。为了确保这种情况不会再次发生,你需要仔细检查你导出/导入的组件,以确保没有错误。现在,让我们看看这是如何发生的。
我们知道有默认导入和命名导入,但让我们讨论一下它们之间的区别。
假设你有以下的输出:
export const myComponent;
现在,这是一个命名的export
。因为它是一个命名的export
,你必须像这样导入它:
import { myComponent } from './myComponent';
现在,让我们看看默认导出是如何工作的:
export default myComponent;
作为默认导出,你可以在不使用花括号的情况下导入它。另一件很酷的事情是,名称不再重要:
import stillMyComponent from './file';
import myComponent from './file';
这两个导入的作用是一样的,尽管我们给它们取了不同的名字。这很酷,对吧?你知道的越多,你就越有准备。
React Native 版本不匹配
所以,让我们直接进入并查看错误消息:
React Native version mismatch.
Javascript version: X.XX.X
Native version: X.XX.X
Make sure you have rebuilt the native code...
每当你尝试构建应用程序时都会出现这个问题。这是因为当你在终端内使用expo start
命令时,你使用的打包工具使用了不同版本的react-native
。你可能会在升级 React Native 或 Expo SDK 版本后遇到这个问题,甚至在尝试连接到错误的本地开发服务器时也会遇到。
让我们来解决这个问题。首先关闭 Expo 服务器。之后,你应该尝试两件不同的事情:第一件事是从你的app.json
文件中删除sdkVersion
文件。第二件事是确保该文件与你的package.json
文件中的expo
依赖的值匹配。
通过使用托管工作流项目,你可以通过运行expo upgrade
命令来确保你的react-native
版本是正确的。如果你有一个原生工作流项目,请确保你已经正确地升级了所有内容。
一旦你做完所有的事情,你应该通过运行以下命令来清除你的缓存:
rm -rf node_modules && npm cache clean --force && npm install && watchman watch-del-all && rm -rf $TMPDIR/haste-map-* && rm -rf $TMPDIR/metro-cache && expo start --clear
现在,我们不应该再看到这个错误了 - 那太棒了!
无法解析
这个错误消息大致会是这样的:
Unable to resolve module <module name> from <path>: Module does not exist in the module map or in these directories
这个错误通常是由在你的package.json
文件中使用^
或~
等符号而生成的。
为了解决这个问题,删除这些符号并删除你的node_modules
文件夹。重新安装所有的包,一切都应该正常工作。
总结
在这一章中,我们为成为 React Native 开发者做好了准备。我们讨论了很多东西,这些东西应该帮助你在你的旅程中。我也坚信我已经能够通过激励你尽可能追求知识来帮助你。
首先,我们讨论了文档有多么有用。我们还学会了如何从尽可能多的资源中收集信息。书籍是我们教育的一个非常重要的部分,所以请确保至少尝试阅读更多关于 React Native 的书籍。
然后,我们讨论了 Galio 以及如何与社区取得联系。我们看到了我们可以免费使用的许多资源,而且质量也很高。当我们再次见面时(至少我希望如此),这将会很有帮助,可以在 Galio 的 Discord 或存储库上交流。
之后,我们解决了一些常见的 React Native 问题,并学会了如何解决它们。我希望你会发现这很有帮助,并且希望你以后能回来查看,这样你就可以比进行谷歌搜索更快地解决错误。
此时,你已经准备好开始开发项目了。你终于可以构思一个想法,并努力使其取得巨大成功。我希望这本书对你有所帮助,也希望你尽可能多地学到了东西。我还希望你对未来更加充满希望和热情。保持安全和健康!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了