Gatsby4-React-Web-开发升级指南-全-
Gatsby4 React Web 开发升级指南(全)
原文:
zh.annas-archive.org/md5/9f42526be48ad606d491dd5c4c43381d译者:飞龙
前言
Gatsby 是一个强大的 React 静态网站生成器,它使你能够创建闪电般的网络体验。使用 Gatsby 的最新版本,你可以将你的静态内容与服务器端渲染和延迟静态内容相结合,创建一个全面的应用程序。通过 Gatsby 提升 React Web 开发为初学者提供全面的介绍,让你迅速掌握 GatsbyJS。
本书包含实践教程和项目,这本易于遵循的指南首先教你 GatsbyJS 的核心概念。然后,你会发现如何通过利用 GatsbyJS 框架的力量来构建高性能、可访问和可扩展的网站。本书采用实用方法,帮助你从个人网站到具有身份验证的大规模应用程序构建一切,让你的网站在 SEO 排名中脱颖而出。
到本书结束时,你将了解如何构建用户喜爱的客户网站。使用这个工具的每个方面,性能和可访问性都是重点,你将通过本书的材料学习如何充分利用它。
本书面向对象
本书面向希望使用 GatsbyJS 与 React 构建更好的静态和动态 Web 应用的 Web 开发者。React 基础知识的前期经验是必要的。Node.js 的基本经验将帮助你充分利用本书。
本书涵盖内容
第一章,Gatsby.js 入门概述,为你提供 Gatsby.js 是什么以及我们在后续章节中构建我们的 Web 应用将使用的指导原则的基线知识。
第二章,样式选择和创建可重用布局,展示了如何就你希望如何设计你的应用程序做出明智的选择。我们将涵盖使用 CSS、SCSS、styled-components 和 Tailwind.css。
第三章,从任何地方获取和查询数据,让你能够轻松地从多种不同的来源将数据源入你的 Gatsby 项目中。
第四章,创建可重用模板,解释了如何使用你的源数据通过编程创建网站页面、博客文章等!
第五章,处理图像,展示了如何在不影响性能的情况下将响应式图像添加到你的 Gatsby 网站中。
第六章,提高你的网站搜索引擎优化,解释了 SEO 是如何工作的,搜索引擎在你的网站页面中寻找什么,以及如何提高你的网站在网上的存在感。
第七章,测试和审计你的网站,涵盖了使用行业标准工具进行测试和审计你的应用程序。
第八章, 网站分析和性能监控,解释了如何向你的网站添加分析功能,并利用你的受众使你的网站变得更好!
第九章, 部署和托管,展示了如何将我们一直在工作的项目部署出来,让全世界都能看到!
第十章, 创建 Gatsby 插件,涵盖了创建源和主题插件,并解释了如何将它们贡献给 Gatsby 插件生态系统。
第十一章, 创建认证体验,展示了如何添加受保护的路线以在你的网站上创建登录体验。
第十二章, 使用实时数据,解释了你可以如何使用套接字来创建利用实时数据的体验。
第十三章, 国际化与本地化,涵盖了你可以使用的模式,使你的网站在扩展时翻译变得简单。
要充分利用这本书
所有代码示例都已使用 macOS 上的 Gatsby 4.4.0 进行测试。然而,它们也应该适用于未来的 4.x 版本。

本书假设你已经安装了一个你熟悉的 集成开发环境(IDE) 。
如果你使用的是这本书的数字版,我们建议你亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助你避免与代码复制粘贴相关的任何潜在错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富的书籍和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
下载彩色图像
我们还提供了一份包含本书中使用的截图和图表彩色图像的 PDF 文件。你可以从这里下载:static.packt-cdn.com/downloads/9781800209091_ColorImages.pdf。
使用的约定
本书使用了一些文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“在你的根目录中创建一个gatsby-config.js文件,并添加以下内容。”
代码块设置如下:
module.exports = {
plugins: [],
};
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
import React from "react"
import {Link} from "gatsby"
export default function Index() => {
return (
<div>
<h1>My Landing Page</h1>
<p>This is my landing page.</p>
<Link to="/about">About Me</Link>
</div>
)
}
任何命令行输入或输出都应如下所示:
gatsby develop -H 0.0.0.0
粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“当你点击查询上方的播放按钮时,你将在中央右侧列中看到该查询的结果,其中包含一个包含数据属性和我们的查询结果的 JSON 对象。”
小贴士或重要注意事项
看起来像这样。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法副本,我们将非常感激您能提供位置地址或网站名称。请通过电子邮件发送至 copyright@packt.com 并附上材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为本书做出贡献,请访问 authors.packtpub.com。
第一部分:入门
完成这一部分后,你应该对 Gatsby.js 有一个清晰的理解。你也应该能够在你本地机器上舒适地开发基本的 Gatsby.js 网站。
在这一部分,我们包括以下章节:
-
第一章, Gatsby.js 入门概述
-
第二章, 样式选择和创建可重用布局
-
第三章, 数据来源和查询(来自任何地方!)
-
第四章, 创建可重用模板
-
第五章, 与图像一起工作
第一章:Gatsby.js 入门概述
在这本书中,我们将利用你现有的 React 知识,并补充 Gatsby.js(从现在起我们将称之为 Gatsby)的知识,以创建性能优异且易于访问的静态网站。我希望给你提供使用 Gatsby 创建更好网站的工具,并让你加入静态网站革命。所以,祝你好运!
本章将从静态网页的简要历史和 Gatsby 的创建原因开始。然后,我们将思考 Gatsby 是什么以及它是如何建立在 React 之上的。接下来,我们将探讨 Gatsby 的一些用例,并确定 Gatsby 的竞争对手。最后,我们将设置一个基本的 Gatsby 项目,创建了我们的一些页面。
在本章中,我们将涵盖以下主题:
-
静态网页的简要历史
-
Gatsby 是什么?
-
Gatsby 用例
-
Gatsby 的竞争对手
-
设置项目
技术要求
本章中的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter01找到。
静态网页的简要历史
静态网站几乎与互联网本身一样历史悠久。它们是任何网站的原始蓝图——超文本标记语言(HTML)、层叠样式表(CSS)和JavaScript(JS)。在 20 世纪 90 年代,HTML 是互联网上唯一的发布机制。要将内容发布到互联网上,你必须创建一个静态 HTML 文件,并通过服务器将其暴露给互联网。如果你想修改你的网页之一,你需要直接更改其相应的 HTML 文件。
虽然如今学习 HTML 是基础教育的一部分,但在 20 世纪 90 年代,理解和编写这种语言是一项新颖的技能。创建或编辑内容成本高昂,因为每次修改都需要具备这种技能的人。幸运的是,内容管理系统(CMS)(WordPress、Drupal 等)很快出现,允许非技术用户控制网页的设计和内容。它还使用户能够通过用户界面存储和管理文件。CMS 今天仍然被广泛使用,并且越来越受欢迎。在过去十年中,使用 CMS 的网站数量从 23.6%增长到 63%。今天,超过 7500 万个网站使用 WordPress——这占到了整个网络的 30%!
前端框架和库几乎以相同的速度获得了知名度。构建单页应用变得司空见惯。如今,在 JS 世界中最为流行的 UI 库是 Facebook 的 React.js,这是一个功能有限的库,但包含了一些重大理念——虚拟 DOM、JavaScript 语法扩展(JSX)和组件化。无法否认 React 对 Web 开发产生了多大的影响。在 2020 年,80%的 JS 开发者使用过它,70%的 JS 开发者表示他们还会再次使用它。
前端框架已经彻底改变了开发者对待 Web 开发的方式,使他们能够专注于功能而非内容,并极大地加快了他们的工作流程。但你的速度取决于最慢的团队成员。当开发者开始使用这些框架并将它们与 CMS 集成时,CMS 平台的笨拙性质暴露无遗。传统的 CMS 工作流程使用了前端框架从等式中移除的数据库和环境。结合 CMS 的安全性和瓶颈问题,导致了静态网站的复兴。
Gatsby 的创始人 Kyle Mathews 是这一趋势的催化剂。他注意到对网站可访问性和性能的期望急剧增加。他观察到应用程序投入数百万美元用于用户体验。不可否认,2005 年和 2015 年网站之间的差异是显著的。在像网络这样的竞争环境中,你必须有一个能够脱颖而出的产品。Mathews 退后一步,识别了现有工具中的差距,并思考理想的产品可能是什么。这项研究引导他创建了 Gatsby。
我们几乎回到了起点,重新回到了静态内容,这在速度和性能方面是无与伦比的。
什么是 Gatsby?
Gatsby 是一个免费的开源静态站点生成器,它利用 React。静态站点生成器是创建静态页面并从源中补充内容的软件应用程序。静态站点生成器是传统数据库驱动的 CMS(如 WordPress)的替代品。在这些传统系统中,内容在数据库中管理和存储。当服务器接收到特定的 URL 请求时,服务器从数据库检索数据,将其与模板文件混合,并生成一个 HTML 页面作为响应。按需生成 HTML 可能是一个耗时的过程,可能会让用户无所事事,或者更糟糕的是,离开你的网站。对于加载时间少于 3 秒的网站,跳出率(在特定网站上查看过一页后离开的访问者的百分比)低于 10%,但对于加载时间为 4 秒的网站,跳出率跃升至 24%,对于加载时间为 5 秒的网站,跳出率高达 38%。
另一方面,像 Gatsby 这样的静态网站生成器在构建过程中生成页面。在这个过程中,Gatsby 将数据引入其 GraphQL 层,在那里可以在页面和模板中进行查询。请求的数据随后以 JSON 格式存储,并由构建的页面访问,这些页面由 HTML、JS 和 CSS 文件组成。用户可以将这些生成的页面部署到服务器。当它收到请求时,服务器会以预定的、静态的、渲染的 HTML 响应。由于这些静态页面是在构建时生成的,因此它们消除了数据库会引入的延迟。你甚至可以完全放弃使用网络服务器,并通过指向存储介质(如 AWS 简单存储服务(S3)存储桶的 CDN 来提供你的网站。这种差异是显著的;使用 Gatsby 构建的网站体验非常快,因为没有什么比发送静态内容更快了。
重要提示
一个静态网站可以包含动态和令人兴奋的体验!一个常见的误解是“静态”意味着网站是静止的。这离事实相差甚远。单词“静态”仅指客户端检索文件的方式。
虽然 Gatsby 以静态网站生成而闻名,但最新版本也包括了服务器端和延迟静态生成,为当静态生成不足时提供了渲染功能。
除了创建一个飞快的用户体验外,Gatsby 还注重开发者体验。随着我们学习和构建,我相信你们会开始认识到使用它的简便性。它实现这一点的步骤可以分解为四个步骤。
社区
Gatsby 拥有一个极其支持性的社区。在撰写本文时,已有超过 3,600 人为 Gatsby 仓库做出了贡献。这还通过围绕 Gatsby 的插件生态系统得到了进一步的放大;社区已经创建了 2,000 多个插件,这些插件抽象了其他开发者可能在他们的项目中希望使用的复杂功能。这些插件作为存储在 JS 仓库(如 NPM)上的包进行分发,可以在几行代码内添加到你的项目中。它们可以通过获取内容、转换数据、创建页面或为主题化应用程序来扩展你的网站。
从任何地方获取内容
每天我们需要组合的数据量以创建体验正在增加。在传统的 React 应用程序中,管理多个数据源可能会变得是一场噩梦。存储、处理、合并和查询数据都需要复杂的解决方案,而这些解决方案难以扩展。
Gatsby 以不同的方式做到这一点。无论你是从内容管理系统(CMS)、实时数据库,还是甚至自定义应用程序编程接口(API)中获取数据,你都可以将这些数据合并到一个统一的数据层中。Gatsby 社区不断贡献源插件,让你能够轻松地从你喜欢的来源获取数据。十有八九,你不需要写一行代码来获取你的数据,但在你需要的时候,我们将在第十章,创建 Gatsby 插件中介绍插件创建。
一旦数据被整合到这个数据层中,我们就可以在一个统一的数据层中探索和查询我们所有的数据源。利用 GraphQL 的力量,无论数据的来源如何,我们都可以以相同的方式查询数据来渲染页面。GraphQL 层是临时的,在应用程序构建完成后就不存在了,因此不会影响你生产网站的尺寸。如果你对 GraphQL 还不太熟悉,不要担心——我将在第三章,数据来源与查询(来自任何地方!)中解释它是如何工作的。
构建你已知的工具
通常当我们接触新技术时,我们会面临一个陡峭的学习曲线,因为我们需要理解新的语法和思维方式。在 Gatsby 中,我们基于你现有的 React 知识来构建,而不是从头开始。支撑我们所有代码的是许多你已经熟悉的相同的 React 组件模型。你应该从一开始就感到相当自信,因为代码看起来应该是熟悉的,如果你不熟悉,Gatsby 也可以帮助你通过更“内容驱动”的方法来学习 React。
提升网页性能
作为网页开发者,我们可以花费相当多的时间调整网站,以榨取它们的性能。有时,这可能会花费与构建设计一样多的时间,甚至更长。此外,性能的提升有时可能会因为超出你控制之外的网站设计变化而瞬间消失。正因为如此,一些大型组织有专门的团队来提高网站性能。但不必非得这样!当我们开始一起构建时,你会发现加载时间从秒级变为毫秒级,你的网站将比传统的 React 应用感觉更加响应。Gatsby 有很多提高性能的技巧,其中一些我们将在本章末尾涉及。它还可以用几行代码将你的网站转变为渐进式 Web 应用(PWA)——如果这还不够酷,我不知道还有什么更酷的!
重要提示
Gatsby 与 React 之间一个基本的区别是,Gatsby 是一个“框架”,而不是一个“库”。当使用库时,您控制应用程序的流程;您在需要时调用它。然而,当使用框架时,控制权发生了反转。框架要求您遵循它们定义的特定流程和布局。在框架内工作通常被视为一种优势,因为熟悉该框架的任何开发者都知道在哪里找到相关的文件和代码。
我希望您已经开始看到一些 Gatsby 之所以如此强大的原因。现在让我们看看它的实际应用。
Gatsby 用例
您可能开始意识到 Gatsby 可以应用于许多不同类型的网站。自 2017 年 Gatsby v1 版本发布以来,这个框架已经被大小公司以多种不同的方式使用。在这里,我想强调一些 Gatsby 表现优异的用例,并建议为什么公司可能选择 Gatsby 来构建这些网站。
小贴士
虽然在这里阅读这些示例网站很好,但我强烈建议您通过自己的设备访问它们。Gatsby 的最好特性之一是它创建的网站速度,亲自体验这一点对于理解其优势至关重要。
文档网站
文档网站是 Gatsby 的完美用例,因为它们的内容主要是静态的,如果不是全部的话。它们的内容也不经常变动,页面需要不频繁的更新。它们的静态性质意味着我们可以在构建过程中生成所有页面路由并将它们加载到 CDN 上,这意味着当请求页面时,请求几乎是瞬时的。这就是为什么您会看到像官方 React 文档(reactjs.org)这样的网站是用 Gatsby 制作的:

图 1.1 – React 文档网站
由于文档页面的更新频率较低,您可以在文档更新时自动构建和部署您的网站。通过 GitHub 集成或 webhooks,您可以让您的文档网站在每次对主分支或每日进行更改时重新部署。我们将在第九章,“部署和托管”中探讨如何创建这类流程。
在线课程
在线课程通常具有独特的结构——它们的大部分内容都在静态学习模块中,但它们也需要少量经过身份验证的路由,以提供登录用户的体验。
网站如 DesignCode.io (designcode.io/courses) 使用 Gatsby 来处理他们的静态内容,这意味着他们的静态页面性能极优,并且它们在客户端渲染认证路由。虽然这确实会增加包的大小,因为它们需要发送更多的 JavaScript,但快速静态页面的好处远远超过了更重的认证页面的成本:

图 1.2 – DesignCode.io 网站
Gatsby 最受欢迎的数据来源之一是 MDX。MDX 是一种强大的格式,允许你在 Markdown 中编写 JSX。为什么它很棒?因为你可以毫无困难地将 React 组件与文档一起包含。React 组件可以比文本更加互动和动态,因此,它是一种创建在线课程的强大格式,因为你可以创建对用户更具吸引力的内容。也许一个更具互动性的课程更容易让人记住?我们将在 第三章,从任何地方获取和查询数据 中详细探讨 MDX。
SaaS 产品
当在线销售 软件即服务 (SaaS) 时,你网站的性能可以被视为你产品性能的反映。因此,拥有一个笨拙的网站可能是你的产品成功与否的关键。正如之前提到的,这是一个你可以深入挖掘以改善网站性能的例子。例如,Skupos (www.skupos.com/) 使用 Gatsby 免费获得更多性能优势。Gatsby 对 搜索引擎优化 (SEO) 也大有裨益。由于页面是预渲染的,你的所有页面内容都对网络爬虫(如 Googlebot)可用,以便导航到你的网站。速度和 SEO 的改进有助于他们的产品网站脱颖而出,并使用户对他们在技术方面的能力充满信心:

图 1.3 – Skupos 网站
Skupos 还通过元数据和 alt-text 补充了他们的网站页面,这进一步帮助网络爬虫理解网站内容。网络爬虫越了解你的网站内容,你的搜索引擎排名就会越好。
设计机构和图片密集型网站
在你的工作更注重视觉的情况下,你的网站通常需要使用大量的高分辨率图像。我们都访问过网站,等待大图像文件加载时感觉像是回到了拨号上网的时代。这种常见的错误往往在加载图像时发生的累积布局变化中被进一步放大。优雅地处理图像的加载状态以避免这种情况可能会很头疼。
Gatsby 在其应用程序中对图像进行了魔法般的处理。它底层使用sharp库(github.com/lovell/sharp)将你的大图像转换为更小的、适合网页的大小。当你的网站加载时,它将首先加载一个较小的分辨率版本,然后模糊到所需的最高分辨率。这导致没有布局偏移,为网站访客提供了远比“跳跃”体验更少的“跳跃”感。一个很好的例子是在使用 Gatsby 开发的Call Bruno Creative Agency(www.callbruno.com/en/reelevant)网站上:

图 1.4 – Call Bruno Creative Agency 网站
他们在其项目页面中使用了大量的图像,但图像加载并不会让你从体验中脱离出来。我们将在第五章,与图像一起工作中详细介绍处理图像的方法。
通过探索这些网站,我们可以看到 Gatsby 在各个行业中帮助公司超越竞争对手的例子。
Gatsby 的竞争对手
虽然这本书主要关注 Gatsby,但理解它不是市场上唯一的 React 静态站点生成器是至关重要的。最常被提及的竞争对手是 Next.js。
直到最近,Next.js 和 Gatsby 之间的关键区别在于服务器端渲染。与 Gatsby 一样,Next.js 应用程序可以以静态方式托管,但它曾经能够服务器端渲染页面,而 Gatsby 则不能。不是部署静态构建,而是部署一个服务器来处理请求。当请求一个页面时,服务器构建该页面并在发送给用户之前将其缓存。这意味着对资源的后续请求比第一次调用更快。截至版本 4,Gatsby 可以预先构建所有页面为静态,或者它可以创建混合构建——静态和服务器端渲染内容的混合。我们将在第九章,部署和托管中进一步讨论这一点。
Next.js 的一个主要缺点是其数据安全性。当构建 Gatsby 站点为静态构建时,数据仅在构建时从源获取,由于内容是静态的,因此它是安全的。Next.js 将数据存储在服务器上,因此更容易被利用。如果你希望通过服务器或使用数据库来设置 Next.js,通常需要更多的初始化。这也意味着在 Next.js 应用程序中需要更多的维护。Next.js 和 Gatsby 都有额外的实用工具来帮助处理图像。然而,Gatsby 可以使静态渲染页面上的图像性能更优,而 Next.js 则不能。
好消息是,所有静态站点生成器都遵循类似的过程。在这本书中学到的技能和心态可以轻松转移到未来的不同生成器中,如果你决定要切换的话。
现在我们已经了解了 Gatsby 的优势所在,让我们开始创建我们的第一个 Gatsby 项目。
设置项目
为了帮助您将所学知识付诸实践,我们将一起构建一个项目。在整个本书中,我们将致力于构建一个个人作品集,这是每个开发者都需要的东西,因此我认为它对大多数读者都将是相关的。这个作品集将包含博客页面,以帮助您在公众面前学习,项目页面以展示您的工作,一个展示您网站上有趣指标的统计页面,以及许多其他有助于您的作品集脱颖而出的功能。
在整个本书中,您将面临选择。我们将讨论为您的网站提供不同实现方案,以及您可能想要实施的数据源。这应该会为您提供灵活性,以与您当前的知识相匹配。或者,您可以跳入深水区——选择权在您手中。在每一个选择的地方,如果您无法决定,我还会提供我的个人建议,以供参考。
要查看我们将要构建的作品集的完成版本,请访问此链接:
elevating-react-with-gatsby.sld.codes/
小贴士
如果您在某个环节遇到困难,请参考本书附带的代码仓库(github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4)。它包含了每个章节后项目应有的副本。
要开始使用 Gatsby,我们需要确保在我们的机器上设置了一些先决工具。如果您是 React 开发者,那么这些先决条件很可能已经存在于您的设备上,尽管我仍然鼓励您阅读此列表,因为您的某些工具可能需要更新。
Node.js 版本 14.15.0+
截至 4.0 版本,Gatsby 支持所有大于 14.15.0 的 Node.js 版本。您可以通过打开终端窗口并输入以下命令来快速检查您是否已安装 Node.js:
node -v
如果您已安装 Node.js,它应该会打印出一个版本号。然而,如果您收到错误,您可以通过访问 Node.js 网站(nodejs.org)下载 Node.js。Node.js 附带npm,这是一个包仓库、包管理器和命令行工具,我们将使用它来安装 Gatsby。
小贴士
您很可能已经在使用 Node.js,并且您的一些现有项目可能需要与这里指定的要求不同的版本。如果您需要在同一设备上管理多个 Node.js 版本,您应该查看Node.js 版本管理器(NVM)(github.com/nvm-sh/nvm)。它为您提供了宝贵的命令,包括安装新版本和在不同版本之间切换。
Gatsby 命令行界面
Gatsby 命令行界面 (CLI) 是由 Gatsby 核心团队构建的工具;它允许你执行标准功能,例如创建新的 Gatsby 项目、设置本地开发服务器以及构建你的生产网站。虽然你可以按项目使用它,但更常见的是全局安装 CLI,这样你就可以在多个 Gatsby 项目中使用其功能,而无需在每个项目中将其作为包安装 – 这样可以节省硬盘空间!
要全局安装 CLI,使用带有全局标志的 npm install:
npm i -g gatsby-cli
要验证其安装,打开一个终端窗口并输入以下内容:
gatsby --help
如果运行此命令提供命令列表且没有出错,那么你就准备好了。
重要提示
在这本书的整个过程中,我使用 npm 作为我的包管理器。如果你更喜欢 Yarn,你可以使用 Yarn 的等效命令。
目录和包设置
在这里,我们将开始创建我们需要启动项目所需的文件和文件夹,以及安装必要的依赖项,如 React 和 Gatsby。
首先,创建一个文件夹来存放我们的项目。你可以称它为任何你喜欢的名字。在这本书的整个过程中,我将把这个文件夹称为应用程序的 root 文件夹。打开终端并导航到你的 root 文件夹。通过运行以下命令在这个文件夹中初始化一个新的包:
npm init -y
现在包已经初始化,让我们安装 React 和 Gatsby:
npm i gatsby react react-dom
在你最喜欢的编辑器中打开你的 root 文件夹中的 package.json、package-lock.json 和 node-modules 文件夹。打开你的 package.json,你应该会看到以下内容:
{
"name": "gatsby-site",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"gatsby": "⁴.4.0",
"react": "¹⁷.0.2",
"react-dom": "¹⁷.0.2"
}
}
在前面的示例中,你可以看到这个文件现在包含了对我们刚刚安装的依赖项的引用。
开发脚本
让我们先修改 package.json,使其包含一些有用的脚本,这将加快我们的开发过程:
{
"name": "gatsby-site",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "gatsby build",
"develop": "gatsby develop",
"start": "npm run develop",
"serve": "gatsby serve",
"clean": "gatsby clean"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"gatsby": "⁴.4.0",
"react": "¹⁷.0.2",
"react-dom": "¹⁷.0.2"
}
}
让我们分解这些脚本:
-
build: 运行 Gatsby CLI 的build命令。这将创建一个编译好的、生产就绪的网站构建版本。我们将在 第九章,部署和托管 中了解更多关于这一点。 -
develop: 运行 Gatsby CLI 的develop命令。我们将在下一节 创建你的第一个页面 中详细审查它。 -
start:start脚本重定向到develop脚本。这是常见的,因为通常使用start脚本来启动包。 -
serve: 运行 Gatsby CLI 的serve命令以提供 Gatsbybuild文件夹。这是一种有用的方式来审查生产构建。 -
clean:clean脚本使用 Gatsby CLI 的clean命令。这将删除本地 Gatsby 缓存和任何构建数据。它将在下一个develop或build命令中重建。
所有这些脚本都可以通过以下命令在 root 文件夹中运行:
npm run script-name
只需将 script-name 替换为你想要运行的脚本的名称。
你会注意到缺少一个测试脚本。不用担心——我们将在第七章中介绍如何测试 Gatsby 应用程序,测试和审计您的网站。
框架文件和文件夹
如前所述,Gatsby 是一个框架。框架需要存在某些文件才能工作。让我们使用 Gatsby 期望找到它们的文件和文件夹来设置我们的项目。
在您的root目录中创建一个gatsby-config.js文件,并添加以下内容:
module.exports = {
plugins: [],
};
如其名称可能暗示的那样,gatsby-config.js文件是 Gatsby 的核心配置文件。随着我们构建项目,我们将经常回到这个文件。当我们完成时,它将充满插件、元数据、样式,甚至离线支持。
在您的root目录中创建gatsby-browser.js和gatsby-node.js文件。这两个文件现在都可以留空。gatsby-browser.js文件包含我们希望在客户端浏览器上运行的任何代码。在下一章中,我们将使用此文件为我们的网站添加样式。gatsby-node.js文件包含我们希望在构建网站过程中运行的代码。
最后,在您的root目录中创建一个src文件夹。这个文件夹将包含我们大部分的开发工作,就像在传统的 React 应用程序中一样。我们创建的页面和定义的组件都将包含在这个文件夹中。
在我们继续之前,让我们确保我们的版本控制正在跟踪正确的文件。
使用版本控制
我怀疑你们中的许多人希望在构建 Gatsby 网站的同时使用版本控制。为了确保 Git 只跟踪重要的文件,创建一个.gitignore文件并添加以下内容:
node_modules/
.cache/
public
这些行阻止了依赖项、Gatsby 构建和缓存文件夹被跟踪。
创建您的第一个几个页面
我们现在已经设置了所有必要的底层代码,以便我们可以开始创建页面。在本节中,我们将使用 Gatsby 创建一个包含三个页面的网站。重要的是要注意,这是一个基本示例,纯粹是为了在我们担心样式和附加功能之前巩固你对 Gatsby 工作原理的理解。
导航到您的src目录,并创建一个名为pages的新文件夹。我们在pages文件夹中创建的任何 JS 文件都将被 Gatsby 视为一个路由。这也适用于pages文件夹内的子文件夹。然而,有一个例外——名为index.js的文件被视为其目录的根。让我们通过几个示例来理解这一点:
-
src/pages/index.js将映射到你的网站.com。 -
src/pages/about.js将映射到你的网站.com/about。 -
src/pages/blog/my-first-post.js将映射到 yourwebsite.com/docs/my-first-post。虽然我们现在不会在这个 URL 上设置页面,但我们将开始使用此类路由,例如在 第三章,从任何地方获取和查询数据。 -
src/pages/404.js将映射到 yourwebsite.com 上的任何页面。重要提示
你放置在
pages文件夹中的任何 React 组件都将成为你网站上的可导航路由。因此,最好将你的组件与你的页面分开。一个常见的模式是在src目录中创建一个与pages文件夹相邻的components文件夹,并导入你想要在页面中使用组件。
首页
在你的 pages 文件夹中创建一个 index.js 文件。作为 pages 文件夹的索引,这将成为你网站的首页。现在我们可以用以下代码填充此文件:
import React from "react"
const Index = () => {
return (
<div>
<h1>My Landing Page</h1>
<p>This is my landing page.</p>
</div>
)
}
export default Index
此文件的 内容应该看起来很熟悉;它只是一个简单的无状态 ReactJS 组件。
我们也可以将其定义为:
import React from "react"
export default function Index(){
return (
<div>
<h1>My Landing Page</h1>
<p>This is my landing page.</p>
</div>
)
}
这两个示例将输出完全相同的结果,所以这只是个人喜好。
关于页面
以类似的方式,我们可以创建一个 about 页面。在这里,你有选择权——你可以在 src/pages/about.js 或 src/pages/about/index.js 中创建这个页面。我在决定选择哪个选项时总是问自己,这个页面是否会有子页面。在 about 页面的情况下,我认为它不太可能包含任何子页面,所以我将选择 src/pages/about.js:
import React from "react"
export default function About(){
return (
<div>
<h1>My About Page</h1>
<p>This is a sentence about me.</p>
</div>
)
}
在这里,我们定义了另一个包含标题和段落的简单 React 组件,以创建我们的 about 页面。
404 页面
Gatsby 期望在 pages 目录中找到一个 404.js 文件。这个页面是特殊的。它包含当 Gatsby 找不到请求的页面时将显示的页面。我相信你之前一定遇到过“页面未找到”的页面。如果没有这个页面,在请求一个不存在的路由时,浏览器将找不到任何资源并向用户显示浏览器错误。虽然 404 页面是显示相同错误的一种另一种形式,但通过创建这个页面,我们可以自己管理错误。我们可以链接到我们网站上的工作页面,甚至建议他们可能试图访问的页面(更多内容请参阅 第三章,从任何地方获取和查询数据)。
现在让我们在 src/pages/404.js 中创建我们的 404 页面:
import React from "react"
export default function NotFound(){
return (
<div>
<h1>Oh no!</h1>
<p>The page you were looking for does not
exist.</p>
</div>
)
}
你应该开始看到一种模式。创建页面就像定义 React 组件一样简单——这应该是你已经熟悉的。
尝试运行 develop 命令
到目前为止,你实际上已经创建了一个完全工作的网站。恭喜!为了测试它,在你的 root 目录中打开一个终端,并运行以下命令:
npm run start
如您从我们的 package.json 中回忆的那样,这将运行 gatsby develop 命令。这可能需要几秒钟的时间来运行,但您应该会看到一些类似以下内容的终端输出:
You can now view gatsby-site in the browser.
http://localhost:8000/
您现在可以打开您选择的浏览器并导航到 http://localhost:8000/,您应该会看到类似以下内容:

图 1.5 – 着陆页预览
这是我们的 index.js 页面组件的渲染版本。您可以在浏览器中修改 URL 到 http://localhost:8000/about 以查看您的 about 页面,以及到 http://localhost:8000/404 以查看您的 404 页面。您还可以通过导航到任何无效路由并按下预览自定义 404 页面按钮在开发中查看您的 404 页面。
提示
如果您不想手动导航到浏览器并输入 URL,您可以通过将 -o 选项附加到 gatsby develop 命令来修改我们的脚本。这会指示 Gatsby 在您运行 develop 命令时自动打开默认浏览器并导航到网站。
Gatsby 详细开发
运行 gatsby develop 启动 Gatsby 开发服务器。这可能有点令人困惑,因为我们之前提到 Gatsby 网站作为静态内容交付,但实际上它是为了加快您的开发过程。
想象一下,如果您的网站包含 10,000 页面;每次对一页进行小改动时,都需要构建整个网站,这将花费很长时间。为了在开发中解决这个问题,Gatsby 使用 Node.js 服务器按需构建您所需的内容。由于它是按需构建的,可能会对页面的性能产生负面影响,因此您绝对不应该因为这种原因在开发中对页面的性能进行测试。
一旦服务器启动,您就可以继续编辑您的代码,而无需重新运行命令。开发服务器支持热重载,这是一个您应该熟悉的概念。
develop 命令具有许多内置选项,允许您对其进行自定义:
-
-H, --host: 允许您修改主机 -
-p, --port: 允许您修改 Gatsby 运行的端口 -
-o, --open: 在浏览器中打开您的项目 -
-S, --https: 启用 HTTPS
您可以使用主机选项在连接到同一网络的任何设备上查看您的网站。这可能在您想比较您的网站在移动浏览器上的行为与桌面体验时很有用。要实现这一点,请运行以下命令:
gatsby develop -H 0.0.0.0
如果命令成功执行,您将在输出中看到一些细微的差异:
You can now view gatsby-site in the browser.
Local: http://localhost:8000/
On Your Network: http://192.168.1.14:8000/
develop 命令为您的网络测试添加了一个 URL。在连接到同一网络的任何设备的浏览器中输入此 URL,将渲染您的网站。
连接您的页面
现在您有多个页面,您可能想要在它们之间进行导航。有两种不同的方法可以实现这一点——使用 Gatsby Link 组件或通过编程式导航。对于一些人来说,这些组件和函数可能听起来很熟悉;这是因为 Gatsby 对 reach-router (reach.tech/router) 库进行了封装。对于那些之前没有使用过 reach-router 的人来说,该库内置了服务器端渲染和路由无障碍功能的支持。Gatsby 在此基础上进行了增强,以满足其对用户无障碍的高标准,确保无论您是谁,都能获得良好的网站体验。
Gatsby Link 组件
在您链接到内部页面时,使用 Gatsby 的 <Link/> 组件替换 <a/> 标签是很重要的。<Link/> 组件的工作方式与 <a/> 标签类似,但有一个重要的区别——它支持预取。预取是指在需要之前加载资源的行为。这意味着当请求资源时,等待该资源的时间会减少。通过预取您页面上的链接,您的下一次点击将导航到已经加载的内容,因此实际上几乎是瞬间的。这在网络条件较差的移动设备上尤为明显,通常在加载页面时会有延迟。
您可以在 404 页面添加 Link 组件的第一个地方。这些页面通常有一个按钮,上面写着“带我回家”之类的文字,当点击时,会导航到主页:
import React from "react"
import {Link} from "gatsby"
export default function NotFound(){
return (
<div>
<h1>Oh no!</h1>
<p>The page you were looking for does not
exist.</p>
<Link to="/">Take me home</Link>
</div>
)
}
如您在前面的代码块中所见,Link 组件有一个名为 to 的属性;这需要传递到您想要导航到的相对于您网站根目录的页面。通过传递 "/" 属性,Gatsby 将导航到您网站的根目录。
您还可以从 index 页面添加到 about 页面的链接:
import React from "react"
import {Link} from "gatsby"
export default function Index() => {
return (
<div>
<h1>My Landing Page</h1>
<p>This is my landing page.</p>
<Link to="/about">About Me</Link>
</div>
)
}
您可以看到,我们在这里将 "/about" 传递给 <Link/> 组件中的 to 属性;这将导航到我们之前创建的 about 页面。
编程式导航
有时,您可能需要用除了点击之外的方式触发导航。也许您需要根据 fetch 请求或当用户提交表单时进行导航。您可以通过使用 Gatsby 的 navigate 函数来实现这种行为:
import React from "react"
import {navigate} from "gatsby"
export default function SomePage() => {
const triggerNavigation = () => {
navigate('/about')
}
return (
<div>
<p>Triggering page navigation via onClick.</p>
<button onClick={()=> triggerNavigation()}>
About Me
</button>
</div>
)
}
与 <Link/> 组件一样,navigate 函数也仅适用于导航到内部页面。
我们现在已经设置了一个基本的 Gatsby 网站,具有在页面之间导航的能力。
摘要
我很欣赏本章的大部分内容都是理论性的,但理解“为什么”同样重要,就像理解“如何”一样。在本章中,我们巩固了盖茨比是什么以及我们在后续章节中构建网站时将使用的指导原则的基础知识。我们看到了盖茨比被使用的例子以及它能带来的好处。我们讨论了你需要的依赖项以及如何初始化盖茨比项目。我们还设置了一个完整的盖茨比基本项目,并创建了我们的网站的前几页。然后,我们使用了内置的盖茨比组件和函数来链接我们的页面。
我们将在整本书中引用本章概述的理论,但就目前而言,让我们将注意力转向美化我们的 Web 应用程序。在下一章中,我们将确定不同的样式化方法,并为你选择一个适合你项目的明智选择。
第二章: 样式选择和创建可复用布局
Gatsby 站点可以通过多种方式进行样式化。在本章中,我们将向您介绍大量样式化技术,以帮助您做出明智的选择,决定您希望如何样式化您的站点。一旦您确定了样式化方法,我们将在第一章,Gatsby.js 入门概述中实现的页面上实施它,然后再创建将在所有我们的站点页面上使用的可复用组件。
在本章中,我们将涵盖以下主题:
-
Gatsby 中的样式化
-
使用 CSS 进行样式化
-
使用 Sass 进行样式化
-
使用 Tailwind.css 进行样式化
-
使用 Styled 组件进行样式化
-
创建一个可复用布局
技术要求
为了导航本章,您需要完成 Gatsby 的设置并创建了第一章,Gatsby.js 入门概述中的页面。
在本章中,我们将开始将我们的第一个可复用组件添加到我们的页面中。由于这些组件不是独立的页面,我们需要一个新的地方来存储它们。在您的src文件夹内创建一个名为components的子文件夹,我们可以使用它。
本章中出现的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter02找到。
Gatsby 中的样式化
本章全部关于样式化您的 Gatsby 站点,但样式化指的是什么?虽然我们的 React 代码定义了我们的网络文档的结构,但我们将使用样式化通过页面布局、颜色和字体来定义文档的外观和感觉。您可以使用大量工具来样式化任何 Gatsby 项目。在这本书中,我将向您介绍四种不同的方法——纯 CSS、Sass、Tailwind.css和CSS in JS。在决定使用哪种方法之前,让我们更详细地探讨每一种。
纯 CSS
当您的浏览器导航到站点时,它会加载站点的 HTML。它将此 HTML 转换为文档对象模型(DOM)。在此之后,浏览器将开始获取 HTML 中引用的资源。这包括图片、视频,更重要的是,现在的 CSS。浏览器读取 CSS,并按元素、类和标识符对选择器进行排序。然后它遍历 DOM,并使用选择器将样式附加到所需的元素上,创建一个渲染树。然后,利用这个渲染树,将可视页面显示在屏幕上。CSS 已经经受住了时间的考验,因为我们已经以这种方式将 CSS 与 HTML 一起使用了 25 年。但使用纯 CSS 有一些优点和缺点。
使用纯 CSS 的优点如下:
-
其年龄:由于 CSS 在撰写本书时已经存在了 25 年,因此有关 CSS 的内容非常丰富。由于其年龄,有人已经解决了您遇到的任何问题的可能性也非常高。这两个原因都使纯 CSS 成为初学者的一个很好的选择。
-
可理解的语法:构成 CSS 的语法非常少使用缩写。对于初学者来说,阅读 CSS 的每一行所做的工作,比本章中其他样式实现要容易得多。
使用纯 CSS 的缺点如下:
-
长样式表:在传统的网站上,您经常看到它们只包含一个 CSS 文件。这使得维护和组织样式变得非常困难,因为文件可能会变得非常长。这可能导致一种模式,即懒惰的开发者找不到他们想要的样式,可能会直接将它们附加到文件的底部(也称为只读样式表)。如果他们这样做,而文件已经存在,那么他们只是在增加文件大小而没有其他作用。
-
类重用混淆:重用样式有时可能会带来比其价值更大的麻烦。假设您已经在应用程序的各个元素中使用了某个特定的类。您可能更新这个类以使其适应一个实例,结果却破坏了所有其他实例。如果您陷入这种循环多次,这可能会真正减慢您的开发速度。通过一点前瞻性思维可以避免这种情况——与其重用类,不如创建可重用的组件。另一个选择是创建不太可能改变的“实用类”;如果您不想自己创建这些类,您应该阅读关于 Tailwind CSS 的部分。
-
继承的痛点:通过使用继承,我们最终将 CSS 紧密耦合到 HTML 的结构中。如果您破坏了这个结构,CSS 可能就不再起作用了。虽然继承有时是不可避免的,但我们应尽量将其保持在最低限度。
CSS 经受了时间的考验,至今仍是一个可靠的选择。您可能会问,为什么我列出了绕过/避免所有这些方法的方式,这些缺点仍然是缺点。这些缺点都可以通过本章中其他实现方式中的任何一种来修复。
Sass
Sass 是一种预处理器脚本语言,它编译成 CSS。它为开发者提供了工具,使他们能够创建更高效的 CSS。
使用 Sass 的优点如下:
-
大型工具集:Sass 包含了一组强大的工具,您在纯 CSS 中无法使用。虽然我们不会详细讨论这些工具,但它们包括混入、循环、函数和导入等工具,可以用来为您的应用程序创建更强大和高效的 CSS。这对于 Sass 来说是一个巨大的优点。
-
.scss文件用于分解文件。然后您可以根据需要将它们相互导入。这极大地帮助提高了您代码的组织性。 -
自由度:Sass 强制执行一种编写方式——你可以选择。这意味着你可以选择一种适合你团队的风格。
使用 Sass 的缺点如下:
-
跳过基础:对于刚开始学习样式的开发者来说,自由度也可能是一个缺点。如果你之前没有使用过 Sass,你可能会创建出工作但过于复杂的代码。这可能会导致未来的开发者难以理解代码。具体的 CSS 指南可以帮助避免这种误用。
-
命名规范:为每个你设计的元素创建类名是一个繁琐的过程。有方法可以帮助你创建有意义的类名;然而,这仍然需要很长时间。
-
两个事实来源:当你编写 HTML 时,你可能会也为你元素添加类名以进行样式化。然后你跳转到 Sass 文件来添加这些类名,但可能会忘记它们的名称。在 HTML 和 Sass 之间跳转可能会是一个烦人的上下文切换。你可能会认为将样式从你的标记中抽象出来是一个好主意,但当标记和样式如此紧密相连时,这可能会是一个不便。
尽管 Sass 是一个强大的工具,但增加的力量也意味着增加的复杂性。虽然对于初学者来说学习曲线可能更高,但掌握它将给你带来极大的自由度。
Tailwind(以实用类为首要的 CSS 框架)
Tailwind CSS 是一个以实用类为首要的 CSS 框架。这种“以实用类为首要”的方法是为了对抗我们之前讨论的 CSS 和 Sass 的缺点。在这个方法中,我们使用小的实用类来构建组件的样式,而不是定义我们自己的类名。这可能会感觉有点像编写内联样式,因为你的元素将添加一串实用类,但好处是如果你不想写,你不需要写一行自己的 CSS。
使用 Tailwind 的优点如下:
-
单一事实来源:当使用 CSS 或 Sass 时,你必须在这两个文件之间切换:你的标记和你的样式表。Tailwind 消除了这个概念,并允许你直接在你的标记中嵌入样式。
-
命名规范:Tailwind 消除了你需要创建自己类名的需求。它有一套非常细粒度的类名,称为“实用类”。你使用这些类来构建元素的样式,而无需担心为每个组件创建唯一的类名。
-
更小的 CSS:Tailwind 为你提供了一套完整的实用类,你很少需要用你自己的样式来补充。因此,你的 CSS 不再增加;事实上,它变得更小了。当你准备好生产构建你的应用程序时,你可以使用 Tailwind 内置的清除功能来删除未使用的类。
-
无副作用:因为我们是在我们的标记中添加样式而不是操作底层类名,所以在我们的应用程序的其他地方永远不会出现任何意外的副作用。
使用 Tailwind 的缺点如下:
-
标记可读性:由于你的标记包含从实用工具构建的样式,元素的类名可能会变得非常长。当你考虑到这些类名可能需要在悬停或断点时改变,你的行长度可能会变得非常长。
-
学习曲线:实用工具优先需要你学习许多类名来了解你必须使用哪些工具来构建样式。这种学习可能需要一些时间,并在开始时减慢你的速度,但我相信一旦你掌握了这些,你的开发速度将会大大提高。
Tailwind 在抽象和灵活性之间达到了很好的平衡。这是列表中最新的实现,也是我个人最喜欢的。
CSS in JS
CSS in JS 允许你在组件内编写纯 CSS,同时避免了与类名发生命名的冲突。为了探索这个选项,我将审查最受欢迎的解决方案——样式组件(styled-components.com)。然而,值得一提的是,有许多不同的 CSS in JS 解决方案,包括 Emotion (emotion.sh) 和 JSS (cssinjs.org)。
使用样式组件的优点如下:
-
单一的真实来源:与 Tailwind 一样,样式组件也消除了上下文切换,因为你的 CSS 代码存储在与使用它的组件相同的文件中。
-
样式与组件绑定:样式是为使用特定组件而创建的,并且位于实现它们的标记旁边。因此,你知道哪些样式被使用,更重要的是,你知道编辑这些样式只会影响与它们一起定位的标记。
-
JS 在 CSS 中:我们可以在 CSS 中使用 JS 来确定样式。这使得在样式内处理条件变得更容易,因为我们不需要创建两个不同的类名并使用三元运算符。
-
扩展:通常情况下,你可能想使用一个组件样式,但为了不同的用例微妙地修改它。而不是再次复制样式并从头开始创建一个新的组件,我们可以创建一个继承另一个组件样式的组件。
使用样式组件的缺点如下:
index.html。在所有页面中使用的样式会在每个页面上被拉入,而且没有简单的方法来轻松地将它们分开。即使缓存样式也很困难,因为类名是动态生成的,因此可能在构建之间发生变化。
如果你喜欢单一的真实来源,当将所有内容合并到一个文件中时,样式组件可以提高你的标记的可读性。虽然性能被列为缺点,但这是样式组件背后的社区正在努力改进的事情。
选择样式工具
当涉及到样式化你的 Gatsby 网站,没有正确或错误的方式。这完全取决于你现有的技能集,你希望你的样式和 JS 多么紧密耦合,以及你个人的偏好。我想以查看一些常见场景及其对应的样式实现方式来结束本节:
-
我的样式经验有限:如果你是样式应用的初学者,我建议使用纯 CSS。你通过这种实现方式学到的基本知识是其他所有实现方式的基础。通过学习基础知识,你将能够在未来更容易地选择另一种实现方式。
-
我不想花太多时间在样式上:如果你在寻找最少设置的选项,那么 Tailwind 就是你的不二选择。使用实用类可以节省你大量时间,因为你不需要创建自己的类。
-
我不喜欢切换上下文:在这种情况下,我会倾向于使用 Styled Components 或 Tailwind,因为在两种实现中,你的样式都位于你的标记旁边——一个文件和一个单一的事实来源。
-
我已经使用过 CSS,并想在此基础上构建:使用 Sass 将是一个很好的选择,因为你可以编写你熟悉和喜爱的 CSS,同时也可以通过 Sass 工具集来增强它。
到目前为止,你应该已经准备好做出明智的选择,决定哪种样式工具适合你。我强烈建议你只实现本章中概述的样式选择之一,而不是尝试混合搭配。如果你添加了多个样式实现,你的网站样式可能会显得不一致。这是因为一个实现可以覆盖另一个。通过坚持一种方法,你将获得额外的优势,即保持网站风格的一致性,这对于强化品牌非常重要。
既然你已经做出了决定,让我们开始查看实现方式。
使用 CSS 进行样式化
在本节中,我们将学习如何将 CSS 样式应用到我们的 Gatsby 项目中。
向我们的 Gatsby 网站添加全局 CSS 样式有两种不同的方法——创建包装组件或使用 gatsby-browser.js。
创建包装组件
包装组件背后的想法是将我们的页面组件包裹在另一个组件中,这个组件将常见的样式带到页面上:
-
在你的
components文件夹中创建StyleWrapper.css:html { background-color: #f9fafb; font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; }在前面的代码中,我们正在定义一个背景颜色和字体家族,所有 HTML 标签的子元素都可以继承。
-
现在我们来添加一些
h1样式到这个文件中:h1 { color: #2563eb; size: 6rem; font-weight: 800; }在这里,我们正在添加最大
heading标签的颜色、大小和权重。 -
同样,我们也可以为
p和a标签添加一些样式:p { color: #333333; } a { color: #059669; text-decoration: underline; }在这里,我们为每个标签添加颜色,在
a标签的情况下,添加下划线以使其更加突出。 -
在你的
components文件夹中创建StyleWrapper.js:import React from "react" import "./StyleWrapper.css" const StyleWrapper = ({children}) => ( <React.Fragment>{children}</ React.Fragment> ) export default StyleWrapper如其名称所示,我们将使用此组件来包裹我们的页面,以应用我们在第二行导入的样式。
-
为了使用
StyleWrapper.js,我们需要将其导入到我们的页面中;让我们以pages/index.js为例:import React from "react" import {Link} from "gatsby" div wrapping with our new layout component. The contained h1, p, and Link elements will be passed into the StyleWrapper component as children.
使用gatsby-browser.js
如果你希望相同的样式应用于每个页面,你可能会觉得在所有页面实例上导入StyleWrapper并不符合不要重复自己(DRY)原则。在确定样式确实需要在每个页面上使用的情况下,我们可以使用 Gatsby 浏览器将它们添加到我们的应用程序中:
-
在你的
src目录中创建一个styles文件夹。由于这些样式是全局使用的,并且与特定组件无关,因此将它们存储在component目录中是没有意义的,正如我们在实现StyleWrapper.js时所做的。 -
在你的
styles文件夹中创建一个global.css文件,并添加以下内容:html { background-color: #f9fafb; font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; } h1 { color: #2563eb; size: 6rem; font-weight: 800; } p { color: #333333; } a { color: #059669; text-decoration: underline; }在这里,我们添加了与替代 CSS 实现中完全相同的样式,因此我不会在这里再次解释它们。关键区别在于接下来的步骤。
-
导航到
gatsby-browser.js并添加以下内容:import "./src/styles/global.css"通过在
gatsby-browser.js中导入我们的 CSS,Gatsby 将为每个页面包裹这个 CSS。
验证我们的实现
无论你选择了哪种方法,如果一切按计划进行,你应该会看到一个样式化的网站,看起来像这样:

图 2.1 – 带样式的索引页开发
你应该能够在这个页面上找到你的 CSS 添加内容。
你现在已经在你的 Gatsby 网站上实现了 CSS 作为样式工具。你可以忽略后续的其他样式实现,并继续到创建可重用布局部分。
使用 Sass 进行样式化
在本节中,我们将学习如何在我们的 Gatsby 项目中实现 Sass 样式:
-
要开始使用 Sass,我们需要安装它以及一些其他依赖项。在你的项目根目录中打开一个终端,并运行以下命令:
npm install sass gatsby-plugin-sass在这里,我们正在安装核心 Sass 依赖项以及与之集成的 Gatsby 插件。
-
使用以下内容修改你的
gatsby-config.js文件:module.exports = { plugins: [ 'gatsby-plugin-sass' ], };在这里,我们正在更新我们的 Gatsby 配置,让 Gatsby 知道要使用
gatsby-plugin-sass插件。现在,在你的src目录中创建一个styles文件夹。 -
在你的
styles文件夹中创建一个global.scss文件,并添加以下内容:html { background-color: #f9fafb; font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; }我很少在
global.scss文件中添加超过 HTML 样式。相反,我更喜欢将其他.scss文件导入到这个文件中。这有助于保持样式有序,并使文件保持小而可读。例如,让我们创建typography.scss来存储一些排版样式:h1 { color: #2563eb; size: 6rem; font-weight: 800; } p { color: #333333; } a { color: #059669; text-decoration: underline; } -
在这里,我们为每个元素添加了颜色,对于
a标签,我们还添加了下划线以使其更加突出。现在我们可以将此文件导入到我们的global.scss文件中:@import './typography; html { background-color: #f9fafb; font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; -
导航到你的
gatsby-browser.js文件并添加以下内容:import "./src/styles/global.scss";这告诉我们的 Gatsby 应用程序在客户端包含这个样式表,使我们能够在应用程序中使用它。
你现在已经在你的 Gatsby 网站上实现了 Sass 作为样式工具。你可以忽略后续的其他样式实现,并继续到创建可重用布局部分。
使用 Tailwind.css 进行样式设计
在本节中,我们将学习如何在我们的 Gatsby 项目中实现 Tailwind 样式:
-
要开始使用 Tailwind,我们需要安装它以及一些其他依赖项。在你的项目根目录下打开一个终端并运行以下命令:
npm install postcss gatsby-plugin-postcss tailwindcss在这里,我们正在安装 PostCSS、其关联的 Gatsby 插件以及
tailwindcss。PostCSS 是一个使用 JS 插件转换样式的工具。这些插件可以检查 CSS 的语法、支持变量和混入、转译未来的 CSS 语法、内联图片等等。在 Tailwind 的情况下,有一个特定的 Tailwind 插件用于 PostCSS,我们将要实现它。 -
使用以下内容修改你的
gatsby-config.js:module.exports = { plugins: [ 'gatsby-plugin-postcss' ], };在这里,我们正在更新我们的 Gatsby 配置,让它知道要使用 Gatsby PostCSS 插件。
-
为了使用 PostCSS,它需要在项目根目录下存在
postcss.config.js文件。现在就创建这个文件并添加以下内容:module.exports = () => ({ plugins: [require("tailwindcss")], });在这个文件中,我们告诉 PostCSS 使用我们新安装的
tailwindcss包。 -
与 PostCSS 类似,Tailwind 也需要一个配置文件。Tailwind 有一个内置的脚本用于创建默认配置。打开一个终端并运行以下命令:
npx tailwindcss init如果这个命令成功执行,你应该会注意到在项目根目录下创建了一个新的
tailwind.config.js文件。这个文件内的默认配置将正常工作,所以目前我们不需要编辑它。 -
在你的
src目录内创建一个styles文件夹。 -
在你的
styles文件夹内创建一个global.css文件并添加以下内容:@tailwind base; @tailwind components; @tailwind utilities; -
将以下内容添加到
gatsby-browser.js文件中:import "./src/styles/global.css";这告诉我们的 Gatsby 应用程序在客户端包含这个样式表,使我们能够使用 Tailwind 类。
完成这些步骤后,我们现在已经为在应用程序中使用 Tailwind 做好了准备。为了使用 Tailwind 的实用类,我们可以在组件中使用 React 的className属性;例如,在pages/index.js中,我们可以添加以下内容:
import React from "react"
import {Link} from "gatsby"
export default function Index(){
return (
<div>
<h1 className="text-3xl font-bold text-blue-
600">My Landing Page</h1>
<p>This is my landing page.</p>
<Link to="/about">About me</Link>
</div>
)
}
在前面的代码中,我们使用以下实用类修改了标题的样式:
-
text-3xl:将文本设置为第三大额外大号,相当于 1.875 rem。 -
font-bold:将文本设置为粗体字体重量。 -
text-blue-600:将文本颜色设置为蓝色。
你可以另外将样式追加到我们创建的global.css文件中,以便它们被包含:
@tailwind base;
@tailwind components;
@tailwind utilities;
h1 {
@apply text-3xl font-bold text-blue-600;
}
这里,你会看到完全相同的样式,只是定义在全局。两者都将等同于h1标签上的相同样式;决定使用哪种变体完全取决于频率。如果你打算多次使用这个h1样式,你应该将其合并到你的 CSS 中,以避免重复编写相同的样式。
让我们再补充一些样式:
@tailwind base;
@tailwind components;
@tailwind utilities;
h1 {
@apply text-3xl font-bold text-blue-600;
}
p {
@apply text-gray-800;
}
a {
@apply text-green-600 underline;
}
在这里,我们为每个元素添加一个颜色,对于a标签,添加下划线以使其更加突出。
你现在已经在你的 Gatsby 网站上实现了 Tailwind 作为样式工具。你可以忽略接下来的其他样式实现,并继续到创建一个可重用的布局部分。
使用 styled-components 进行样式化
在本节中,我们将学习如何在我们的 Gatsby 项目中实现 Styled Components 作为样式工具:
-
在你的项目根目录打开终端并运行以下命令来安装你的依赖项:
npm install gatsby-plugin-styled-components styled- components babel-plugin-styled-components这些是依赖项的详细信息:
-
styled-components:Styled Components 库 -
gatsby-plugin-styled-components:Styled Components 的官方 Gatsby 插件 -
babel-plugin-styled-components:在构建之间提供一致的哈希类名
-
-
使用以下内容更新你的
gatsby-config.js:module.exports = { plugins: ['gatsby-plugin-styled-components'], }这指示 Gatsby 使用我们刚刚安装的 Styled Components 插件。
我们可以拥有所有必要的组件来在页面/组件级别和全局级别创建样式。
-
为了演示如何使用它们,导航到你的
pages/index.js文件并添加以下内容:import React from "react" import {Link} from "gatsby" div tag. We can see that it also has styles for any h1 or p tag that are children. -
有时,你可能想要全局创建样式;为了演示这一点,请导航到你的
gatsby-browser.js文件并添加以下内容:import React from "react" import { createGlobalStyle } from "styled-components" const GlobalStyle = createGlobalStyle' body { background-color: ${props => (props.theme === "blue" ? "blue" : "white")}; } ' export const wrapPageElement = ({ element }) => ( <> <GlobalStyle theme="blue"/> {element} </> )我们使用
styled-components的createGlobalStyle辅助函数来创建全局样式。这阻止了 Styled Components 被限制在本地 CSS 类中。通过使用
wrapPageElement方法,我们告诉 Gatsby 将每个页面包裹在组件中。我们可以利用这一点来将每个页面包裹在我们的全局样式中。
无论你的实现选择如何,你现在都应该有了开始构建一个完全样式化的网站的基本知识。现在让我们开始创建一个可重用的布局,我们将在整个网站上使用它。
创建一个可重用的布局
大多数网站都有头部和页脚,这些在所有页面上都存在。根据我们对页面工作原理的了解,你可能会想将头部组件导入到每个页面组件中。但是等等——当你突然需要向该组件传递一个新属性时会发生什么?这类情况正是为什么减少页面之间的任何重复是一个好主意的原因。相反,创建一个包含头部和页脚的布局组件,然后我们可以将其包裹在我们的页面中,这会是一个更好的选择。
为了使我们的components文件夹结构良好,创建子文件夹来存放网站的各个部分是有用的。在components文件夹中创建一个layout文件夹来存放与布局相关的组件。我们将使用这些布局组件跨越所有页面文件。现在,让我们用标题、页脚和布局组件来填充这个文件夹。
重要提示
在本节中的代码示例中,您会注意到我正在使用Tailwind.css来为我的组件添加样式。在配套的 GitHub 仓库(github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main)中,您可以找到使用本章中涵盖的所有样式实现这些组件的实现。在未来的章节中,我将坚持使用 Tailwind。
网站页眉
页眉组件作为我们网站的锚点。在所有页面上包含您的网站页眉是很常见的,这样访客就会记得他们是在您的网站上。
要开始,让我们在我们的components文件夹中创建一个Header.js组件:
import React from "react"
const Header = () => (
<header>
<p>Site Header</p>
</header>
)
export default Header
在前面的代码中,我们创建了最基本的标题示例。请注意,我们正在使用 HTML 的header标签。正如我们将在第六章中学习到的,提高网站搜索引擎优化,在创建内容时使用正确的标签非常重要,因为它有助于网络爬虫和辅助工具理解您的网站。
网站页脚
在您的网站上添加页脚可以是一个强大的工具。我喜欢将其视为在用户完成页面后保持用户参与度的一种方式。我们可以用它提供快速链接到我们的社交媒体,以便他们能够联系到我们,我们可以建议他们可能喜欢的其他有趣内容,我们甚至可以告诉他们当前页面有多少次浏览。
让我们从基本实现开始。在components文件夹中创建一个Footer.js组件:
import React from "react"
const Footer = () => (
<footer>
<p>Site Footer</p>
</footer>
)
export default Footer
就像我们的Header一样,使用正确的 HTML footer标签是很重要的。
布局组件
我们可以直接将我们的页眉和页脚导入到我们创建的每个页面中,但如果我们这样做,会导致大量的重复。一种常见的解决方法是创建一个Layout组件。我们将每个构建的页面都包裹在这个组件中。这不仅是一个引入我们的页眉和页脚的简单方法,而且还可以让我们以最小的努力为每个页面的主要内容进行样式设计:
import React from "react"
import Footer from "./Footer"
import Header from "./Header"
const Layout = ({children}) => (
<div>
<Header/>
<main>
{children}
</main>
<Footer/>
</div>
)
export default Layout
在这里,您可以看到我正在导入我们新创建的Header和Footer组件。我正在使用children属性并在main块内渲染子内容。
为了演示使用Layout组件,让我们将我们的索引页面包裹在这个组件中。修改页面中的index.js,如下所示:
import React from "react"
import {Link} from "gatsby"
import Layout from "../components/layout/Layout"
export default function Index(){
return (
<Layout>
<h1>My Landing Page</h1>
<p>This is my landing page.</p>
<Link to="/about">About me</Link>
</Layout>
)
}
你可以看到我已经将着陆页包裹在我们的新Layout组件中,该组件已在第三行导入。如果你此时启动gatsby develop,你应该会看到你的页面内容,在其前面有一个页眉,在其后面有一个页脚。你现在可以继续将其他页面包裹在你的layout组件中。在继续之前,让我们暂时退后一步,看看我们如何组织为我们的页面创建的组件。
小贴士
之前讨论的一些样式实现使用了样式包装器。如果你的实现使用了样式包装器,请将其导入到你的layout组件中,并用此组件包裹内容。这样,你只需在一个组件中包裹你的页面,而不是layout和style wrapper组件。
使用原子设计进行组织
随着你的网站扩展,尝试保持你的components文件夹的结构是很重要的。一种常用的方法是使用原子设计原则。原子设计是通过将网站元素分解为原子、分子、生物体、模板和页面来创建有效的界面设计系统的过程:
-
原子:这些是我们网站可能包含的最小组件,例如按钮、排版组件或文本输入。在保持其功能的同时,原子不能逻辑上分解成子组件。
-
分子:由两个或更多原子组成,分子是协同工作以提供某些功能的小组元素。一个由文本输入和按钮组成的搜索框可以被视为一个分子。
-
生物体:由一组分子和原子组成,这些形成界面的大块区域,例如网站的英雄部分。
-
模板:这些将生物体包裹在布局中,并提供页面内容和骨骼结构。
-
页面:一个具有实际内容的模板实例。
在构建组件时使用原子设计允许你将组件分解成更小的自包含单元。这些单元可以在导入到应用程序之前单独测试和开发,这既允许更严格的开发过程,也减少了在执行前端开发时对后端逻辑的依赖。
一旦我们定义了我们的原子设计模式,我们在处理样式时就可以更加灵活。更改原子的样式也将更新任何分子和生物体所使用的样式。
根据你的样式实现方式,抽象常用标记,如品牌颜色、间距规则和字体族,也是一个好主意。与在应用程序的各个地方粘贴十六进制值来修改品牌颜色相比,维护一个单一的真实来源的项目要容易得多。
使用原子设计来组织你的components文件夹,当规模扩大时,这真的很有帮助,所以请记住在未来的章节中,随着你的应用程序的扩展。
摘要
在本章中,你学习了如何以多种方式样式化 Gatsby 网站。这应该有助于你做出明智的选择,决定你将如何继续样式化你的应用程序。我们看到了如何使用 CSS、Sass、Tailwind.css 和 Styled Components 来样式化你的 Gatsby 网站。你应该已经决定使用其中之一并实施它。在未来的章节中,我将使用 Tailwind.css 来样式化应用程序,但这只是个人偏好。你应该使用你认为最适合你的网站和现有知识的方法。
我们还开始创建将构成我们网站骨架的第一个可重用组件。虽然我们的 layout 组件现在可能看起来很原始,但我们将在下一章将其与内容集成,并添加图像以进一步在 第五章,与图像一起工作 中使其生动起来。
在继续到下一章之前,我鼓励你花时间基于这里概述的样式进行构建,直到你的现有页面看起来是你想要的样子。虽然我认为定义你自己的样式是最好的,但你可以在代码仓库中的 Tailwind.css 中找到一个完全样式化的网站示例。
在下一章中,我们将开始从本地文件、CMS 和 API 中获取内容。我们将使用这些数据在 Gatsby 网站上程序化地创建页面。
第三章:获取和查询数据(来自任何地方!)
在本章中,你将了解 Gatsby 的数据层。你将从理解 Gatsby 上下文中的数据含义开始,然后学习GraphQL的基础知识。一旦你有了这个理解,你将学习如何从本地文件中获取和查询数据。然后我们将探讨从几个无头 CMS 中获取数据。
在本章中,我们将涵盖以下主题:
-
Gatsby 中的数据
-
介绍 GraphQL
-
从本地文件获取和查询数据
-
从无头 CMS 获取和查询数据
技术要求
要完成本章,你需要完成 第二章,样式选择和创建可重用布局。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter03找到。
Gatsby 中的数据
在深入之前,我认为明确我们在这本书中提到的“数据”的含义很重要。当我们提到数据时,我们指的是任何静态内容的媒介,而不是 React 代码。到目前为止,我们一直在 React 组件中直接添加文本。对于开发者来说,这可能是一种完全可接受的方式来构建小型网站,但随着规模的扩大,将内容混合到标记中会使开发变得更加困难。这也使得没有 React 经验的同事无法更新或添加网站的新内容。
将数据存储在页面和组件之外,并在需要时将其拉入,这是一种更常见的做法。我们可以以两种方式存储此类数据:
-
本地:存储在与我们的源代码相同的仓库中的文件,例如 JSON、CSV、Markdown 或 MDX 文件。
-
远程:存储在另一个位置的文件,我们将其作为构建过程的一部分摄取,例如来自无头 CMS、数据库或 API 的内容。
重要提示
你可能已经注意到,在谈论数据时没有引用图像,可能会想知道如何处理它们。由于它们的复杂性,本书中专门有一章介绍图像 – 第五章,处理图像。
现在我们已经了解了 Gatsby 中的数据含义,让我们学习如何在我们的应用程序中查询它,以便我们可以在网站页面上使用它。
介绍 GraphQL
GraphQL 是一个查询数据的规范——关于如何高效查询数据的通用指南。这个规范是在 2012 年由 Facebook 的工程师在开发他们的移动应用程序的REST服务时开发的。他们希望在他们的移动平台上使用现有的 REST 服务,但这将需要在他们的 API 的各个区域进行大量的修改和特定的逻辑。工程师们还注意到,他们的 API 请求的响应中有许多数据点他们并没有使用。这意味着那些网络带宽较低的人正在加载他们甚至没有使用的数据。
因此,Facebook 的团队开始着手开发 GraphQL 来解决这些问题,并重新思考他们为设备获取数据的方式。GraphQL 将重点从后端工程师指定由什么请求返回什么数据,转移到了前端开发者指定他们需要什么。
Gatsby 的 GraphQL
当你想要从 Gatsby 内部获取数据时,Gatsby 总是使用 GraphQL。这是一个很棒的功能,因为我们有一个高效的方式来获取数据,无论它的类型如何。如果你已经设置了一个 GraphQL 服务器,Gatsby 可以直接调用 GraphQL API。然而,我们在网上需要使用的大量数据并不是已经以 GraphQL 格式存在的。
幸运的是,Gatsby 的插件架构允许你将非 GraphQL 数据引入 Gatsby,然后在你拥有这些数据后使用 GraphQL 来查询它。无论你的数据是本地还是远程,或者它是什么格式,你都可以使用 Gatsby 的一个插件来拉取数据。然后,你可以使用 GraphQL 规范在我们的页面上查询这些数据。
这是一个非常适合我们所有内容的优秀架构,无论它来自哪里。当它进入 Gatsby 时,我们总是以相同的方式查询和检索数据。
让我们看看一个 GraphQL 查询包含的高级示例:
query SampleQuery {
content {
edges {
node {
property
}
}
}
}
在这里,你可以看到我们使用了query这个词,后面跟着查询的名称,在我们的例子中是SampleQuery。然后,在大括号内,我们指定了我们想要获取的内容类型——在这里你看到content,这会改变成你想要的内容来源。edges指的是内容源中具有作为数组返回的关系的连接项集合。然后,当我们深入一层,我们有node,它指的是单个项。在这里,你可以看到我们正在查询一个单个属性。
GraphQL 的其中一个优点是你可以非常具体地说明你需要的数据,并且只获取那些具体的内容。正如前一个示例所示,我们只查询了节点的单个属性,但如果它包含了一百个属性呢?通过只提取我们需要的,我们可以创建一个非常具体的查询,只获取我们需要的。
现在,让我们看看一个针对 Gatsby 的特定 GraphQL 查询:
query MySitePages {
allSitePage {
edges {
node {
path
}
}
}
}
在这里,我们可以看到我们正在将查询命名为 MySitePages。我们正在检索的内容来自 allSitePage 源,这是一个默认集合,包含在 Gatsby 项目中创建的所有页面。edges 指的是所有页面,而 node 指的是我们想要的特定页面。在每一页中,我们正在查询该页面的 path 参数。
当在 Gatsby 中运行此查询时,它将返回 JSON。如果您在我们的网站上运行前面的查询并记录结果,您将看到以下对象:
{
"data": {
"allSitePage": {
"edges": [
{
"node": {
"path": "/404/"
}
},
{
"node": {
"path": "/about/"
}
},
{
"node": {
"path": "/"
}
}
]
}
}
}
如您所见,我们得到的是一个具有数据属性的对象。在其中,您可以看到我们的命名查询及其边缘。边缘包含每个节点及其相应的路径属性。在结果中,我们可以看到网站上存在的每个页面 – 我们有 404 页面、about 页面和 home 页面。
现在,让我们了解如何在 GraphQL 中过滤和排序数据。
GraphQL 中的过滤
有时,返回的数据中的所有节点都不太有用。我们可能偶尔想根据特定字段过滤掉节点。让我们看看一个例子,其中我们正在从 allSitePage 源中过滤节点:
query AllSitePagesExcept404 {
allSitePage(filter: {path: {ne: "/404/"}}, limit: 1) {
edges {
node {
path
}
}
}
}
在这个例子中,我们得到一个路径不等于 (ne 为简称) /404/ 的单个页面。随着我们开始为页面开发更复杂的查询,我们将更详细地研究过滤。现在,重要的是要认识到这是可能的。
在 Gatsby 中,可以单独获取一个节点,但更常见的是查询一个集合。例如,如果我们想检索一个单独的 SitePage 节点,我们可以使用以下查询:
query ASingleSitePage {
sitePage {
path
}
}
此查询将接收与请求匹配的第一个节点,并将其作为对象返回,而不是更大的数组。
现在我们已经了解了如何构建 GraphQL 查询,让我们看看我们如何使用 GraphiQL 来探索我们的数据。
使用 GraphiQL
当谈到学习 GraphQL 时,幸运的是 Gatsby 随附了一个名为 GraphiQL 的工具 (github.com/graphql/graphiql)。这是一个连接到 Gatsby 中所有 GraphQL 选项的 Web 接口,为我们提供了一个测试和在我们将查询嵌入到代码之前进行查询的好界面。
如我们所知,在开发我们的网站时,Gatsby 会打开 http://localhost:8000 来预览我们在构建网站时的网站。如果您导航到 http://localhost:8000/___graphql,您将打开一个连接到您的开发 Gatsby 网站的 GraphiQL 接口。当您打开这个页面时,您应该会看到一个类似下面的界面:
![图 3.1 – GraphiQL 用户界面]
![img/B15983_03_01.jpg]
图 3.1 – GraphiQL 用户界面
在最左侧,你会看到探索器,它显示了在 Gatsby 中使用 GraphQL 可以获取的所有可能的内容片段。你可以在探索器区域内检查属性,让 GraphiQL 自动为你构建查询。在中央左侧列中,我们可以看到我们需要使用的查询来检索我们想要的数据。当你点击查询上方的播放按钮时,你将在中央右侧列中看到该查询的结果,其中包含一个包含数据属性和我们的查询结果的 JSON 对象。在最右侧,你会看到文档探索器区域,你可以使用它作为探索你的数据并识别你拥有的不同类型数据的替代方式。
现在,让我们学习我们可以在应用程序内使用查询来检索数据的位置。
使用构建的 GraphQL 查询
在你的 Gatsby 项目中,你可以使用 GraphQL 查询的三个主要位置:
-
Gatsby-node.js:这个文件是我们可以基于动态数据程序化创建页面的地方之一。如果我们有一份 Markdown 格式的博客文章列表,并且想要为每篇文章创建一个页面,我们就会在这里使用查询来检索我们需要动态创建页面的文章数据。 -
在页面内:我们可以向单个实例页面添加查询,以便在该页面内提供数据。这就是我们将测试本章中获取的数据的方式。我们还可以在页面模板内进行查询,这是我们尚未讨论的内容,但它是我们将在第四章“创建可重用模板”中详细探讨的关键概念。页面模板可以基于 URL 中的 slug 运行查询,然后根据该 URL 确定要显示的页面。在单个实例页面和模板中,查询是在构建时运行的,因此创建的页面仍然是静态的。
-
在任何其他组件内:我们还可以在我们的任何 React 组件内检索 GraphQL 数据。在页面模板之外检索数据的方法不同,因为在外部页面模板之外,你不能使用变量获取动态内容。因此,这种方式的查询是静态的。我们将在第五章“与图像一起工作”中看到静态查询的示例。
现在你已经了解了 Gatsby 中 GraphQL 的基础知识,让我们开始将不同类型的数据导入到我们的 GraphQL 层中。
从本地文件中获取数据
在本节中,我们将学习如何从本地文件中获取和查询数据。正如我们之前提到的,当我们说本地文件时,我们指的是位于我们仓库代码旁边的文件。
网站元数据
在 gatsby-config.js 文件中存储小块可重用数据是一个很好的地方。Gatsby 将 siteMetadata 属性暴露给数据层,这样你就可以在整个应用程序中检索它。在我们的网站上下文中,我建议在这里存储你的网站地址、你的名字、你的角色和简短的传记。如果实施得一致,当任何这些信息发生变化时,你只需在 siteMetadata 中更改一次字段,就可以在整个网站上看到更改。
提示
gatsby-config.js 是一个文件,随着你扩展 Gatsby 项目,你经常会发现它变得相当大。为了尽量保持有序,尽量为你的 siteMetadata 保留少量小字符串。如果你考虑在这里添加大块文本,可能更好的做法是将它作为一个 Markdown 文件添加。
让我们在主页上创建一些网站元数据并将其导入:
-
首先,使用以下代码更新
gatsby-config.js:module.exports = { siteMetadata key sits next to the plugins we have defined. Here, you can see we have defined the key values I suggested earlier. Keep in mind that these key values are just a suggestion and that if you want to add or remove keys, feel free to do so. -
使用 GraphiQL 界面构建 GraphQL 查询以检索数据。它应该看起来像这样:
query BasicInfo { site { siteMetadata { name role } } }你的网站元数据在
site源中可用。在前面的查询中,我们只检索了name和role。 -
在你的主页上嵌入构建的查询:
import React from "react"; import { Link, graphql from Gatsby. We are then appending our query from *Step 2* to the end of the file, below our page component. The export name isn't important as Gatsby looks for any GraphQL string within your pages, but here, you can see I am calling it query. When Gatsby builds this page, this query is pulled out of our source code, parsed, and run, and the resultant data is passed into our page component via the data prop you can see on line 5\. We can then use the data contained within the query (in our case, `name` and `role` from `siteMetadata`) to populate our site hero.Important NoteYou can only export one query per component. If you ever need more data on the page, instead of exporting another query, extend your existing query.
现在,让我们了解如何从 Gatsby 默认不包含的数据源中获取数据——从 Markdown 开始。
Markdown
Markdown 语法是在 Gatsby 网站上编写内容的一种流行方式。如果你之前使用过 GitHub 或 Bitbucket,那么你很可能已经遇到过这种格式,因为它们都在 README 文件中使用它。Markdown 是在你的网站上编写较长的写作内容的一个很好的格式——文档、博客文章,甚至是一个长的传记。
要开始在 Gatsby 中使用 Markdown,你只需要创建文本文件——不需要额外的基础设施来实现它。Gatsby 还提供了一个 核心插件(由 Gatsby 团队拥有和维护),用于将 Markdown 转换为可以由我们的组件使用的内联内容。使用核心插件,不需要编写代码即可实现 Markdown 并进行设置。
让我们在 Markdown 中创建一个简短的传记并将其添加到我们的关于页面:
-
在你的项目根目录下创建一个名为
MD的文件夹来存储我们的 Markdown。将这个文件夹放在你的
src目录之外是一个好习惯,因为它不包含任何源代码,而是文本内容。这使得没有 React 经验的开发者修改网站内容变得更加容易。 -
在
/MD目录下创建一个名为bio的文件夹来存储你的传记。随着我们添加更多提供不同类型内容的 Markdown 文件,将它们分开存储是有帮助的。 -
在我们新创建的
bio文件夹内创建一个bio.md文件,并添加以下代码:--- type: bio ---这是文件的第一部分,包含
type。这个type将帮助我们通过 GraphQL 查询查询到这个特定的文件。 -
使用 Markdown 语法创建你的传记正文:
--- type: bio --- # A short biography about me This is a very short biography about ***me***. But it could be as long as I want it to be.你可以使用任何有效的 Markdown 语法在这里;我通过只包括一个标题和一段段落来使这个例子简短,但请随意添加你想要的任何内容。
-
安装
gatsby-source-filesystem:npm install gatsby-source-filesystem如其名所示,这个插件允许 Gatsby 读取本地文件。
-
安装
gatsby-transformer-remark:npm install gatsby-transformer-remark我们可以使用这个插件来识别 Markdown 文件并读取它们的内容。这个插件将读取语法并将其转换为我们可以嵌入到组件中的 HTML。
-
接下来,让我们在
gatsby-config.js中配置我们的新依赖项:module.exports = { siteMetadata: { siteUrl: 'https://your.website', name: 'Your Name', role: 'Developer at Company', bio: 'My short bio that I will use to introduce myself.', }, plugins: [ gatsby-source-filesystem to tell Gatsby to read files from the Markdown folder we created previously.We also added `gatsby-transformer-remark` so that Gatsby can read Markdown files into its GraphQL layer. -
启动你的开发服务器并导航到你的 GraphiQL 接口。构建并运行查询以检索
bio信息:query Biography { markdownRemark(frontmatter: {type: {eq: "bio"}}) { html } }在这里,我们构建了一个查询,其中我们从
markdownRemark中检索 HTML。我们过滤 Markdown,其中 frontmatter 类型等于bio,由于只有一个这样的文件,我们将始终检索正确的文件。通过在 GraphiQL 接口中运行此查询,你应该会看到类似以下的内容:{ "data": { "markdownRemark": { "html": "<h1>A short biography about me</h1>\n<p>This is a very short biography about <em><strong>me</strong></em>. But it could be as long as I want it to be.</p>" } }, "extensions": {} }在这里,你可以看到我们编写的 Markdown 已经被转换成了 HTML,我们现在可以在我们的页面中使用它。
-
在你的
about页面中嵌入这个查询:import React from "react"; import { graphql } from "gatsby"; import Layout from "../components/layout/Layout"; export default function About({ data prop. I'd like to draw your attention to the div with the dangerouslySetInnerHTML prop. dangerouslySetInnerHTML is React's replacement for using innerHTML in the browser's DOM. It's considered *dangerous* because if the content can be edited by a user, this can expose users to a **cross-site scripting attack**. A cross-site scripting attack injects malicious code into a vulnerable web application. In our case, however, the content is always static and always defined by us, so we have nothing to worry about.
如果你想要写长篇的文章,Markdown 可以是一个很好的选择,但如果你想要让你的文章更加互动呢?也许你想要在文章中间加入一个投票或者在一个段落之间让用户注册你的电子邮件?有许多这样的场景在 Markdown 中无法优雅地实现。对于这些功能,MDX 就是答案。
MDX
MDX 是一种格式,它允许你使用 JSX 来增强你的 Markdown。你可以在 Markdown 中导入组件并将它们嵌入到你的内容中。
让我们在关于页面中使用 MDX 创建一个包含你的工作历史的增强型个人简介:
-
在你的项目根目录下创建一个名为
MDX的文件夹来存储我们的 Markdown(就像 Markdown 一样,出于相同的原因),良好的做法是将这个文件夹放在src之外,即使它可以包含 React 组件。 -
在
/MDX目录下创建一个名为bio的文件夹来存储你的个人资料(就像我们处理 Markdown 一样)。 -
在你的
/MDX文件夹中创建一个名为components的文件夹来存储专门用于我们的 MDX 文件中的 React 组件。 -
在
components文件夹中创建一个EmploymentHistory组件,以便我们可以在我们的 MDX 文件中嵌入:import React from "react"; const employment = [ { company: "Company One", role: "UX Engineer", }, { company: "Company Two", role: "Gatsby Developer", }, ]; const EmploymentHistory = () => ( <div className="text-left max-w-xl mx-auto"> <div className="grid grid-cols-2 gap-2 mt-5"> {employment.map(({ role, company }) => ( <> <div className="flex justify-end font- bold"><p>{role}</p></div> <p>{company}</p> </> ))} </div> </div> ); export default EmploymentHistory;我在这里以工作历史为例,但这也可以是任何有效的 React 组件。在这个例子中,我们定义了一个包含对象的就业经验小数组,每个对象都有一个公司和角色。在
EmploymentHistory中,我们遍历这些角色并将它们布局成网格。然后我们像平常一样导出这个组件。 -
在
/MDX/bio目录下创建bio.mdx文件:--- type: bio --- type as bio. Just below that, you will see we have introduced an import statement pointing to our newly created component. We can then use the imported component wherever we like within the body of our content, much like I have on the last line in the preceding example. -
安装必要的
mdx依赖项:npm install gatsby-plugin-mdx @mdx-js/mdx @mdx- js/react -
配置
gatsby-config.js以包含gatsby-plugin-mdx插件:module.exports = { siteMetadata: { siteUrl: 'https://your.website', name: 'Your Name', role: 'Developer at Company', bio: 'My short bio that I will use to introduce myself.', }, plugins: [ { resolve: 'gatsby-source-filesystem', options: { name: 'mdx-bio', gatsby-source-filesystem to tell Gatsby to read files from the MDX folder we created previously. We have also added gatsby-plugin-mdx so that Gatsby can read MDX files into its GraphQL layer. -
启动你的开发服务器并导航到你的 GraphiQL 界面。构建并运行查询以检索更新的 MDX bio:
query Biography { mdx(frontmatter: { type: { eq: "bio" } }) { body } }在这里,我们构建了一个查询,其中我们从
mdx源检索mdx主体的内容,其中前缀类型等于bio。 -
在你的关于页面中嵌入查询:
import React from "react"; import { graphql } from "gatsby"; import Layout from "../components/layout/Layout"; data prop. We then used MDXRenderer from gatsby-plugin-mdx to render the MDX body's content.Important NoteUsing `MDXRenderer` does increase your bundle size and the time it takes for your JavaScript to be parsed. This is because instead of rendering all the HTML at build time, any pages containing MDX are now being rendered to HTML on the frontend. This is important to keep in mind as it will negatively impact your site's performance.
现在我们已经了解了如何摄取本地数据,让我们来看看从远程来源获取数据——一个内容管理系统(CMS)!
从 Headless CMS 获取数据
Headless CMS 是一种只关注内容本身而不关心其呈现方式的 CMS。传统的 CMS 将内容存储在数据库中,然后使用一系列 HTML 模板来控制内容如何呈现给观众。然而,在 Headless CMS 中,我们不是返回 HTML,而是通过 API 返回结构化数据。
内容创作者仍然可以通过用户界面添加和编辑数据,但前端完全独立存储。这对于你的内容创作者不是开发者,或者当你在外出时想在手机上写一篇帖子而不需要启动笔记本电脑时非常完美。
由于 Gatsby 拥有庞大的插件生态系统,你的网站可以轻松支持许多不同的 Headless CMS。你可以写一本书来介绍如何将它们中的每一个都集成到你的项目中,所以,让我们专注于两个——GraphCMS 和 Prismic。
重要提示
仅在此章节中概述的 Headless CMS 选择中实现一个。拥有两个相同类型数据的来源不仅会令人困惑,而且还会导致网站构建时间更长,因为需要从两个来源而不是一个来源检索数据。
GraphCMS
GraphCMS 是一个全托管 SaaS 平台,被全球超过 30,000 个不同规模的团队使用。他们的查询在全球 190 个边缘 CDN 节点上缓存,这意味着无论你身处何地,将数据从 GraphCMS 拉入你的 Gatsby 项目应该非常快。让我们通过在工具中创建一个我们可以在应用程序中摄取的兴趣爱好列表来介绍如何使用 GraphCMS:
-
导航到 GraphCMS 网站(graphcms.com)并登录。
-
创建一个新的空白项目并选择你想要托管数据所在的区域。
-
导航到你的项目的
模型将打开以下对话框:![图 3.2 – 在 GraphCMS 中创建模型]![img/B15983_03_02.jpg]
图 3.2 – 在 GraphCMS 中创建模型
在这里,你可以看到我正在创建一个名为Icebreakers的模型。你会注意到你需要提供一个API ID及其复数形式,以便在查询单个项目与整个集合之间更容易区分。点击更新模型后,你应该能看到Icebreakers已经被添加到左侧侧边栏的模型中。
-
我们现在可以通过添加字段来定义 Icebreakers 模型中包含的数据类型。点击 Icebreakers 模型后,您将在右侧看到许多字段选项。我们可以使用这些选项来告诉 GraphCMS 我们的数据将采用什么格式。在我们的例子中,一个爱好由一到三个单词组成,因此使用单行文本字段选项是合适的。选择此选项将打开以下对话框:
![图 3.3 – 在 GraphCMS 中创建字段]()
图 3.3 – 在 GraphCMS 中创建字段
输入一个合适的显示名称和 API ID,例如hobbies。将描述写为我拥有的爱好集合。我还勾选了允许多个值,这样我们就可以存储一个爱好列表而不是单个爱好。点击更新以保存此配置。
-
导航到网站的“内容”部分。在页面右上角点击创建项目。这将打开以下窗口:
![图 3.4 – 在 GraphCMS 中填充内容]()
图 3.4 – 在 GraphCMS 中填充内容
我们现在可以开始填写我们的爱好,在添加时将它们添加到列表中。一旦完成,请点击页面右上角的保存。
-
返回到内容窗口,您会看到您创建的 Icebreaker 处于草稿模式。这意味着我们还不满意内容,而且我们还不能从 API 中检索它:
![图 3.5 – GraphCMS 内容和其草稿状态]()
图 3.5 – GraphCMS 内容和其草稿状态
-
要使内容生效,我们需要通过选择项目然后点击发布按钮来发布它。
-
接下来,我们需要修改端点设置以允许公共 API 访问。默认情况下,您的 GraphCMS API 无法从其平台外部访问。您可以更改公共 API 访问的设置或创建具有访问权限的永久性认证令牌。通常,我倾向于保持我的数据公开,因为即使不知道 API 的 URL,它仍然可以检索。由于默认情况下无法编辑,所以所有内容仍然会在我网站上公开显示。
导航到设置,然后是API 访问,并修改您的公共 API 权限如下:
![图 3.6 – GraphCMS 公共 API 设置]()
图 3.6 – GraphCMS 公共 API 设置
您会看到我已经勾选了从已发布阶段获取内容。通过这样做,我们现在可以检索通过 API 的访问页面顶部 URL 端点发布的所有数据。
-
滚动到页面顶部并注意您的 master URL 端点。现在我们将转到我们的 Gatsby 项目,并使用此 URL 开始摄取数据。
-
在项目的根目录下打开一个终端,并安装必要的依赖项、官方 GraphCMS 源插件和
dot-env:npm install gatsby-source-graphcms gatsby-plugin-image dotenvgatsby-source-graphcms将允许我们在应用程序中从 GraphCMS 获取数据,而dotenv是一个零依赖模块,它从.env文件中加载环境变量。我们将以.env格式存储我们的 API 端点。此插件还要求在内部使用gatsby-plugin-image,所以请确保安装它。我们将在第五章,与图像一起工作中更多地讨论gatsby-plugin-image。 -
在你的项目根目录创建一个
.env文件,并将 GraphCMS 的主 URL 端点作为变量添加:GRAPHCMS_ENDPOINT=.env file is used to house environment variables. Be sure to replace the highlight with your master URL endpoint from *Step 6*. This file should not be committed to source control and, as such, should be added to your .gitignore. -
修改你的
gatsby-config.js文件,使其包含gatsby-plugin-image和gatsby-source-graphcms:dotenv to load in our create .env file, and then we use that variable within the plugin configuration of gatsby-source-graphcms. -
现在我们可以启动我们的开发服务器。你会注意到,当开发服务器启动时,会创建一个名为
graphcms-fragments的新文件夹。这个文件夹由插件维护,包含解释我们数据结构的片段,以供 GraphQL 数据层使用。 -
到目前为止,我们可以像查询任何其他来源的数据一样查询我们的数据。首先,我们必须构建一个查询:
query Hobbies { graphCmsIcebreaker { hobbies } }在这里,我创建了一个查询,从自动生成的
graphCmsIcebreaker源中提取我们的爱好数组。 -
我们现在可以将此查询嵌入到我们的
about页面中:import React from "react"; import { graphql } from "gatsby"; import Layout from "../components/layout/Layout"; import { MDXRenderer } from "gatsby-plugin-mdx"; export default function About({ data }) { const { mdx: { body }, graphCmsIcebreaker: { hobbies }, } = data; return ( <Layout> <div className="max-w-5xl mx-auto py-16 lg:py-24 text-center"> <MDXRenderer>{body}</MDXRenderer> <div> <h2>Hobbies</h2> {hobbies.join(", ")} </div> </div> </Layout> ); } export const query = graphql' { mdx(frontmatter: { type: { eq: "bio" } }) { body } graphCmsIcebreaker { hobbies } } ';你会注意到我只是将新的查询附加到现有的页面查询中,捆绑到同一个 GraphQL 字符串中。Gatsby 期望每个页面只有一个查询。然后我解构了数据属性以检索爱好数组。
现在我们已经了解了 GraphCMS 的工作原理,让我们转向如何实现 GraphCMS 的一个竞争对手,Prismic。
Prismic
Prismic 比 GraphCMS 小,大约有 5,000 个付费客户。使其脱颖而出的一个特性是他们提供动态多会话预览,允许你在 Gatsby 中共享多个同时动态预览(带有可分享的链接)。当你与客户一起工作时,这可以提高你的工作流程,因为你需要来回发送客户的网站内容。让我们通过在 UI 中添加一个爱好列表来学习如何集成 Prismic,这样我们就可以在我们的 Gatsby 网站上摄取它们:
-
在
/src目录下创建一个名为schemas的文件夹。与 GraphCMS 不同,Prismic 不会自动为我们创建模式;相反,我们将使用 Prismic UI 在创建它们时检索它们。 -
导航到 Prismic 的网站(prismic.io)并登录。使用免费计划创建一个新的存储库(如果你需要,你总是可以稍后扩展)。
-
点击创建第一个自定义类型按钮,选择单选类型。将你的类型命名为Icebreaker并提交。
-
在右侧构建模式侧边栏的底部滚动,并将一个组拖到中央页面:![图 3.7 – Prismic 组字段选项
![图片]()
图 3.7 – Prismic 组字段选项
-
将你的字段命名为hobbies;相应的 API ID 将自动填充。点击确定以确认这一点。
-
将一个富文本字段拖动到这个组中:![图 3.8 – Prismic 文本字段配置
![img/B15983_03_08.jpg]
图 3.8 – Prismic 文本字段配置
这将打开前面截图左侧显示的侧面板。我们将使用富文本字段作为单个爱好的类型。首先,让我们给它起个名字——爱好似乎很合适。确保API ID与分配的名称匹配。取消勾选允许多个段落框,然后确保只有段落对象被突出显示。通过这样做,我们可以确保我们的爱好总是单行,只包含段落。使用确定按钮提交。
-
保存文档。
-
现在我们已经定义了我们的类型,导航到 JSON 编辑器并复制其内容。
-
在您的
schemas文件夹内创建一个名为icebreaker.json的新文件,并将复制的 JSON 粘贴进去。 -
返回首页并点击文档。然后点击铅笔图标按钮创建你的 Icebreaker 类型的新实例:![图 3.9 – Prismic 集合界面
![img/B15983_03_09.jpg]
图 3.9 – Prismic 集合界面
你现在可以使用你的爱好类型来创建你的数据。一旦你对你的爱好列表满意,你可以点击保存,然后点击发布。
-
返回首页,导航到设置,然后点击API 和安全。确保您的仓库安全设置为仅对主分支的公共 API:![图 3.10 – 仓库安全
![img/B15983_03_10.jpg]
图 3.10 – 仓库安全
这意味着任何拥有您 API URL 的人都可以访问当前正在直播的内容,但不能预览未来的发布。请记下您的 API 入口点,它应该位于本页面的顶部。现在,让我们看看我们的 Gatsby 项目,并开始使用该 URL 获取数据。
-
安装 Gatsby Prismic 源插件:
npm install gatsby-source-prismic gatsby-plugin-image -
修改你的
gatsby-config.js文件:module.exports = { ... plugins: ... 'gatsby-plugin-image', gatsby-plugin-image, so make sure it has been added to your configuration. -
我们现在可以启动我们的开发服务器,并像往常一样查询我们的数据。在打开 GraphiQL 后,你应该会看到一个新来源
prismicIcebreaker,我们可以用它来查询我们的爱好:query Hobbies { prismicIcebreaker { data { hobbies { hobby { text } } } } }在这里,我们正在检索
hobbies对象中每个爱好的文本值。 -
我们现在可以将这个查询嵌入到我们的
about页面中:import React from "react"; import { graphql } from "gatsby"; import Layout from "../components/layout/Layout"; import { MDXRenderer } from "gatsby-plugin-mdx"; export default function About({ data }) { const { mdx: { body }, data prop and is available for us to use in whatever way we wish.
你应该开始看到使用 GraphQL 在 Gatsby 中的强大功能。一旦我们摄入了数据,我们就可以使用相同的格式来查询它。以这两个为例,你应该能够使用源插件从另一个 CMS 中获取数据。
摘要
在本章中,你学习了如何使用 Gatsby 的数据层。你了解了如何通过 GraphiQL 探索你的 GraphQL 数据层的基础知识,现在你应该能够从多种不同的来源(siteMetadata、Markdown、MDX 和 CMS 使用其插件)轻松地获取和摄入数据到你的 Gatsby 项目中。如果你对如何创建源插件以及如何创建自己的插件感兴趣,请查看[第十章,创建 Gatsby 插件。
在下一章中,我们将创建并使用可重复使用的模板来处理那些出现多次的页面,例如博客页面。这对于当你想要使用相同布局同时利用多份数据时非常有用。
第四章:创建可重用模板
本章是您真正开始看到 Gatsby 为大型网站带来的强大功能的地方。您将了解我们如何使用可重用模板和通过 GraphQL 获取的数据编程创建页面。到本章结束时,您将创建博客帖子列表、博客页面和标签页面。您还将了解如何将分页和搜索功能引入您的网站。
到目前为止,我们创建的所有页面都是单个实例,这意味着网站上只有一个该页面的副本(例如,我们的索引页面,永远只有一个副本)。但是,当我们考虑像博客页面这样的页面时会发生什么呢?为每篇帖子创建单个实例页面将是一个非常费力的过程。因此,我们可以使用模板。模板是页面组件的多实例,它映射到数据。对于 GraphQL 查询中的每个节点,我们都可以使用这个模板创建一个页面,并用该节点的数据填充它。
现在我们已经了解了在 Gatsby 中我们所说的模板是什么,让我们创建我们的第一个几个模板,然后编程地使用它们创建页面。
在本章中,我们将介绍以下主题:
-
定义模板
-
创建模板和程序化页面生成
-
搜索功能
技术要求
要完成本章,您需要完成第三章,从任何地方获取和查询数据。如果您有一系列博客帖子,我们可以用来构建我们的页面,并已导入 Gatsby,您将充分利用本章。源代码不重要——如果您能在您的 GraphQL 数据层中看到它们,您就可以开始本章了。如果您没有可用的帖子,您可以在以下位置找到一些占位符 Markdown 文件,您可以将它们导入 Gatsby:github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter04/placeholder-markdown。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter04找到。
重要提示
为了保持代码片段的大小可管理,本章中的许多示例都省略了样式,并带有指向我们已编写的代码的注释。要查看这些组件的完整样式版本,请导航到本书的代码仓库。
创建模板和程序化页面生成
在本节中,我们将使用模板编程生成页面。我们将创建博客页面、博客列表预览页面和标签页面。为了确保所有这些都能正确工作,重要的是要确保您要填充博客页面的每个数据节点都包含以下内容:
-
标题: 博客文章的标题。
-
描述: 对博客文章内容的单行描述。
-
日期: 文章应该发布的日期。
-
标签: 与博客文章相关联的标签列表。
-
正文: 文章的主要内容。
如果你从同一来源获取多种类型的内容,最好也包含一个类型字段。这将允许你过滤掉不属于此类型的节点。
添加这些内容到节点的方法将根据来源而变化。然而,在 Markdown 的情况下,你可以按照以下格式创建你的文章:
---
type: Blog
title: My First Hackathon Experience
desc: This post is all about my learnings from my first
hackathon experience in London.
date: 2020-06-20
tags: [hackathon, webdev, ux]
---
# Body Content
在这里,我们将title、desc、date和tags添加到frontmatter中。正文内容将是frontmatter之后的所有内容。
重要提示
我将在本章中查询本地 Markdown 文件中的数据。如果你从其他类型的本地或远程来源获取内容,你仍然可以使用所有代码,除了查询和节点字段操作,你必须修改以与你的来源一起工作。如果你在构建查询时遇到困难,请参考第三章,从任何地方获取和查询数据。
无论你的来源是什么,你应该确保你的内容填充了相同的字段,以确保与博客相关数据的 GraphQL 查询始终一致。
现在我们已经建立了必要的博客节点数据字段,让我们使用我们的数据来创建博客文章页面。
博客文章模板
在本节中,我们将为每个博客文章创建页面。我们将通过以下步骤创建和使用我们的第一个模板来完成这项工作:
-
修改你的
gatsby-node.js文件,使其包含以下代码:const { createFilePath } = require('gatsby-source- filesystem'); exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions; if (node.internal.type === 'MarkdownRemark') { const slug = createFilePath({ node, getNode, basePath: 'pages' }); createNodeField({ node, name: 'slug', value: slug, }); } };onCreateNode函数在创建新节点时被调用。使用此函数,我们可以通过添加、删除或操作它们的字段来转换节点。在这种情况下,如果节点是MarkdownRemark类型,我们将添加一个slug字段。slug是我们网站上特定页面的地址,因此在我们的博客页面中,我们希望每个博客文章都有一个独特的slug,它将在网站上渲染。从文件名创建 slug 可能很复杂,因为你需要处理会破坏 URL 格式的字符。幸运的是,gatsby-source-filesystem插件提供了一个名为createFilePath的函数来创建它们。 -
通过运行你的开发服务器并使用 GraphiQL 来探索你的节点,验证每个博客页面都有一个
slug。如果你使用 Markdown,你应该在MarkdownRemark节点的fields对象中找到它。 -
在
src目录内创建一个名为templates的新文件夹来存放我们的页面模板。 -
在
templates目录内创建一个名为blog-page.js的新文件。这是我们创建博客页面模板的文件。 -
将以下代码添加到
blog-page.js文件中:import React from "react"; import Layout from "../components/layout/Layout"; import TagList from "../components/blog-posts/TagList" export default function BlogPage() { return ( <Layout> <div className="max-w-5xl space-y-4 mx-auto py-6 md:py-12 overflow-x-hidden lg:overflow-x- visible"> <h1 className="text-4xl font-bold">Blog Title</h1> <div className="flex items-center space-x-2"> <p className="text-lg opacity-50">Date</p> <TagList tags={["ux"]} /> </div> <div> Article Body </div> </div> </Layout> ); }在这里,我们正在创建一个包含静态数据的博客文章模板,稍后我们将用实际内容替换它。您可以看到我们有一个包含博客文章标题的标题。然后我们跟随博客的
日期和TagList组件,我们将在稍后创建它。最后,我们有主要的文章正文。 -
在
src/components目录内创建一个名为blog-posts的文件夹,我们将在此存储任何与博客相关的组件。 -
在
src/components/blog-posts文件中创建一个TagList组件。我们将在需要将屏幕上的tag徽章列表渲染时使用此组件:import React, { Fragment } from "react"; const TagList = ({ tags }) => { return ( <Fragment> {tags.map((tag) => ( <div key={tag} className="rounded-full px-2 py-1 uppercase text-xs bg-blue-600 text-white" > <p>{tag}</p> </div> ))} </Fragment> ); }; export default TagList此组件接受一个
tags数组作为属性,遍历它们,并返回一个包含该tag的样式div。所有这些都包裹在一个Fragment组件中。通过使用Fragment,我们可以避免强制执行我们的tags的排序和定位,而可以允许父元素来决定。现在我们已经创建了一个模板文件及其组件,我们可以在
gatsby-node.js文件中使用它。 -
将以下代码添加到您的
gatsby-node.js文件顶部:const path = require('path'); const { createFilePath } = require('gatsby-source- filesystem'); exports.createPages = async ({ actions, graphql, reporter }) => { const { createPage } = actions; const BlogPostTemplate = path.resolve('./src/templates/blog-page.js'); const BlogPostQuery = await graphql(' { allMarkdownRemark(filter: { frontmatter: { type: { eq: "Blog" } } }) { nodes { fields { slug } } } } '); if (BlogPostQuery.errors) { reporter.panicOnBuild('Error while running GraphQL query.'); return; } BlogPostQuery.data.allMarkdownRemark.nodes.forEach(({ fields: { slug } }) => { createPage({ path: 'blog${slug}', component: BlogPostTemplate, context: { slug: slug, }, }); }); }; -
在这里,我们正在使用
createPages函数,它允许我们动态地创建页面。为了确保您可以查询所有数据,此函数仅在所有数据都已获取后运行一次。在这个函数内部,我们首先解构actions对象以检索createPage函数。然后,我们告诉 Gatsby 在哪里可以找到我们的博客文章模板。有了这两部分,我们现在可以查询我们的数据了。您应该会看到一个熟悉的 GraphQL 查询,用于从所有类型为Blog的 Markdown 中选择slug。然后我们有一个小的if语句来捕获错误,但假设它是成功的,我们就有创建页面所需的所有数据。我们可以遍历我们的数据结果,遍历每个数据节点,通过指定路径(使用slug)和我们的模板为每个节点创建一个页面。您还会注意到我们在这里定义了一些context。在context中定义的数据可以作为 GraphQL 变量在页面查询中使用,这将使在以下步骤中将正确的 Markdown 内容映射到正确的页面变得容易。重新启动您的开发服务器,并通过导航到端口的任何非存在路由来打开开发 404 页面。这将显示您网站上包括我们刚刚创建的页面的页面列表。点击其中一个应该会渲染我们在创建模板时定义的静态内容。现在,这些页面已成功创建,让我们回到模板并修改它以检索正确的内容而不是静态内容。 -
使用以下代码修改
src/templates/blog-post.js文件:import React from "react"; slug property we defined in the gatsby-node.js file comes in handy. We can use that slug to find the blog post where slug matches in the node's fields. We query for all the data that we need to populate this page with and retrieve date, title, tags, and the Markdown HTML. This is then passed into the template via the data prop, exactly like in our single instance pages. We can then use this content to swap out the static placeholder content we had previously. -
通过重新启动您的开发服务器并再次导航到您的某个博客页面,您现在应该会看到它用其节点数据填充了。您已成功创建了第一个程序化页面!
由于我们只有少数几篇博客文章,创建所有这些页面不会花费太多时间。然而,如果您需要创建数千个页面,会发生什么?您不必等待所有网站页面构建完成,可以指示 Gatsby 延迟生成一些页面。您可以通过在
gatsby-node.js中的createPage函数传递defer:true来实现这一点,如下所示:createPage({ path: 'blog${slug}', component: BlogPostTemplate, defer: true, context: { slug: slug, }, });通过这次改动,任何以这种方式创建的页面将在第一次请求该页面时构建,而不是在构建时构建。这个特性将构建类型从静态构建改为混合构建。有关这种差异的更多信息,请参阅第九章,部署和托管。
现在我们已经创建了博客文章页面,我们必须有一种方法从我们的其他页面链接到它们。让我们创建一个博客预览模板页面,在那里我们可以列出我们的博客文章预览和链接到我们刚刚创建的页面。
博客预览模板
虽然我们可以创建一个博客文章的单个列表并渲染它,但使用分页来分割网站上的博客文章列表、文章和产品列表是一个标准模式。在您的网站上使用分页有三个主要好处:
-
更好的页面性能:如果每篇文章的预览中都包含一张图片,那么随着每个项目的添加,我们需要传输到客户端的数据量将显著增加。通过引入分页,客户端在浏览一组项目时只会下载小部分数据。这导致页面加载时间更快,这在带宽较低的地区尤为重要。
-
改进的用户体验:在单个页面上显示所有内容可能会让用户感到不知所措,因此,我们必须将内容分解成小而可管理的块。
-
更便捷的导航:如果我们在一个连续的列表中渲染数百个产品,用户在滚动时将无法知道有多少产品。通过将内容拆分为多个页面,每个页面有固定数量的产品,用户可以更好地理解您的内容规模。
考虑到所有这些,让我们使用模板创建一个分页的博客预览页面:
-
在
src/components/blog-posts文件中创建一个Pagination组件:import React from "react"; import { Link } from "gatsby"; const Pagination = ({ numPages, currentPage }) => { var pageArray = []; for (var i = 1; i <= numPages; i++) pageArray[i] = i; return ( <div> <ul> {currentPage !== 1 && ( <li> <Link to={currentPage === 2 ? '/blog' : '/blog/${currentPage - 1}'}> Previous </Link> </li> )} {pageArray.map((pageNum) => ( <li key={'pageNum_${pageNum}'} > <Link to={pageNum === 1 ? '/blog' : '/blog/${pageNum}'}> {pageNum} </Link> </li> ))} {currentPage !== numPages && ( <li> <Link to={'/blog/${currentPage + 1}'}>Next</Link> </li> )} </ul> </div> ); }; export default Pagination;在这里,我们创建了一个组件,它将允许我们访问分页的博客预览页面。该组件包含页数和当前页作为属性。使用这两条信息,我们可以确定用户是否可以从当前页面导航到下一页或上一页。这个组件的工作原理最好通过查看它的渲染方式来解释:
![图 4.1 – 分页组件状态]()
图 4.1 – 分页组件状态
在第一种情况下,当前页面是1,因此不需要渲染上一页按钮。相反,我们只显示前一页和下一页按钮。在第二种情况下,我们处于第2页,用户可以前后导航,因此我们可以渲染上一页和下一页按钮。在最后一种情况下,我们处于最后一页,因此不需要渲染下一页按钮。
-
在
src/templates/中创建一个新的模板,命名为blog-preview.js,并添加以下页面查询:/* Space for page component */ export const pageQuery = graphql' query($skip: Int!, $limit: Int!) { blogposts: allMarkdownRemark( limit: $limit skip: $skip filter: { frontmatter: { type: { eq: "Blog" } } } sort: { fields: frontmatter___date, order: DESC } ) { nodes { frontmatter { date title tags desc } fields { slug } } } } ';文件中的查询从
allMarkdownRemark(我在此查询中命名为blogposts)获取数据。blogposts查询检索所有frontmatter类型等于Blog的 Markdown。它按降序对帖子集合进行排序。这里事情变得有趣 - 我们还向查询提供了skip和limit。skip告诉查询跳过集合中的多少个文档,而limit告诉查询限制结果的数量。我们将在gatsby-config.js文件中提供skip和limit,以及numPages和currentPage。 -
在
blog-preview.js文件中,在查询之前创建页面组件:import React from "react"; import { graphql, Link } from "gatsby"; import Layout from "../components/layout/Layout"; import Pagination from "../components/blog- posts/Pagination"; import TagList from "../components/blog-posts/TagList" export default function BlogPreview({ pageContext, data }) { const { numPages, currentPage } = pageContext const { blogposts: { nodes }, } = data; // return statement }就像我们的其他查询一样,当文件末尾的查询运行时,它将通过
data属性为我们的页面提供data。在这里,我们正在解构pageContext以访问numPages和currentPage。我们也在使用解构data来获取blogposts查询中的nodes。我们将在下一步的return语句中添加我们的渲染。 -
在同一文件中创建
return语句:return ( <Layout> <div className="max-w-5xl mx-auto space-y-8 py-6 md:py-12"> {nodes.map( ({ frontmatter: { date, tags, title, desc }, fields: { slug } }) => ( <div> <Link to={'/blog${slug}'}> <h2 className="text-2xl font- medium">{title}</h2> <div className="flex items-center space-x-2"> <p className="text-lg opacity- 50">{date.split("T")[0]}</p> <TagList tags={tags}/> </div> <p>{desc}</p> </Link> </div> ) )} <Pagination numPages={numPages} currentPage={currentPage} /> </div> </Layout> );我们使用两个来源的
nodes来遍历帖子,渲染每个帖子的预览(利用TagList组件),以及渲染我们的Pagination组件。现在我们已经创建了模板,我们可以将其导入到我们的gatsby-config.js文件中。 -
修改你的
gatsby-config.js文件的createPages函数,使用以下代码:exports.createPages = async ({ actions, graphql, reporter }) => { const { createPage } = actions; const BlogPostTemplate = path.resolve('./src/templates/blog-page.js'); BlogPreviewTemplate, then we run our Markdown query as normal. As we will be now using BlogPostQuery.data.allMarkdownRemark.nodes in two places (blog previews and blog post page creation), we can assign it to a constant. We will also assign two more constants – the number of posts per page (postsPerPage) and the number of pages (numPages) that we will need for pagination. postsPerPage specifies how many posts we want on each of our paginated blog post previews. numPages calculates how many preview pages are needed by dividing the total number of posts by postsPerPage and then rounding up to the nearest whole integer using the Math.ceil function. We then create an Array with a length equal to the number of pages and loop through it using the forEach function. For each index (i), we use the createPage action. We provide this action with the path to where the page should be located, which is /blog if i is 0 and /blog/i+1 for anything higher. We also provide BlogPreviewTemplate and context, which contain limit and skip, which we utilize on the page. -
你现在可以开始启动你的开发服务器以验证分页是否正常工作。你应该在
/blog位置看到按降序排列的帖子。如果你有比postsPerPage值更多的帖子,你应该也会看到你的Pagination组件,这表明有额外的页面,并允许你导航到那里。
现在我们已经实现了博客预览页面,让我们利用所学知识创建另一个页面集合 - 标签页面。
标签页面模板
作为用户,看到我的帖子按日期排序并不总是足够的 - 我可能希望能够找到与单个主题相关的帖子组。标签页面是在点击博客帖子中的一个标签时导航到的页面。导航到这些页面之一,你会看到一个与该标签相关的帖子列表。
让我们程序化地为文章中存在的每个标签创建标签页面:
-
安装
lodash:npm i lodashlodash是一个 JavaScript 工具库,我们将使用它来使标签 URL 友好。因为一个标签可能由多个单词组成,我们需要一种方法来删除空格。虽然你可以自己创建一个函数来做这件事,但lodash有一个.kebabCase()函数非常适合这个用例。 -
修改
TagList组件,将我们的tag徽章转换为Link组件:import React, { Fragment } from "react"; import { Link } from "gatsby"; import { kebabCase } from "lodash" const TagList = ({ tags }) => { return ( <Fragment> {tags.map((tag) => ( <Link key={tag} to={'/tags/${kebabCase(tag)}'}> <div key={tag} className="rounded-full px-2 py-1 uppercase text-xs bg-blue-600 text-white" > <p>{tag}</p> </div> </Link> ))} </Fragment> ); }; export default TagList作为
Link组件,它们需要一个to属性。这个属性应该指向你的tag页面将被创建的位置——在我们的例子中,/tags/tag-name是位置。我们可以使用lodash中的kebabCase函数来确保标签中的任何空格都被转换为连字符。 -
在
src/templates文件夹中创建一个tags.js文件:/* Space for page component */ export const pageQuery = graphql' query($tag: String) { blogposts: allMarkdownRemark( sort: { fields: [frontmatter___date], order: DESC } filter: { frontmatter: { tags: { in: [$tag] }, type: { eq: "Blog" } } } ) { totalCount nodes { frontmatter { date title tags desc } fields { slug } } } } '; -
此组件将非常类似于我们在“博客预览模板”部分之前构建的
blog-preview.js文件,除了对查询进行了一些小的修改。在这个查询中,我们仍然获取我们的 Markdown 内容,但这次我们过滤掉了不包含页面标签的帖子。 -
在
tags.js文件中的查询之前创建页面组件:import React from "react"; import { graphql, Link } from "gatsby"; import Layout from "../components/layout/Layout"; import TagList from "../components/blog- posts/TagList"; export default function Tags({ pageContext, data }) { const { tag } = pageContext; const { blogposts: { nodes }, } = data; return ( <Layout> <div> <p>Posts tagged with "{tag}"</p> {nodes.map( ({ frontmatter: { date, tags, title, desc }, fields: { slug } }) => ( <div> <Link to={'/blog${slug}'}> <h2>{title}</h2> <div> <p>{date.split("T")[0]}</p> <TagList tags={tags} /> </div> <p>{desc}</p> </Link> </div> ) )} </div> </Layout> ); }页面随后渲染一个包含你当前过滤帖子所用的标签的段落,后面跟着过滤后的帖子列表。每个帖子预览都以其
title、date、描述(desc)和tags进行渲染,就像在blog-preview.js文件中一样。重要提示
如果你打算在
blog-preview.js和tags.js文件中的列表中渲染相同的条目,那么你可能应该将条目预览组件抽象成一个单独的组件。为了保持这些示例的独立性,我这里不会这样做。 -
将
lodash导入到gatsby-config.js文件的顶部,靠近其他导入:const _ = require("lodash");我们还需要在这个文件中使用 lodash 的
kebabCase。 -
将你的标签模板和查询添加到
gatsby-config.js文件中的createPages函数:exports.createPages = async ({ actions, graphql, reporter }) => { // actions destructure & other templates TagsTemplate. Then, we append our query with a new query to our Markdown source. This group (which we've named tagsGroup) retrieves an array containing every unique tag that is within frontmatter of our posts. We can then use this new data to loop through every `tag` and create a `tag` page for each one. We pass a `path` to each `createPages` function, pointing to `tags/`, followed by the `tag` name that's parsed through the `kebabCase` function. We pass the `component` property we want it to build the page with, which in our case is `TagsTemplate`, at the beginning of this file. You will also notice that we are also passing `tag` to the page's `context` so that the page knows which `tag` it relates to. -
你现在可以开始启动你的开发服务器以验证标签页面是否正常工作。导航到开发 404 页面;你应该看到每个标签都有一个以
tags/开头的页面。点击其中一个,你应该会看到我们的标签页面模板和与该标签相关的博客帖子列表。进一步练习
我们已经学会了如何分页博客列表,以及创建标签页面。为什么不更进一步,对标签页面进行分页呢?
通过这样,我们已经学会了如何以编程方式为博客文章、博客列表和标签创建页面。现在,让我们关注我们如何创建一个网站搜索,以便随着网站的扩展,找到我们的博客内容变得更加容易。
搜索功能
集成网站搜索的方法有很多种。许多选项既可以是托管也可以是本地。对于像我们正在创建的这样的小型项目,通常选择本地索引解决方案会更好,因为你搜索的页面数量永远不会很大。这也意味着你的网站搜索将在离线场景中工作,这可以是一个真正的加分项。
elasticlunr Gatsby 插件,内容被索引然后通过 GraphQL 供 elasticlunr 索引使用。然后可以对此索引进行搜索查询以检索页面信息。
让我们使用 elasticlunr 集成站点搜索:
-
安装
elasticlunrGatsby 插件:npm install @gatsby-contrib/gatsby-plugin-elasticlunr- search -
将
elasticlunr插件添加到你的gatsby-config.js插件数组中:{ resolve: '@gatsby-contrib/gatsby-plugin- elasticlunr-search', options: { fields: ['title', 'tags', 'desc'], resolvers: { MarkdownRemark: { title: node => node.frontmatter.title, tags: node => node.frontmatter.tags, desc: node => node.frontmatter.desc, path: node => '/blog'+node.fields.slug, }, }, filter: (node, getNode) => node.frontmatter.type === "Blog", }, },作为
options的一部分,我们向插件提供了一个我们希望索引的fields列表。然后,我们给它一个resolvers对象,该对象解释了如何解析源中的fields。在我们的博客文章中,我们可以从frontmatter中检索title、tags和desc。我们可以使用特定的内容和数据的slug来构造path。最后,我们还传递了一个filter。这个filter告诉插件只使用frontmatter类型为Blog的节点,因为我们只想在这个时候让我们的博客页面可搜索。 -
在
src/layout文件夹中创建一个Search.js组件:import React, { useState, useEffect } from "react"; import { Link } from "gatsby"; import { Index } from "elasticlunr"; const Search = ({ searchIndex }) => { const [query, setQuery] = useState(""); let [index, setIndex] = useState(); let [results, setResults] = useState([]); useEffect(() => { setIndex(Index.load(searchIndex)); }, [searchIndex]); }; export default SearchSearch组件接收searchIndex作为属性。你首先会注意到一个useEffect钩子,它会将索引加载到状态钩子中。一旦我们加载了索引,我们就可以查询它。 -
在
useEffect下方创建一个search函数:const search = (evt) => { const query = evt.target.value; setQuery(query); setResults( index .search(query, { expand: query.length > 2 }) .map(({ ref }) => index.documentStore.getDoc(ref)) ); };你会看到每当调用
search函数时,我们都会使用我们的query字符串来搜索索引。你会注意到我们向search函数传递了expand: query.length > 2作为选项。这告诉elasticlunr允许输入超过两个字符时的部分匹配。如果你为更少的字符允许部分匹配,你经常会发现你得到了大量与用户所查找内容不相关的结果。一旦我们搜索了索引,我们就可以在index中的documentStore上进行map操作,并返回文档结果,这些结果随后通过useState钩子传递到状态中。 -
创建
search结果渲染函数:const searchResultSize = 3; return ( <div className="relative w-64 text-gray-600"> <input type="search" name="search" placeholder="Search" autoComplete="off" aria-label="Search" onChange={search} value={query} /> {results.length > 0 && ( <div> {results .slice(0, searchResultSize) .map(({ title, description, path }) => ( <Link key={path} to={path}> <p>{title}</p> <p className="text- xs">{description}</p> </Link> ))} {results.length > searchResultSize && ( <Link to={'/search?q=${query}'}> <p>+ {results.length - searchResultSize} more</p> </Link> )} </div> )} </div> );我们使用
useState钩子的results值在状态的结果上map,并在渲染函数中将结果渲染到屏幕上。为了获得更好的用户体验,通常一个好的做法是包含一个searchResultSize常量。此值决定了要显示的最大结果数。这阻止了你有数百个结果时页面覆盖的情况。相反,如果有更多结果,我们只需向用户指示还有多少个结果。 -
修改你的
Header.js文件以检索站点索引并将其传递给你的Search组件:import React from "react"; import { Link, Header.js is not a page component, we cannot append the graphql query to the end of the page as Gatsby is not looking for it. However, we can still locate data with the component by using StaticQuery. Static queries differ from page queries as they cannot accept variables like our pages can via page context. In this scenario, that's not a constraint as the search index is always static.`StaticQuery` has two important props – `query` and `render`. `query` accepts a `graphql` query, while `render` tells the component what to render with the data from that query. In this particular instance, we are querying for the elasticlunr `index`, and then rendering our `Search` component using that `data`, passing `index` as a prop. -
现在我们已经完成了搜索功能,重新启动你的开发服务器。你应该会看到我们网站的页眉现在包含了我们的
Search组件。尝试输入几个字符并点击其中一个结果。你应该会被导航到相应的页面。
通过调整解析器和使用这里概述的方法和工具,我们可以将不同类型的页面添加到结果中,以创建真正的全站搜索。
摘要
在本章中,你学习了如何使用可重用的模板编程创建页面。你应该有信心现在可以使用任何 GraphQL 数据源创建页面。我们已经实现了一个带有分页的博客文章列表、博客页面、标签页面,并为博客文章创建了一个即使在离线状态下也能工作的网站搜索功能。
在下一章中,我们将掌握将图片添加到我们的 Gatsby 网站中的艺术。首先,我们将了解为什么导入图片并不那么简单,然后再创建那些渐进式加载且性能良好的图片。
第五章:与图片一起工作
在本章中,你将掌握将图片添加到你的 Gatsby 网站中的技巧。首先,我们将简要了解网络图片的历史,然后理解为什么导入图片并不像你想象中那么简单。接下来,我们将学习创建渐进式加载且性能优异的图片。
在本章中,我们将涵盖以下主题:
-
网络上的图片
-
StaticImage组件 -
GatsbyImage组件 -
覆盖
gatsby-plugin-image的默认设置 -
从 CMS 获取图片
技术要求
要完成本章,你需要完成 第三章,从任何地方获取和查询数据。
本章的代码可以在 github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter05 找到。
网络上的图片
你上次访问没有任何图片的网站是什么时候?你可能觉得这是一个很难回答的问题。图片是网站和我们的浏览体验的关键部分。我们使用图片来展示标志、产品、个人资料和营销,通过视觉媒介传达信息、吸引或激发情感。虽然图片在这些用例(以及更多用例)中非常出色,但它们是页面大小最大的单一贡献者。根据 HTTP Archive (httparchive.org/),桌面上的平均页面大小为 2,124 KB。图片占用了 973 KB 的空间,大约占总页面大小的 45%。
由于图片对我们浏览体验的至关重要性,我们无法摒弃它们。但当它们占据了页面大小的很大一部分时,我们应该尽我们所能确保它们得到优化、可访问,并且尽可能高效。较新版本的浏览器(包括 Chrome)内置了 响应式图片 功能。浏览器不再提供单个图片源,而是可以接受一个源集。浏览器使用这个集来根据设备的大小加载不同尺寸的图片。这确保浏览器永远不会加载比可用空间大的图片。开发者可以通过 懒加载 来优化图片,特别是在 React 中。懒加载是将图片的加载推迟到稍后的时间点的过程。这可能是初始加载之后,或者当它们在屏幕上变得可见时。使用这种技术,你可以提高你网站的速度,更好地利用设备的资源,并减少用户的流量消耗。
Gatsby 中的图片
手动创建包含懒加载图片的高性能网站本身就是一个项目。幸运的是,Gatsby 有一个插件,当你生成响应式、优化后的图片时,它可以减轻你的痛苦——gatsby-plugin-image。
此插件包含两个具有特定功能的 React 图像组件,旨在在使用图像时为开发者和网站访客提供更好的用户体验:
-
加载设备查看网站的正确图像。
-
在图像加载时保持其位置,从而减少累积布局偏移。
-
使用懒加载策略来加快网站初始加载时间。
-
在图像加载时提供了多个占位符图像选项。你可以使图像模糊,或者使用图像的可缩放矢量图形(SVG)作为替代。
-
如果浏览器支持,支持新的图像格式,如WebP。
在下一节中,我们将开始探讨本章的两个图像组件中的第一个——StaticImage组件。
StaticImage组件
当图像始终保持不变时,StaticImage组件使用最佳。它可能是你的网站标志,它在所有页面上都是相同的,或者是在博客文章末尾使用的个人照片,或者是在主页的英雄部分,或者在任何不需要动态图像的地方。
与大多数 React 组件不同,StaticImage组件在传递 props 时有某些限制。它不会接受和使用其父组件的任何 props。如果你需要这种功能,你将想要使用GatsbyImage组件。
为了了解我们如何利用StaticImage组件,我们将在主页的英雄部分实现一个图像:
-
在你的
src文件夹旁边创建一个assets文件夹。为了保持整洁,将图像与源代码分开是一个好的做法。我们将使用assets文件夹来存放任何视觉资产。 -
在
assets文件夹内创建一个名为images的文件夹。我们将使用这个文件夹来存储图像类型的视觉资产。 -
在你的
images文件夹中添加一个图像。图像文件类型必须是.png、.jpg、.jpeg、.webp或.avif格式。 -
安装
gatsby-plugin-image及其依赖项:npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-source-filesystem这些依赖项会生成其他 node 进程,与之前的安装相比,可能需要更长的时间来安装。
-
更新你的
gatsby-config.js文件,使其包含三个新的依赖项:plugins: [ 'gatsby-plugin-image', 'gatsby-plugin-sharp', 'gatsby-transformer-sharp', // Other plugins ], -
将
StaticImage组件导入到你的index.js文件中:import { StaticImage } from "gatsby-plugin-image"; -
在页面的渲染中使用
StaticImage组件:<StaticImage src="img/sample-photo.jpeg" />src属性应该是从当前文件到图像的相对路径,不是根目录。 -
通过 props 修改通过渲染的图像:
<StaticImage src="img/sample-photo.jpeg" alt="A man smiling" placeholder="tracedSVG" layout="fixed" width={400} height={600} />让我们详细看看这些 props:
-
src:此属性是从当前文件到图像的相对路径。 -
alt:就像使用正常的img标签一样,确保包含一个带有描述图像的alt属性,以便你的图像保持可访问性。 -
Placeholder:这个属性告诉组件在图像加载时显示什么。在这里,我们将其设置为tracedSVG,它使用由追踪图像创建的占位符 SVG 来填充图像将加载的位置,同时也给用户一个关于照片中形状的感觉。其他选项包括blurred、dominantColor和none。 -
layout:这个属性决定了插件输出图像的大小及其缩放。在这里,我们将其设置为fixed——这意味着图像在渲染时将保持一致的大小。其他布局选项包括constrained,它接受最大高度或宽度并可以缩小,以及fullWidth,它也会缩放以适应容器,但不受最大高度或宽度的限制。 -
width和height:我们可以使用这些属性来指定图像的宽度和高度。小贴士
StaticImage也可以将其src属性作为远程源。任何指定为 URL 的图像将在构建时下载并缩放。使用远程图像而不是本地图像是保持你的存储库小的一种好方法,但应该记住,如果图像在存储库之外,Gatsby 并不知道该图像何时发生变化。如果远程服务器上的图像被更改,它只有在重新构建你的项目时才会更新。
-
现在我们已经了解了如何利用StaticImage组件,让我们将注意力转向gatsby-plugin-image的另一部分,并了解GatsbyImage组件。
GatsbyImage 组件
如果你需要使用动态图像,例如嵌入在 Markdown 内容中的图像,那么你可以使用GatsbyImage组件。
让我们使用GatsbyImage组件为我们的 Markdown/MDX 博客文章添加英雄图像:
-
安装
gatsby-transformer-sharpnpm 包:npm install gatsby-transformer-sharp -
将一些图像添加到
assets/images中,用作你博客文章的封面——每篇文章一个。 -
更新你的
Gatsby-config.js文件,使其包含你的assets源:{ resolve: 'gatsby-source-filesystem', options: { path: '${__dirname}/assets/images', }, },与
StaticImage不同,GatsbyImage要求图像被摄入我们的数据层。我们可以使用gatsby-source-filesystem插件来实现这一点,但需要提供我们图像的路径。 -
对于每篇博客文章,修改文章文件的
frontmatter,使其包含一个包含图像相对路径的hero键:--- type: Blog title: My First Hackathon Experience desc: This post is all about my learnings from my first hackathon experience in London. date: 2020-06-20 placeholder Markdown with a hero key added. Be sure to replace the relative path in this example with the one to your image. -
在
src/templates/blog-page.js文件的顶部添加GatsbyImage组件和getImage函数作为导入:import { GatsbyImage, getImage } from "gatsby-plugin- image"; -
修改文件的页面查询以引用新的图像:
export const pageQuery = graphql' query($slug: String!) { blogpost: markdownRemark(fields: { slug: { eq: $slug } }) { frontmatter { date title tags gatsbyImageData function looks very similar to the props of the StaticImage component that we saw in *Step 8* of the previous section. In this instance, we are using the BLURRED placeholder for the image, which uses a blurred, lower-resolution version of the image in place of the original image while it is loading. We are now able to retrieve the hero data from the component as it is included in the page query. -
使用
data属性中的新数据来获取组件内的图像:const { blogpost: { frontmatter: { date, tags, title, hero from the data prop, and then use the getImage utility from gatsby-plugin-image to retrieve the image data that's required to render it and assign it to a const. -
在你的
return语句中渲染你的图像:<GatsbyImage image={const defined in *Step 7* to render the image within a GatsbyImage component. Be sure to provide it with alt text to keep your image accessible – you could provide this via frontmatter as well if you wish. -
启动或重新启动你的开发服务器,欣赏你的辛勤工作。导航到一篇博客文章,你应该会看到你的图像优雅地模糊。
进一步练习
我们已经学会了如何将图像添加到我们的博客页面,那么为什么不利用你所学的知识,将同一图像的较小版本添加到博客预览页面呢?如果您想了解如何实现,可以在本书的 GitHub 存储库中找到实现方法(
github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-3/tree/main/Chapter05)。
您可能会发现自己需要在整个网站上的图像中添加相同的配置。让我们找出我们如何使用默认值来保持我们的代码以不要重复自己(DRY)的形式。
覆盖 gatsby-plugin-image 默认值
为了保持一致的外观和感觉,你可能已经在两个图像组件的许多实例中包含了相同的道具。如果你的网站图片很多,保持这些图像组件更新可能是一项单调的任务。相反,你可以在gatsby-plugin-sharp的选项中配置默认值:
{
resolve: 'gatsby-plugin-sharp',
options: {
defaults: {
formats: ['auto', 'webp'],
placeholder: 'blurred'
quality: 70
breakpoints: [300…]
backgroundColor: 'transparent'
tracedSVGOptions: {}
blurredOptions: {}
jpgOptions: {}
pngOptions: {}
webpOptions: {}
avifOptions: {}
}
}
},
让我们详细看看这些选项:
-
formats:插件生成的文件格式。 -
placeholder:覆盖临时图像的样式。 -
quality:创建的默认图像质量。 -
breakpoints:用于全宽图像的宽度。它永远不会创建一个宽度超过源图像的图像。 -
backgroundColor:图像的默认背景颜色。 -
tracedSVGOptions和blurredOptions:在占位符图像类型与全局默认值不同的情况下,用于占位符图像类型的默认选项。 -
jpgOptions、pngOptions、webpOptions和avifOptions:在占位符图像类型与全局默认值不同的情况下,用于图像类型的默认选项。
我们现在对gatsby-plugin-image包有了很好的理解。然而,使用此包与其他来源(如内容管理系统(CMS))结合使用时,还有一些重要的细分市场——让我们来看看。
从 CMS 获取图像
并非总是实际在您的存储库中存储图像。您可能希望其他人能够在不修改代码的情况下更新或添加图像到您的网站。在这些情况下,通过 CMS 提供图像更为可取。尽管如此,我们仍然需要使用 Gatsby 图像插件,因为我们希望无论来源如何,我们的图像都能有良好的性能。为了了解我们如何通过 CMS 集成图像,让我们用一个例子来说明。让我们使用gatsby-plugin-image和 CMS 在我们的关于页面添加一个个人资料图像。
重要提示
由于市场上存在大量的无头 CMS,我们将继续关注在第三章的从无头 CMS 获取数据部分中提到的两个:GraphCMS 和 Prismic。
以下两个部分将假设你已经安装了 CMS 的依赖项,并通过你的 gatsby-config.js 文件集成了 CMS。请只实现以下之一。
从 GraphCMS 获取图片
通过对我们的配置和查询进行一些小的修改,我们可以确保我们可以从 GraphCMS 获取使用 gatsby-plugin-image 的图片,并以与本地获取的图片相同的方式在网站上加载:
-
导航到 GraphCMS 的网站 (graphcms.com) 并登录。
-
导航到你的项目资产并点击 upload 按钮。
-
将你希望使用的本地图片拖放到页面上。请务必注意文件的名称,因为我们稍后会需要它。
-
发布资产。
-
修改你的
gatsby-source-graphcms插件:{ resolve: 'gatsby-source-graphcms', options: { endpoint: process.env.GRAPHCMS_ENDPOINT, gatsby-source-graphcms plugin's options to include the downloadLocalImages option, the plugin will download and cache the CMS's image assets within your Gatsby project. -
修改你的关于页面上的
query,使其包含graphCmsAsset源:export const query = graphql' { markdownRemark(frontmatter: { type: { eq: "bio" } }) { html } gatsbyImageData within our query and can make use of any of the configuration options that it supports. Here, we are specifying that the image should be full width. -
在
src/pages/about.js文件的顶部添加GatsbyImage组件和getImage函数作为导入:import { GatsbyImage, getImage } from "gatsby-plugin- image"; -
使用
data属性中的新数据来获取组件内的图片:const { markdownRemark: { html }, graphCmsAsset: { localFile }, } = data; const profileImage = getImage(localFile);首先,从
data属性中检索graphCmsAsset,然后使用gatsby-plugin-image的getImage工具检索渲染图片所需的数据。最后,将其分配给名为profileImage的const。 -
在你的
return语句中渲染你的图片:return ( <Layout> <div className="max-w-5xl mx-auto py-16 lg:py-24 text-center"> const parameter that we defined in *Step 8* to render the image within a GatsbyImage component. Be sure to provide it with alt text to keep your image accessible – you could provide this via frontmatter as well if you wish. -
重新启动你的开发服务器,并通过导航到你的 about 页面来欣赏你的辛勤工作。
现在我们已经了解了如何在 GraphCMS 中获取图片,让我们来看看如何在 Prismic 中实现同样的效果。
从 Prismic 获取图片
通过对我们的配置和查询进行一些简单的更改,我们可以使用 gatsby-plugin-image 从 Prismic 获取图片,并以与本地图片相同的方式在网站上加载它们:
-
导航到 Prismic 的网站 (prismic.io) 并登录。选择你的现有存储库。
-
导航到 CustomTypes 选项卡,点击 Create new 按钮,并选择 Single Type。将你的类型命名为 Profile 并提交。
-
使用 build-mode 侧边栏(在右侧),将一个图片组件拖入类型中。
-
将字段命名为 photo;相应的 API ID 应该会自动填充。点击 OK 以确认。如果你操作正确,你的个人资料类型应该看起来如下所示:![图 5.1 – Prismic 个人资料类型
![图 5.1 – Prismic 个人资料类型
图 5.1 – Prismic 个人资料类型
-
保存文档。
-
导航到 JSON 编辑器并将内容复制到你的
src/schemas文件夹中名为profile.json的新文件中。 -
导航到 Documents 选项卡并点击 Create new 按钮。如果 Prismic 没有自动打开你的新类型,请选择 Profile。使用界面,在 photo 字段中上传新的图片到文档中。
-
保存并发布新文档。我们现在已经在 CMS 中设置了一切,可以返回到我们的 Gatsby 项目。
-
在你的
gatsby-config.js文件中更新gatsby-source-prismic的配置:{ resolve: "gatsby-source-prismic", options: { repositoryName: "elevating-gatsby", schemas: { icebreaker: require("./src/schemas/icebreaker.json"), schemas that we added in *Step 6*. We will also add the shouldDownloadFiles option. This is a function that determines whether to download images. In our case, we always want it to download images so that we can use gatsby-plugin-image, and therefore set the function to always return true. -
在
src/pages/about.js文件的顶部添加GatsbyImage组件和getImage函数作为导入:import { GatsbyImage, getImage } from "gatsby-plugin- image"; -
修改您的关于页面的
query,使其包括prismicProfile源:export const query = graphql' { markdownRemark(frontmatter: { type: { eq: "bio" } }) { html } prismicProfile { data { photo { localFile { childImageSharp { gatsbyImageData(layout: FULL_WIDTH) } } } } } } ';我们在
query中使用gatsbyImageData并可以利用它支持的任何配置选项。在这里,我们指定图像应全宽显示。 -
使用
data属性中的新数据在组件内获取图像:const { markdownRemark: { html }, prismicProfile: { data: { photo: { localFile }, }, }, } = data; const profileImage = getImage(localFile);首先,从
data属性中检索prismicProfile数据,然后使用gatsby-plugin-image中的getImage实用工具来检索渲染图像所需的图像数据,并将其分配给名为profileImage的const。 -
在您的
return语句中渲染您的图像:<GatsbyImage image={profileImage} alt="Your alt text" className="mx-auto max-w-sm" />使用在步骤 12中定义的
const参数在GatsbyImage组件中渲染图像。确保提供alt文本以保持图像的可访问性——如果您愿意,也可以通过frontmatter提供。 -
重新启动您的开发服务器,通过导航到您的关于页面来欣赏您的辛勤工作。
您现在应该能够舒适地使用图像并通过 Prismic CMS 获取图像。虽然我们只看了两个 CMS,但这些概念可以应用到任何支持图像的无头 CMS 中。现在,让我们总结一下本章所学的内容。
摘要
在本章中,我们学习了网页上的图像以及它们对我们浏览体验的重要性。我们学习了gatsby-plugin-image并实现了其中包含的两个图像组件。我们还学习了在什么情况下使用哪个组件。
到目前为止,您应该能够舒适地开发各种类型的 Gatsby 网站页面。在下一章中,我们将开始探讨如何将我们的开发网站上线。我们将从查看搜索引擎优化开始这一旅程。
第二部分:上线
在这部分,我们将逐步从开发工作过渡到使我们的网站准备上线。到本部分结束时,您应该已经将网站部署上线。
在这部分,我们包括以下章节:
-
第六章, 提高您网站的搜索引擎优化
-
第七章, 测试和审计您的网站
-
第八章, 网站分析和性能监控
-
第九章, 部署和托管
第六章:提升你的网站搜索引擎优化
在本章中,你将了解搜索引擎优化(SEO)是如何工作的,搜索引擎在你的网站页面中寻找什么,以及如何提高你的网站在网上的存在感。我们还将深入了解其他元数据的使用,以制作视觉吸引人的社交分享预览。到本章结束时,你将创建一个可重复使用的 SEO 组件,为每个页面提供元信息。我们还将创建一个站点地图,以便搜索引擎更容易理解我们的网站。最后,我们还将学习如何阻止你的网站在搜索引擎中显示,如果你不希望它公开可见。
在本章中,我们将涵盖以下主题:
-
介绍 SEO
-
创建 SEO 组件
-
探索元预览
-
了解 XML 站点地图
-
隐藏你的网站不被搜索引擎收录
技术要求
为了完成本章,你需要已经完成了第五章,与图像一起工作。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter06找到。
介绍 SEO
SEO 是提高搜索引擎如 Google、Bing 和 Yahoo 推荐你的网站内容作为给定查询或问题的最佳结果的实践。
重要提示
在本章中,你将了解什么是 SEO,为什么它很重要,以及如何实现包含提升 SEO 排名组件的页面。SEO 是一个庞大的主题,本书无法全面涵盖。因此,我们鼓励你将本章学到的知识通过研究来进一步拓展。
Google 将是本章关注的搜索引擎。Google 在全球搜索引擎市场占有 92%的份额。与其他所有搜索引擎合并,市场份额不到 10%,毫无疑问,Google 正在主导这个领域。正因为如此,将本章内容针对 Google 是合乎逻辑的。
如果你希望搜索引擎推荐你的内容,有三个任务需要协同工作:
-
确保你的内容可以被搜索引擎的网络爬虫发现。
-
向搜索引擎展示你是一个值得信赖的信息来源。
-
通过优秀的 UX、内容层次结构和多媒体,使你的内容对用户友好且吸引人。
通过投入时间实施和改进这三件事,搜索引擎将给你最宝贵的流量形式——有机流量。最好的部分?它是免费的!当 Google 将你的网站作为结果页面的一部分展示时,你不需要为其排名或点击付费。
那么,这对搜索引擎有什么好处呢?广告和赞助商。每当搜索用户在谷歌上搜索时,你也会看到来自付费赞助商的结果,偶尔还有个性化广告。这就是搜索引擎赚钱的方式,为了保持收入流的稳定,他们需要你不断回来。为了做到这一点,他们需要确保为你提供最佳的内容,以便你在下一次搜索时再次使用他们。
现在我们已经了解了搜索引擎在寻找什么,让我们来了解一下我们可以提供给搜索引擎的重要页面信号,以帮助他们对我们网站进行排名。
页面搜索引擎优化
页面信号是搜索引擎可以从网站页面获取的信号。由于你的网站页面在你的控制之下,提高页面信号是最容易做对的事情,也是影响网站排名的最简单方式。因此,我们将在本章中花费大部分时间来提高这些信号。
页面信号可以分为两组——技术信号和内容信号。
技术信号
技术信号是与你的网站代码相关的那些信号:
-
速度:搜索引擎希望用户能够快速收到他们的结果,因此快速加载的页面在排名上会有提升。
-
移动响应性:如今,大多数网络内容都是通过移动设备消费的,因此拥有良好的移动用户体验非常重要。搜索引擎越来越重视这一点,现在谷歌的索引已经以移动优先为原则。
-
安全性:确保你的网站安全可以提高你网站的信誉。例如,HTTPS 网站比 HTTP 网站有优势。
内容信号
内容信号是与你网站页面上的副本和链接相关的那些信号:
-
内容层次结构:标题、内容标题和页面层次结构非常重要。
-
页面内容:谷歌一直在寻找高质量和准确的内容,这些内容最终能够回答用户的查询。当你创建页面并填充它们时,请记住这一点。你的内容需要为你的网站访客提供真正的目的。
-
丰富内容:如今,谷歌不仅仅关注原始文本。网络充满了多媒体内容。谷歌正在寻找包含图片和视频而不是原始文本的内容。多媒体内容允许用户更好地与你的内容互动,因此更受青睐。
-
最近更新:如果你的页面内容有一段时间没有更改,谷歌可能会将其内容视为过时。谷歌正在积极检查你的页面内容是否最近创建的。通过确保你的内容“新鲜”,谷歌可以确信你的内容是最近的。
-
外部链接:通过引用外部内容,这告诉谷歌信息是准确的,因为它类似于多个网站上的内容。
通过查看这些信号,你可能已经开始意识到,你可以将这些因素投入大量的时间。这取决于你决定搜索引擎排名和社交媒体分享对你网站的价值,这将反过来决定你应该花多少时间来实施本章中提到的内容。
现在我们已经了解了什么是 SEO,让我们将注意力转向如何通过 SEO 组件来提高我们的页面 SEO 排名。
创建一个 SEO 组件
网上的每个网站都有元标签。元标签是提供网页摘要的文本和图像内容的片段。当有人分享你的网站或它出现在搜索引擎中时,这些数据会在浏览器中渲染。让我们创建一个 SEO 组件,以便我们可以拥有丰富的预览,吸引用户访问我们的网站:
-
安装必要的依赖项:
npm i react-helmet-async gatsby-plugin-react-helmet- asyncreact-helmet-async是一个依赖项,它管理对文档头所做的所有更改。 -
在你的
gatsby-config.js文件中包含gatsby-plugin-react-helmet-async插件:module.exports = { // rest of config plugins: [ gatsby-browser.js file so that it wraps the root element in HelmetProvider, like this (*you do not need to do this step yourself*):import React from "react";
当需要时,在 React 组件和页面中使用
head。 -
在
src/components内部创建一个名为SEO.js的新文件。这是我们将在其中创建我们的 SEO 组件的文件。 -
打开新创建的文件并添加以下代码:
import React from "react"; import { Helmet } from "react-helmet-async"; import { useStaticQuery, graphql } from "gatsby"; export default function SEO({ description, lang = "en", title }) { return ( <Helmet htmlAttributes={{ lang, }} title={title} titleTemplate={`%s · My Site`} meta={[ { name: `description`, content: description, }, ]} /> ); }在这里,我们添加了语言作为 HTML 属性。我们还添加了
title标签、标题模板和description作为元数据。如果想要有一个一致的格式,标题模板就很有用。让我们假设我们传递的标题是Home。在这种情况下,模板的最终页面标题将是Home · My Site。
你现在可以在你的页面上使用你的 SEO 组件了,让我们试试吧!我们将使用src/pages/index.js文件作为示例:
import React from "react";
import { Link, graphql } from "gatsby";
import { StaticImage } from "gatsby-plugin-image";
import Layout from "../components/layout/Layout";
import SEO from "../components/layout/SEO";
export default function Index({ data }) {
const {
site: {
siteMetadata: { name, role },
},
} = data;
return (
<Layout>
<SEO
title="Home"
description="The landing page of my website"
/>
{/* REST OF FILE */}
</Layout>
);
}
在这里,你可以看到组件嵌入在我们的首页中。我们添加了title和description属性,以确保这些可以填充 SEO 组件中包含的<title>和<description>标签。如果你现在运行gatsby develop,你应该看到标签页的标题改变以匹配你的新标题。
我们还可以使用我们的 GraphQL 数据提供title和description。在我们的博客页面模板文件(src/templates/blog-page.js)中,我们可以使用博客文章的frontmatter来填充 SEO 组件:
export default function BlogPage({ data }) {
const {
blogpost: {
frontmatter: { date, tags, title, hero, desc },
html,
},
} = data;
return (
<Layout>
<SEO title={title} description={desc} />
{/* Rest of render */}
</Layout>
)}
在这里,我们将title和desc从 Markdown 文章的frontmatter传递给 SEO 组件,以便它可以使用这些信息来生成标签。
现在我们已经设置了基础知识,我们如何增强我们的网站预览,使其在社交媒体中更具吸引力?让我们来看看。
探索元预览
如果你曾经通过 Twitter、Slack 或任何其他即时通讯服务与朋友分享过网站,你可能看到过一张漂亮的预览图片、标题和描述出现在卡片中,以使用户了解你将他们发送到何处。这是通过元标签实现的。
我们已经在我们的搜索组件中包含了一些这些(title和description元标签),但在这里,我们将实现两种其他常见类型 – OpenGraph和 Twitter 元数据。然后我们将学习如何合并和验证这些标签。
Open Graph 元数据
Open Graph是一个由 Facebook 最初设计和创建的互联网协议,其单一目的是统一和标准化网页内的元数据,以获得更好的页面内容表示。该协议通过向您的网站标题中添加特定的元标签来实现这一点。这些标签提供了有关您网站页面内容的信息。这可能包括从页面标题这样基本的信息到页面上的视频时长这样更复杂的信息。通过填充适当的字段,我们可以创建一个关于我们的网站页面外观的捆绑摘要。
我们可以通过向Helmet组件的meta属性添加 Open Graph 元标签,通过我们现有的SEO组件添加 Open Graph 元标签:
<Helmet
meta={[
{
property: `og:title`,
content: title,
},
{
property: `og:description`,
content: description,
},
{
property: `og:type`,
content: `website`,
},
{
property: `og:image`,
content: `your-image-url.com`,
}
]} />
在这里,我们正在为内容的title、description、type和image实现 Open Graph 标签。正如您所看到的,所有 Open Graph 标签都有og前缀。这些只是通过协议可用的元标签中的一小部分。
要查看所有可用类型的完整列表,请访问 Open Graph 协议网站(ogp.me)。
Twitter 元数据
与 Facebook 一样,Twitter 也决定创建自己的元标签,如 Open Graph。所有 Twitter 标签都使用twitter前缀而不是og。与 Open Graph 标签区分开来的一点是,Twitter 在其平台上还有一个用于内容显示格式的标签。第一种类型是summary:

图 6.1 – Twitter 摘要卡片
summary显示网站页面的简短摘要预览。如果您正在寻找带有图像预览的更大内容,可以使用summary_large_image类型代替:

图 6.2 – 带有大型图像的 Twitter 摘要卡片
如您所见,这种类型显示了一个更大的图像,对用户来说更具吸引力。
我们可以通过向Helmet组件的meta属性添加 Twitter 元标签,通过我们现有的SEO组件添加 Twitter 元标签:
<Helmet
meta={[
{
name: `twitter:card`,
content: `summary_large_image`,
},
{
name: `twitter:creator`,
content: twitter,
},
{
name: `twitter:title`,
content: title,
},
{
name: `twitter:description`,
content: description,
},
{
name: `twitter:image`,
content: `your-image-url.com`,
}
]} />
虽然许多标签都是不言自明的,但值得指出的是twitter:creator标签。如果您将您的 Twitter 用户名作为此属性的值,Twitter 将能够识别您为网站的创建者。
现在我们已经实现了 Open Graph 和 Twitter 元标签,让我们将它们合并和合并。
合并标签
您可能已经注意到,我们在通过 Twitter 标签和 Open Graph 标签提供的数据之间存在一些重复。例如,在两种情况下,我们都在提供标题(twitter:title和og:title)。包含这些重复内容并没有什么害处。包含这种冗余只会为您的页面增加几个字节。
但如果您希望保持整洁,减少标签数量是可能的。Twitter 会抓取您的网站页面——如果它找不到它正在寻找的 Twitter 标签,并且如果存在,它将回退到 Open Graph 标签。这对于标题和描述等重复项来说很好,但仍然重要的是要包含那些特定的 Twitter 标签,例如卡片类型。
现在,让我们将我们在前两个部分中找到的 Open Graph 标签和 Twitter 标签合并,以创建一个既服务于这两种格式又没有冗余的子集:
<Helmet
meta={[
// Twitter MetaData
{
name: `twitter:card`,
content: `summary_large_image`,
},
{
name: `twitter:creator`,
content: twitter,
},
// Open Graph MetaData
{
property: `og:title`,
content: title,
},
{
property: `og:description`,
content: description,
},
{
property: `og:type`,
content: `website`,
},
{
property: `og:image`,
content: `your-image-url.com`,
},
]}
/>
在这里,我们可以看到我们已经完全省略了 title、description 和 image 的 Twitter 标签,因为它们将回退到 Open Graph 标签。然而,我们保留了 creator 和 card 的 Twitter 标签,因为它们不通过 Open Graph 提供。
现在我们已经了解了如何使我们的网站在分享时看起来很棒,那么当它被搜索引擎分享时呢?我们如何突出我们希望它关注的信息?
验证标签
无论您是否实现了 Open Graph 标签、Twitter 标签或两者都实现,您在将网站页面在线分享之前都希望确保您的标签能够正确工作。Facebook 和 Twitter 都创建了应用程序来预览在它们平台上分享的链接将如何显示:
-
Twitter 卡片验证器:
cards-dev.twitter.com/validator -
Facebook 分享调试器:
developers.facebook.com/tools/debug
这些工具执行非常相似的功能——它们会抓取输入的网页上的任何相关元标签,您已经定义了这些标签。然后,它们使用这些标签在其平台上显示网站预览的样子。还有一些第三方服务可以同时验证这两个平台,例如 MetaTags.io (metatags.io)。
小贴士
这里提到的验证器仅适用于托管在公共服务器上的网站。您必须首先部署您的网站,才能使用这些工具测试您的元标签。您将在 第九章 中了解更多关于如何部署您的网站的信息,部署和托管。
到目前为止,你应该已经熟悉了创建和测试元标签。现在,让我们关注如何使我们的网站更容易被网络爬虫解释。
了解 XML 站点地图
站点地图是一个特殊的文件,它提供了关于您网站上网页和文件的信息,以及它们之间的关系。创建此文件允许网络爬虫在无需手动爬取网站的情况下收集有关您网站的信息。它帮助我们向搜索引擎突出显示我们特别希望他们查看的页面。让我们为我们的网站创建一个站点地图:
-
安装
gatsby-plugin-sitemap依赖项:npm install gatsby-plugin-sitemap -
更新您的
gatsby-config.js文件:module.exports = { siteMetadata: { siteUrl: `https://your.website.com`, }, plugins: [ `gatsby-plugin-sitemap`, // other plugins ] }通过包含这个插件,Gatsby 将在构建网站时自动创建一个网站地图。重要的是要记住,这个插件仅在运行在生产环境中时生成输出。当你使用
gatsby develop时,你将看不到你的网站地图文件被创建。只有当运行gatsby build命令时,才会创建网站地图文件。
现在我们已经遵循了那些步骤,让我们验证我们的实现。运行gatsby build && gatsby serve,然后导航到你的网站上的/sitemap/sitemap-index.xml。这个页面应该会显示一些类似以下的XML数据:
<sitemapindex
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://your.website.com/sitemap/sitemap-0.xml</loc>
</sitemap>
</sitemapindex>
这个页面是你的网站地图的索引,告诉搜索引擎在哪里找到你的网站数据。在你的情况下,你可能会看到一个单独的条目,类似于前面代码块中显示的。如果你遵循<loc>标签中的路径(/sitemap/sitemap-0.xml),你会找到类似以下的内容:
<urlset>
<url>
<loc>https://your.website.com/blog</loc>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc> Post/</loc>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
…
</urlset>
在这个列表中,你应该会看到网站上每个页面的条目,包括changefreq和priority。如果网站页面出现在这个列表中,那么谷歌可以找到关于你页面的信息,而无需手动爬取你的网站。恭喜你!
重要提示
你的网站的 404 页面及其开发变体始终被排除在网站地图之外,所以你不必担心过滤掉这些页面。
谷歌已经为验证你的网站地图以及其他搜索分析构建了一个很好的工具——谷歌搜索控制台。你可以用它来检查你网站的索引状态,并根据哪些查询驱动流量到你的网站来优化你网站的可见性。你可以通过访问search.google.com/search-console/about来尝试它。
我们现在已经了解了如何让谷歌轻松找到并显示我们的网站,但如果我们想做的相反,让我们的网站不在搜索引擎中显示呢?我们将在下一节中探讨这个问题。
隐藏你的网站不被搜索引擎发现
为了防止你的页面出现在谷歌和其他搜索引擎中,你必须更新页面的<head>属性,使其包含以下元标签:
<meta name="robots" content="noindex">
通过包含一个noindex元标签,爬虫在爬取该页面并看到该标签时,会将其从搜索结果中删除。这无论网站是否被链接到互联网上的其他网站都会发生。
就像我们的SEO组件一样,我们可以在组件中添加这个功能,以便在需要时跨页面重用:
-
在
src/components/layout目录下创建一个名为NoRobots.js的新文件。 -
打开新创建的文件,并添加以下代码到其中:
import React from "react"; import { Helmet } from "react-helmet-async"; export default function NoRobots() { return ( <Helmet meta={[ { name: `robots`, content: "noindex", }, ]} /> ); }这个组件在包含在任何页面上时,会在
head中添加noindex元数据。以这种方式使用这个组件,我们可以在隐藏一些页面同时仍然允许其他页面被索引的情况下,获得灵活性。
要隐藏您网站中的静态资源,如图片,以避免被搜索引擎索引,我们需要包含一个robots.txt文件。这个文件被搜索引擎爬虫用来确定它可以访问您网站的哪些部分。有一个名为gatsby-plugin-robots-txt的插件已经设置好了,使得创建这个文件变得简单易行。现在让我们来实现这个插件:
-
安装
gatsby-plugin-robots-txt依赖项:npm install gatsby-plugin-robots-txt -
使用以下代码更新您的
gatsby-config.js文件:module.exports = { siteMetadata: { siteUrl: `https://your.website.com`, }, plugins: [ /static folder. The /static folder is where site images are stored. Adding this disallow policy will stop your images from appearing in a Google image search. They will only be found within your site pages.
我们现在清楚地了解了如何从搜索引擎中排除页面和资源。
摘要
在本章中,我们学习了 SEO 是什么以及搜索引擎用来识别优质内容的信号。我们创建了一个SEO组件,我们可以用它来为我们的网站页面添加元信息。然后,我们通过添加 Open Graph 和 Twitter 元标签来增强它,以便在社交媒体平台上获得更好的网站预览。我们还实现了一个网站地图,以帮助搜索引擎有效地索引我们的网站。最后,我们学习了如何使我们的网站从搜索引擎中隐藏。
在下一章中,我们将学习如何测试和审计我们的网站。我们还将探索如何审计我们网站页面的 SEO。通过学习如何审计我们的网站页面,我们可以找到提高页面速度的方法,这也会提升我们的 SEO 排名。
第七章: 测试和审计您的网站
在本章中,我们将了解单元测试是什么,为什么它有用,以及如何开始对您的 Gatsby 网站进行单元测试。然后我们将学习如何使用 Git 钩子在运行常见的 Git 命令时触发单元测试和其他命令。接着,我们将研究如何测量核心 Web 指标,以了解我们的 Gatsby 网站页面体验在实验室和现场环境中的表现情况。到本章结束时,您应该能够舒适地分析 Gatsby 网站在本地运行得如何,通过使用单元测试并在网站上查看 Web 指标。
在本章中,我们将涵盖以下主题:
-
探索单元测试
-
为测试添加 Git 钩子
-
审计核心 Web 指标
技术要求
要完成本章,您需要完成第六章,提高您的网站搜索引擎优化。您还需要安装 Google Chrome。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter07找到。
探索单元测试
单元测试是一种测试您在应用程序中逻辑上定义的最小代码片段的方法。在单元测试期间,我们将一小部分代码隔离出来,并验证它是否独立于代码库的其余部分按预期行为。我们实例化这段代码,调用它,然后观察其行为。如果观察到的行为与我们预期的相符,那么我们知道我们的代码正在做它应该做的事情。通过设置大量的这些测试,我们可以更好地了解在编辑代码库的大部分内容时,哪里出了问题。
在 React 和 Gatsby 中,有多个不同的方式可以设置单元测试。在这里,我们将关注最受欢迎的组合之一——Jest和React Testing Library。让我们在我们的仓库中创建一个结构,以便我们可以使用这些工具测试我们的网站:
-
安装必要的依赖项:
npm install -D jest babel-jest @testing-library/jest- dom @testing-library/react babel-preset-gatsby identity-obj-proxy -
创建一个
jest.config.js文件:module.exports = { transform: { "^.+\\.jsx?$": '<rootDir>/jest-preprocess.js', }, moduleNameMapper: { ".+\\.(css|styl|less|sass|scss)$": 'identity-obj- proxy', ".+\\.(jpg|jpeg|png|gif|eot|otf|webp |svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac |oga)$": '<rootDir>/__mocks__/file-mock.js', }, testPathIgnorePatterns: ['node_modules', '.cache'], transformIgnorePatterns: ['node_modules/(?!(gatsby)/)'], testEnvironment: "jsdom", globals: {__PATH_PREFIX__: '', }, setupFiles: ['<rootDir>/loadershim.js'], setupFilesAfterEnv: ['<rootDir>/jest.setup.js'] };Gatsby 和 Jest 都在底层使用 Babel。然而,与 Gatsby 不同,Jest 不处理自己的 Babel 配置。我们使用
jest.config.js文件手动设置 Jest 与 Babel,以及配置我们的测试。让我们分解这个文件的内容,以便我们了解每个部分的作用:
a.
transform:这告诉 Jest,所有以.js或.jsx结尾的文件都需要用jest-preprocess.js文件处理,我们将在下一步创建这个文件。b.
moduleNameMapper: 在测试时,测试静态资产(如图像)是不常见的。因此,Jest 不关心它们。但是,它仍然很重要,因为它需要知道如何处理它们,因为它们可能嵌入到您的代码中。在这里,我们为 Jest 提供了一个处理样式的模拟,该模拟使用我们在第一步中安装的identity-obj-proxy包,以及另一个处理常见图像、视频和音频文件的模拟。我们将在本节稍后创建第二个模拟。c.
testPathIgnorePatterns: 这告诉 Jest 忽略在node_modules中找到的任何测试,因为我们不希望引入在我们的包和.cache目录中找到的测试。d.
transformIgnorePatterns: 在这里,我们告诉 Jest 在转换代码时忽略 Gatsby,因为 Gatsby 包含未转换的 ES6 代码。e.
globals: 在这里,我们定义了一个名为__PATH_PREFIX__的全局变量,Gatsby 在幕后使用它。我们还需要在这里定义它,因为一些 Gatsby 组件如果没有它将无法正常工作。f.
setupFiles: 在这里,我们列出我们想要用于配置测试环境的配置文件。它对每个测试运行一次。在这里,我们告诉它运行我们将在本节稍后创建的loadershim.js。g.
setupFilesAfterEnv: 在这里,我们指定我们想要用于设置测试的配置文件。关键的是,这些文件在测试环境设置之后运行。 -
在您的根目录中创建一个
jest-preprocess.js文件:const babelOptions = { presets: ["babel-preset-gatsby"], }; module.exports = require("babel- jest").default.createTransformer(babelOptions);这是我们定义我们的 Babel 配置的地方。由于我们使用 Gatsby,我们使用
babel-preset-gatsby预设。根据需要,您可以扩展此预设列表。 -
在您的根目录中创建一个
loadershim.js文件:global.___loader = { enqueue: jest.fn(), }我们使用此文件使用 Jest 模拟函数模拟全局
loader.enqueue函数。 -
在您的根目录中创建一个新的文件夹,命名为
__mocks__。 -
在
__mocks__文件夹中创建一个file-mock.js文件:module.exports = "test-file-mock"如我们在 步骤 2 中提到的,此文件模拟了静态资产文件类型。
-
在
__mocks__文件夹中创建一个gatsby.js文件:const React = require("react") const gatsby = jest.requireActual("gatsby") module.exports = { ...gatsby, graphql: jest.fn(), Link: jest.fn().mockImplementation( ({ activeClassName, activeStyle, getProps, innerRef, partiallyActive, ref, replace, to, ...rest }) => React.createElement("a", { ...rest, href: to, }) ), StaticQuery: () => React.createElement("div", { id: "StaticQuery", }), useStaticQuery: jest.fn(), }在这里,我们模拟了我们从
gatsby包中使用的任何组件或函数。我们从Link组件中移除属性,并返回一个<a/>标签。我们用div替代StaticQuery组件。最后,我们还使用 Jest 模拟函数模拟了useStaticQuery函数。 -
在
__mocks__文件夹中创建一个gatsby-plugin-image.js文件:const React = require("react") const gatsbyPluginImage = jest.requireActual("gatsby- plugin-image") module.exports = { ...gatsbyPluginImage, StaticImage: () => React.createElement("div", { id: "StaticImage", }), }在这里,我们模拟了我们从
gatsby-plugin-image包中使用的任何组件或函数。我们用div替代StaticImage组件。 -
在您的根目录中创建一个
jest.setup.js文件:require('@testing-library/jest-dom/extend-expect'); const { useStaticQuery } = require("gatsby"); beforeAll(() => { useStaticQuery.mockReturnValue({ site: { siteMetadata: { siteUrl: "test.url.com", social: { twitter: "@slarsendisney" }, }, }, }); });在每个测试之前,我们需要为
useStaticQuery模拟一个返回值。任何使用 SEO 组件的页面组件将失败,除非它们可以从该函数检索此数据。 -
在您的
src目录中创建一个test-utils.js文件:import React from 'react' import {render} from '@testing-library/react' import { HelmetProvider } from "react-helmet-async"; const Wrapper = ({children}) => { return ( <HelmetProvider> {children} </HelmetProvider> ) } const customRender = (ui, options) => render(ui, {wrapper: Wrapper, ...options}) export {customRender as render}此文件不是必需的,但很有帮助。你的应用程序可能很大一部分会使用一个提供者,我们通常会在
gatsby-browser.js中将其包装在根元素中。在 Jest 中我们无法这样做。因此,我们不是在每次测试中都定义wrapper,而是更倾向于创建一个自定义的render函数,该函数将任何内容包装在所需的提供者中。然后,在需要时,我们调用这个render而不是从@testing-library/react导出的那个。 -
在你的
package.json文件中创建一个测试脚本:"scripts": { "build": "gatsby build", "develop": "gatsby develop", "start": "npm run develop", "serve": "gatsby serve", "clean": "gatsby clean", npm run test command, it will start Jest and begin testing.
现在我们已经准备好开始测试了!这本书没有足够的空间来提供一个完整的单元测试指南。然而,让我们为几种不同的组件类型创建一些示例测试,例如简单组件、SEO 组件和我们的 Gatsby 页面组件。
测试简单组件
测试简单组件可以像在任何标准的 React 项目中做的那样进行。让我们以测试我们的头部组件为例来看看。
在你的头部组件旁边创建一个 Header.test.js 文件,使用以下代码:
import React from "react";
import {render, screen} from '@testing-library/react'
import '@testing-library/jest-dom'
import Header from "./Header";
test("Renders header", async () => {
render(<Header />);
expect(screen.getByText('Site Header'))
});
在这里,我们将 Header 组件渲染到屏幕上。然后我们测试屏幕是否包含一些显示 Site Header 的文本,以确保 Header 组件已被渲染。我们通过使用 screen.getByText 函数来完成这项测试。
现在我们已经了解了如何测试简单的组件,让我们来看一个更复杂的例子——你网站的 SEO 组件。
测试 SEO 组件
在 Gatsby 页面中一个常见的组件,重要的是要测试的是 SEO 组件。确保我们使用该组件添加的元标签被正确地应用到文档的头部,这样我们就可以知道当页面被分享时,它将具有我们在 第六章,提高你的网站搜索引擎优化 中设置的丰富预览。让我们看看我们如何进行这项测试。
在你的 SEO 组件旁边创建一个 SEO.test.js 文件,使用以下代码:
import React from "react";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom";
import { HelmetProvider } from "react-helmet-async";
import SEO from "../SEO";
HelmetProvider.canUseDOM = false;
test("Correctly Adds Meta Tags to Header", async () => {
const mockTitle = "Elevating React with Gatsby";
const mockDescription = "A starter blog demonstrating
what Gatsby can do.";
const context = {};
render(
<HelmetProvider context={context}>
<SEO title={mockTitle} description={mockDescription} />
</HelmetProvider>
);
const head = context.helmet;
expect(head.meta.toString()).toMatchSnapshot();
});
首先,我们通知 react-helmet-async 从 HelmetProvider 中它不能使用 SEO 组件。渲染后,我们检查上下文中的 helmet 对象是否包含 meta,如果包含,我们确保它与快照匹配。
现在,让我们了解如何测试整个网站页面。
测试 Gatsby 页面组件
如果你想要测试页面,你可以使用我们在 探索单元测试 部分的 第 10 步 中设置的定制 render 函数。让我们以测试我们网站的首页为例来看看。
与我们的组件测试不同,最好避免将页面测试放在与页面文件相同的目录中。这是因为 Gatsby 会自动尝试在 pages 目录中为每个导出的 React 组件创建页面。相反,在 pages 目录旁边创建一个名为 pages-lib 的文件夹,专门用于 Gatsby 页面测试。
在 pages-lib 目录中创建一个 index.test.js 文件,使用以下代码:
import React from "react";
import { screen } from "@testing-library/react";
import {render} from "../../test-utils"
import "@testing-library/jest-dom";
import Index from "../pages/index";
test("Renders Index Page with correct name", async () => {
const data = {
site: {
siteMetadata: { name: "My Name", role: "My Role" },
},
};
render(<Index data={data} />);
expect(screen.getByText(data.site.siteMetadata.name));
});
在这种情况下,我们正在使用在test-utils.js文件中设置的定制渲染函数。这是因为页面组件通常还包含一个SEO组件,它使用Helmet组件,因此需要包裹在HelmetProvider中。同时,也很重要将页面通常通过 GraphQL 检索的任何数据传递给data属性,因为页面上的 GraphQL 查询将不会运行。
现在我们已经了解了如何编写测试,让我们了解如何通过 Git 钩子触发它们。
添加测试的 Git 钩子
push我们的代码,如果它们失败,我们可以停止推送。通过实现这个功能,被推送的代码不太可能破坏我们测试的任何功能。
让我们通过创建一个由git push触发的 Git 钩子来实现这个功能。这将确保在允许push命令运行之前,我们的单元测试已经通过。我们将使用husky包来完成这项工作,因为它易于设置和维护:
-
安装必要的依赖项:
npm install husky --save-dev -
在你的
package.json文件中创建一个postinstall脚本,使用以下命令:npm set-script postinstall "husky install"此命令将在我们的
package.json文件中添加一个新的脚本,称为postinstall,这将导致安装husky。 -
运行这个新脚本:
npm run postinstall由于我们第一次设置此功能,我们需要通过命令行手动触发
postinstall脚本来手动安装husky。后续的每个开发者将永远不需要手动运行此操作。 -
添加一个钩子:
npx husky add .husky/pre-push "npm run test"这添加了一个
pre-push钩子,它会运行我们的 npmtest脚本。运行此命令后,后续的每次推送都会运行test脚本,并且只有在成功的情况下才会进行推送。重要提示
在推送时运行测试并不总是最好的测试方式。我们可能在本地的未提交代码中存在导致测试通过,但未包含在推送中的代码。这可能导致在持续集成/持续部署(CI/CD)环境中相同的测试失败。
现在我们已经了解了如何通过 Git 钩子触发单元测试,让我们将注意力转向另一种类型的测试——审计核心 Web vitals。
审计核心 Web vitals
Web vitals (web.dev/vitals)是谷歌发起的一项倡议,旨在为提供统一指导的质量信号提供指导,这些信号对于在网络上提供良好的用户体验至关重要。这些信号直接关联到第六章中讨论的信号,即提高网站搜索引擎优化。
核心 Web vitals 是谷歌 Web vitals 中的一小部分,专注于三个支柱——页面加载速度有多快,你有多快可以与页面交互,以及页面在加载期间和用户交互期间有多稳定。这三个支柱包含在以下三个指标中:
-
最大内容渲染时间:表示加载时间的指标。它是从您开始导航到页面时,浏览器将页面的大部分内容变为可见所需的时间的度量。这是用户感知网站加载完成的时刻。
-
首次输入延迟:衡量交互响应时间。首次输入延迟是浏览器从导航到一个可以与页面上的任何元素(如表单或按钮)交互的点所需的时间。
-
累积布局偏移:衡量页面在加载过程中稳定性的指标。在页面加载时,您的元素在页面上的移动越少,您的得分就越高。
现在我们已经了解了这些核心 Web 核心指标,我们如何衡量它们?我们可以使用两种不同的方法来检索这些指标。如下所示:
-
实验室测试数据:您根据需求生成用于测试的数据。由于它基于用户数据的近似值,因此准确性较低。但在开发过程中,它通常非常有用,因为我们可以用它来迭代地开发我们的网站,而无需部署它。
-
字段数据:从查看您网站的用户那里收集的数据。这是数据最准确的数据源,因为它直接对应于您的用户如何感知您的网站。
让我们看看如何使用灯塔工具检索实验室测试数据,以及使用web-vitals包检索字段数据。
使用 Chrome 的灯塔工具
灯塔工具将分析您的网站的性能、可访问性、搜索引擎优化和渐进式 Web 应用功能。它不仅在每个类别中给出分数,还会告诉您如何改进网站以提高这些分数。最好的部分?它是内置在 Google Chrome 中的 - 不需要其他下载或工具安装。
现在,让我们使用此工具为我们网站生成一个灯塔报告:
-
使用
gatsby build命令构建您的网站。正如我们在第一章中看到的,“Gatsby.js 入门概述”,这将创建您网站的生成版本。我们审核网站的生成版本而不是开发版本至关重要,因为 Gatsby 添加到构建中的开发工具会极大地增加您网站的包大小。 -
使用
gatsby serve命令提供您的构建。默认设置下,您的网站应该已活化为 http://localhost:9000/。 -
在隐身模式下打开 Google Chrome 并导航到 http://localhost:9000/。您应该看到您网站的索引页面。通过在隐身模式下加载此页面,我们确保您安装的任何 Chrome 扩展都不会干扰测试。
-
在页面的任何位置右键单击并点击检查。这将打开窗口右侧的开发者工具。
-
点击顶部栏中央的箭头并选择灯塔工具:![图 7.1 – 开发者工具中的灯塔位置
![图片]()
图 7.1 – 灯塔在开发者工具中的位置
-
这将显示灯塔报告生成器窗口,其外观如下:![图 7.2 – 灯塔报告生成器
![图片]()
图 7.2 – 灯塔报告生成器
选择你想要审计的类别,所有这些类别默认都是开启的。所有这些类别都很重要,除非你特别想提高单个指标并希望报告生成得更快,否则建议保持所有类别开启。
你还必须选择一个设备类型。默认情况下,此设置设置为移动设备。灯塔将尝试模拟一个移动设备尝试访问页面,这包括使用较小的视口和限制网络连接。运行多个报告——每个设备类型一个——是个好主意,因为它确保你的网站在每种设备上都有良好的体验。请注意,出于 SEO 目的,谷歌在其网站排名中使用移动指标。
-
点击生成报告将启动灯塔工具。在这个过程中,你可能看到页面闪烁几次。这没有什么好担心的。恭喜你——你已经运行了你的第一个灯塔报告!
一旦灯塔运行完成,你将看到报告生成器窗口已被包含每个类别的报告所取代。让我们看看性能类别:

图 7.3 – 灯塔性能报告
你可能会在这些指标中注意到一些熟悉的项目。灯塔将其审计作为其审计的一部分,对三个核心网络指标进行了审计。每个指标都将用颜色编码,以指示你需要集中精力改进的地方。绿色表示良好,橙色表示需要改进,而红色表示该指标的得分被认为很差。在得分不理想的情况下,灯塔将提出你可以对网站进行的更改,以改善得分。让我们看看一个例子:

图 7.4 – 带有建议改进的灯塔可访问性报告
在前面的屏幕截图中,我们可以看到我们的按钮目前不可访问,因为它们没有可访问的名称。将鼠标悬停在失败的元素上将在网站中突出显示它,这样我们就可以快速纠正它。
现在我们已经了解了如何检索实验室测试数据,让我们来探讨如何使用web-vitals JavaScript 包检索现场数据。
使用web-vitals JavaScript 包
web-vitals包是由谷歌 Chrome 团队开发的一个 1KB 大小的包。此包监控网络核心指标,包括当真实用户访问你的网站时的核心网络指标。它旨在以与其他谷歌报告工具极其相似的方式测量它们。
重要提示
web-vitals 包利用了并非所有浏览器都支持的浏览器 API。该包仅保证在 Google Chrome 中提供完整支持。如果您正在使用此工具收集指标,请考虑结果只能在受支持的浏览器中检索。如果您正在汇总这些指标,请记住,它们并不一定代表您网站的所有访客。
为了了解如何在我们的应用程序中使用 web-vitals,让我们创建一个基本的示例,其中我们简单地记录用户导航到我们的网站时的核心指标:
-
安装
web-vitals包:npm install web-vitals -
创建一个利用
web-vitals包的函数:import { getCLS, getFID, getLCP } from "web-vitals"; export default async function webVitals() { try { getFID((metric) => console.log(metric)); getLCP((metric) => console.log(metric)); getCLS((metric) => console.log(metric)); } catch (err) { console.log(err); } }在这个例子中,我们检索指标,然后简单地记录到控制台。将它们包裹在
try catch块中非常重要,以避免在 API 不受支持时崩溃页面。这也允许您相应地处理错误。 -
在您的
gatsby-browser.js文件中使用以下代码:import "./src/styles/global.css" import webVitals from "./src/utils/web-vitals" webVitals()通过在此文件中调用函数,当用户从外部来源首次导航到我们的网站时,它将运行一次,但不会在网站内的每次页面导航时运行。
启动您的开发服务器并通过 Google Chrome 导航到您的网站,您应该在控制台中看到记录的指标。在这个例子中,我们只是简单地显示它们,但我们可以将它们发送到我们的分析平台。我们将在第八章“网络分析和性能监控”中更详细地探讨这一点。
我们现在对如何在现场和开发过程中测量网站核心指标有了很好的理解。
摘要
在本章中,我们学习了单元测试——它是什么以及为什么它很重要。然后,我们将单元测试集成到我们的 Gatsby 网站中。我们还查看了一些不同的单元测试配方,我们可以使用它们来测试不同类型的 React 组件。然后,我们学习了 Git 钩子,并实现了一个使用 husky 运行单元测试的 Git 钩子。最后,我们调查了网站核心指标。我们使用网站核心指标在本地使用 Lighthouse 以及在现场使用 web-vitals 包来测试我们的页面体验。通过您所学的知识,您现在应该感觉到您可以在本地测试一个网站,一旦它上线,还可以审计其性能、可访问性和 SEO。
在下一章中,我们将探讨如何将分析功能添加到我们的网站中,包括如何跟踪 web-vitals 字段数据。
第八章:网站分析和性能监控
在本章中,我们将探讨我们可以监控我们应用程序和访问它的用户行为的方法。我们将学习页面分析如何成为帮助创造更好用户体验的有价值工具。我们将学习如何使用两种不同的工具来收集这些页面洞察,具体取决于我们的需求。我们将学习在使用这些工具时我们在法律上需要做什么。我们还将实现插件,使我们能够调试用户在使用我们的网站时遇到的错误。
到本章结束时,你应该有信心能够收集不同类型的分析,并使用它们来了解自己(或网站所有者)关于代码更改如何影响用户体验的信息。
在本章中,我们将涵盖以下主题:
-
介绍网站分析
-
实现页面分析
-
监控您网站的性能
技术要求
要完成本章,您需要完成第七章,测试和审计您的网站。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter08找到。
介绍网站分析
网站分析是收集、汇总、分析和报告网站数据的行为。让我们将网站分析分为两个类别:
-
页面分析:我们收集的有关用户如何与我们的网站互动的分析。例如,这可能包括页面浏览量、点击率或跳出率。
-
性能监控:我们收集的关于我们的代码如何为用户表现的分析。这主要用于记录用户遇到的 JavaScript 错误。
无论类别如何,它们都以类似的方式工作。首先,插入的script标签将一小部分 JavaScript 加载到页面中。这段代码在访问网站的任何人的网络浏览器中运行。在大多数情况下,代码会在用户的浏览器上留下一个包含少量数据的文本文件,这些数据被称为 cookies。这些数据用于识别用户会话。这些数据连同请求信息一起发送回分析工具,以识别用户和正在跟踪的事件。
您已经收集了所有这些数据——接下来是什么?通过查看汇总的用户数据,我们可以深入了解用户的行为。当您试图改善用户体验时,这些信息是您最强大的盟友。您可以使用这些报告来识别用户喜欢的内容类型趋势或用户最常离开您的应用程序的页面等。
现在,让我们花点时间来谈谈收集用户数据时的隐私问题。
隐私
无论你打算收集什么数据,考虑用户的隐私总是很重要的。如果你打算存储、转移或处理用户的个人信息,公开并可供公众访问的隐私政策是一项法律要求。同样,流行的分析服务提供商,包括 Google Analytics,在其服务条款中规定,如果你使用他们的服务,你必须发布隐私政策。如果不这样做,你就违反了与他们的合同,并且非法使用工具。
除了隐私政策之外,如果你的网站有来自欧洲的访客,拥有一个 cookie 政策也是一个好主意。截至 2018 年,欧盟规定你必须从用户那里获得“明确、知情”的同意才能使用 cookie。这通常以横幅的形式出现,你在用户首次访问你的网站时向他们展示。
既然我们已经了解了网站分析是什么,让我们转向如何实现它们的第一种类型——页面分析。
实现页面分析
你可以使用许多工具来执行页面分析。在这本书中,我们将探讨以下两个:
-
Google Analytics
-
Fathom Analytics
Google Analytics 是页面分析的世界领导者。超过一半的网站都在使用这个工具。其受欢迎的原因之一是其历史,它自 2005 年以来一直存在。当它推出时,分析领域几乎没有竞争。另一个原因是它是免费的。重要的是要记住,如果你使用的是免费工具或网站,你的数据往往是产品。如果你对你的隐私以及网站访客的隐私感到担忧,那么 Fathom Analytics 可能是一个更好的选择。
与 Google 不同,Fathom Analytics 不跟踪个人数据。例如,当页面访问被记录时,它只会告诉你有人访问了,但不会告诉你具体是谁。Fathom 的脚本不使用 cookie,这意味着你不需要 cookie 政策或 cookie 同意横幅。由于 Fathom 致力于尽可能少地收集个人信息,你的隐私政策也可以相对简短。
重要提示
只需实现本节中提到的页面分析工具之一。拥有多个执行相同任务的脚本只会使你的页面更重。
我们将在以下章节中讨论如何实现两种页面分析工具。让我们从 Google Analytics 开始。
添加 Google Analytics
要开始跟踪我们 Gatsby 网站内的数据,我们需要从 Google Analytics 获取一个测量 ID。让我们按照以下步骤进行:
-
从你的浏览器导航到
analytics.google.com/analytics/web/?authuser=0#/provision/create。 -
为您的账户提供一个 账户名称 并输入您的 账户数据共享设置 偏好:
![图 8.1 – Google Analytics 账户设置]()
图 8.1 – Google Analytics 账户设置
名称是特定于项目的,所以给它取一个相关的名字。注意数据共享选项 – 只分享你感到舒适分享给 Google 的信息。
-
通过输入 属性名称、报告时区(区域)和 货币 来设置一个属性:
![图 8.2 – Google Analytics 属性设置]()
图 8.2 – Google Analytics 属性设置
此属性特定于你的网站或应用程序。在我们的例子中,这将用于引用 Gatsby 网站的分析,所以像 personal-website 或 my-website 这样的名字是合适的。
-
填写 Google 所需的商业信息并提交表格。然后,你将看到以下屏幕:
![图 8.3 – Google Analytics 仪表板]()
图 8.3 – Google Analytics 仪表板
这是您第一次看到 Google Analytics 仪表板。在我们能够开始利用其功能之前,我们需要设置我们的第一个数据流。
-
在 选择平台 下选择 Web。这将打开以下屏幕:
![图 8.4 – Google Analytics 网站流设置]()
图 8.4 – Google Analytics 网站流设置
在 URL 下输入你的网站地址,并为你的网站流 命名。最后,通过点击 创建流 提交表格。这将显示我们新创建的流的详细信息。
-
记录流量的测量 ID。
现在我们已经获得了测量 ID,让我们将注意力转向我们的 Gatsby 网站仓库,并使用它开始收集网站统计信息:
-
安装必要的依赖项:
npm install gatsby-plugin-google-gtag -
在你的
gatsby-config.js文件中包含gatsby-plugin-google-gtag插件:{ resolve: `gatsby-plugin-google-gtag`, options: { trackingIds: [ "GA-TRACKING_ID", // Your Measurement ID ], gtagConfig: { anonymize_ip: true }, }, },通过在你的配置中包含此插件,Gatsby 将将所需的 Google Analytics 脚本附加到你的应用程序的主体中。该插件提供了许多选项,所有这些选项都可以在这里找到:
www.npmjs.com/package/gatsby-plugin-google-gtag。重要的是要注意
anonymize_ipgtag 配置选项。在某些国家,如德国,匿名化 IP 是法律要求。在不进行任何额外配置的情况下,插件将自动发送页面浏览事件,每当你的网站路由发生变化时。
这是一个很好的开始,但你很可能会还想跟踪你网站上其他的事件。让我们看看我们如何跟踪自定义事件和外部链接。
自定义事件
除了页面浏览量之外,还有许多其他方式可以跟踪用户参与度。今天的网站越来越互动,因此能够跟踪交互非常有用。我们可以通过使用自定义事件在 Google Analytics 中实现这一点。让我们看看使用按钮点击的例子。
假设我们有一个简单的按钮组件:
import React from "react"
const Button = () => {
return (
<button>Click Me</button>
)
}
export default Button
要跟踪这个按钮的点击,我们可以利用 gtag 函数,该函数通过插件暴露在窗口中:
import React from "react"
const Button = () => {
const track = (e) => {
typeof window !== "undefined" &&
window.gtag("event", "click", { /* Meta Data */ })
}
return (
<button onClick={track}>Click Me</button>
)
}
export default Button
在前面的代码块中,您可以看到在 onClick 中,我们调用了一个 track 函数。这个函数有条件地调用 window.gtag 函数,如果 window 被定义。我们需要执行这个检查,因为这个函数在服务器端渲染时不起作用。
重要提示
此插件仅用于生产使用。这意味着在您在开发项目中工作时发生的任何事件都不会被跟踪。为了测试插件是否正常工作,您需要构建并托管网站。
现在我们已经了解了自定义事件是如何工作的,让我们看看我们如何跟踪通过外部链接离开我们网站的人。
外部链接
了解用户何时以及从哪里离开您的网站可能很有用。也许您在博客文章中引用了另一位开发者的网站,用户离开去访问那个网站?为了跟踪这种外部流量,gatsby-plugin-google-gtag 插件包含一个现成的组件 – OutboundLink。让我们看看我们如何使用它:
import React from "react"
import { OutboundLink } from "gatsby-plugin-google-gtag"
const MyLink = () => {
return (
<OutboundLink href="https://sld.codes">Visit
sld.codes.</OutboundLink>
)
}
export default MyLink
正如您在这个示例中应该能看到的那样,我们可以使用 OutboundLink 组件作为 a 标签的直接替换。正如其名称所暗示的,您应该只为此组件使用外部链接。如果链接是内部链接,您应该使用 Gatsby 的 Link 组件。
Google Analytics 是跟踪您网站页面分析的一个很好的方式,但还有许多其他工具您可以用来实现这个目的。让我们看看一个替代方案 – Fathom Analytics。
使用 Fathom 分析
Fathom Analytics 被宣传为 Google 的隐私关注替代品。当用户浏览您的网站时,Google Analytics 会收集大量数据,但 Fathom 建议获得的信息太多。Fathom 只收集他们创建单页统计仪表板所需的信息。与 Google 不同,Fathom 不是免费的,起价为每月 $14。为了在我们的 Gatsby 网站中开始跟踪数据,我们需要从 Fathom Analytics 获得一个网站 ID。让我们现在按照以下步骤进行:
-
使用浏览器导航到
usefathom.com并创建一个账户。您需要使用信用卡注册,但您将获得 7 天的免费试用。 -
在创建账户后,您应该会被提示创建一个新的网站:![Figure 8.5 – Fathom 仪表板
![Figure 8.05_B15983.jpg]()
Figure 8.5 – Fathom 仪表板
给您的网站起一个合适的名字。例如,personal-website 就是一个合适的选择。点击 创建网站。
-
提交后,您将获得您的 网站 ID 和适用于多种框架的嵌入代码,包括 Gatsby:


图 8.6 – Fathom 嵌入代码
我建议您 不要 遵循他们为 Gatsby 提供的说明。为什么?因为他们的说明建议修改 Gatsby 用于服务器端渲染 head 和其他 HTML 部分的组件,而 Gatsby 无法保证在不同版本之间保持此文件的一致性,因此干预可能会在以后升级时造成困难。相反,只需记下您的 网站 ID。然后,最小化浏览器。
现在我们已经检索到我们的 网站 ID,让我们将注意力转向我们的 Gatsby 网站存储库,并开始用统计数据填充 Fathom 仪表板:
-
安装必要的依赖项:
npm install gatsby-plugin-fathom -
在您的
gatsby-config.js文件中包含gatsby-plugin-fathom插件:{ resolve: 'gatsby-plugin-fathom', options: { siteId: 'FATHOM_SITE_ID' } } -
将
FATHOM_SITE_ID替换为您通过 Fathom 网站检索到的 网站 ID 属性。 -
构建并托管您的 Gatsby 网站。
gatsby-plugin-fathom仅用于生产环境,因此我们需要创建并托管一个生产版本以验证其是否正常工作。 -
当您的网站加载完成后,导航到它。一旦渲染完成,返回 Fathom 并点击 验证网站代码。它应该会通知您 fathom 已经全部连接好了!
现在,让我们调查如何跟踪比页面视图更多的内容。
自定义事件(目标)
与 Google Analytics 类似,Fathom 也允许您跟踪自定义事件。Fathom 将这些事件称为“目标”。要了解我们如何跟踪一个目标,让我们看看使用按钮点击的示例。
首先,我们需要通过 Fathom 创建一个事件:
-
导航到您的 Fathom 分析仪表板。在 事件 部分下,点击 添加事件。
-
给您的活动起一个名字,然后点击 创建事件。注意您的事件代码。
现在我们有了事件代码,让我们来使用它!假设我们有一个简单的按钮组件:
import React from "react"
const Button = () => {
return (
<button>Click Me</button>
)
}
export default Button
要跟踪此按钮的点击,我们可以利用通过插件公开的 useGoal 函数:
import React from "react"
import { useGoal } from "gatsby-plugin-fathom"
const Button = () => {
const handleGoal = useGoal("YOUR_EVENT_CODE")
return (
<button onClick={() => handleGoal(100)}>Click
Me</button>
)
}
export default Button
useGoal 钩子公开了一个接受单个参数的函数,该参数是您目标的价值。也许这是一个购买按钮,您希望在仪表板上记录您的收入。如果您的目标没有价值,将此参数设置为 0。
现在我们已经了解了如何跟踪页面分析,让我们看看如何通过应用程序监控在生产中监控我们的应用程序的错误。
监控您网站的性能
在生产环境中调试最困难的事情之一是您似乎无法在您的机器上复制的用户错误。没有日志,这可能是一项不可能的任务。幸运的是,有些工具专门用于监控应用程序中的错误,并在事情出错时提醒您。用于此目的最受欢迎的工具之一是 Sentry.io。
使用 Sentry.io 分析
Sentry.io 是一个全栈错误跟踪系统,支持各种桌面、浏览器和服务器应用程序——包括 GatsbyJS!Sentry 通过直接集成到我们网站的日志基础设施来工作。让我们学习如何实现 Sentry,以便我们可以监控生产中的错误:
-
通过浏览器导航到
sentry.io/signup/并创建一个账户。 -
登录后,通过导航到项目并点击创建项目来创建一个新项目。
-
填写新项目用户界面,如下所示:

图 8.7 – Sentry 初始化
选择 Gatsby 作为您的平台。由于您的网站在发布时可能规模较小,我建议将默认警报设置设置为在每一个新问题上提醒我。最后,为您的项目命名。然后,点击创建项目。
然后,Sentry 会为您提供一个很好的分步指南,说明如何在 Gatsby 项目中设置 Sentry。让我们在这里重申这些步骤:
-
安装必要的依赖项:
npm install --save @sentry/gatsby -
在您的
gatsby-config.js文件中包含@sentry/gatsby插件:{ resolve: "@sentry/gatsby", options: { dsn: "YOUR_DSN_NUMBER", sampleRate: 0.7 }, },sampleRate是发送错误事件的速率。Sentry 建议使用0.7作为默认值,这意味着 70% 的错误事件将被发送。完整的插件选项列表可以在此处找到:
docs.sentry.io/platforms/javascript/guides/gatsby/configuration/options/。
这几行代码就足以让 Sentry 开始跟踪生产中网站的错误和性能。您可以放心,如果您的网站访客遇到错误,您将立即知道。
摘要
在本章中,我们学习了网站分析以及它们如何有助于使我们的应用程序发挥最佳性能。我们学习了我们可以收集的不同类型的数据以及我们在收集用户数据时应遵守的法规。我们以两种不同的方式实现了页面分析——一种是通过提供大量数据,另一种是采取更注重隐私的立场。最后,我们还使用 Sentry.io 实现了应用程序监控。现在,您应该有信心收集网站分析。
在下一章中,我们将最终将我们在前八章中学到的所有内容结合起来,并部署我们的网站。
第九章:部署和托管
在本章中,我们将最终将我们一直在工作的项目部署出去,让全世界都能看到!我们将深入了解 Gatsby 创建的不同类型的构建,并了解如何调试常见的构建错误。在此之后,我们将继续学习如何使用各种不同的平台来部署它们。此外,我们还将发现如何通过将其作为 Express 服务器的一部分来提供服务,以锁定对网站的访问。
在本章中,我们将涵盖以下主题:
-
理解构建类型
-
常见构建错误
-
部署前的检查清单
-
用于部署混合构建的平台
-
用于部署静态构建的平台
-
使用限制用户访问权限的 Gatsby 网站提供服务
技术要求
要导航本章内容,您需要完成第八章,网络分析和性能监控。
本章中展示的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter09找到。
理解构建类型
Gatsby 版本 4 引入了两种不同的网站构建方式:
-
作为静态构建:这将在构建时使用 Node.js 创建所有页面。生成的文件都是静态的 HTML、JavaScript 和 CSS,可以完全静态地提供服务。
-
作为混合构建:这是一种静态构建与服务器端渲染的页面或通过延迟静态生成创建的页面的混合。
当运行 gatsby build 时,Gatsby 将检查您网站的内容,并在可能的情况下创建一个静态构建。然而,如果您的网站包含需要服务器端渲染的页面或通过延迟静态生成创建的页面,它将创建一个需要运行在 Node.js 服务器或通过无服务器函数上的服务器端代码的构建。这两种类型的构建都可以使用 gatsby serve 命令在本地进行测试。
在部署构建之前,确保本地一切正常运行是值得的。现在,让我们花点时间看看常见的构建错误,并学习如何避免它们。
常见构建错误
在我们的项目开发过程中,我们主要以开发模式运行项目。这是一个很好的主意,确保通过运行 gatsby build 命令,网站也能与生产构建一起工作。
有时,您可能会发现构建过程中出现错误。那么,让我们谈谈最常见的问题以及我们如何修复它们:
-
您最常遇到的问题是浏览器中找到的
window和document变量。因此,在您的网站构建过程中,它无法访问它们。您可以通过几种方式解决这个问题。您可以执行一个检查以确认变量已定义(例如,typeof window !== undefined && yourFunction()),或者如果适当,您可以将代码移动到useEffect钩子中。 -
确保您的所有组件、页面以及
gatsby-browser.js和gatsby-ssr.js文件不要混合使用 ES5 和 ES6 语法,因为这可能导致构建崩溃。 -
请特别注意确保您
pages目录中找到的所有 JavaScript 文件都是具有默认导出的 React 组件。Gatsby 将此文件夹中的所有 JavaScript 文件视为页面。如果您在此目录中有组件或其他实用函数,您将收到一个错误,表明页面组件必须导出一个 React 组件才能有效。如果您看到这个错误,只需将相关文件移出文件夹即可。
现在我们可以在没有问题的前提下构建我们的网站,让我们检查在部署我们的网站之前我们应该运行的实际检查清单。
部署前的检查清单
无论您打算如何部署您的网站,您都应该在本地机器上遵循一些步骤,以确保您的首次部署能够顺利运行:
-
确保所需的任何部署平台插件都已安装. 我们将要查看的一些平台有专门用于其产品的 Gatsby 插件。通过将它们添加到您的 Gatsby 网站中,平台能够更好地理解您的项目,因此可以更快地构建您的网站。
-
确保您的 Gatsby 网站构建时没有错误. 一旦构建成功通过,尝试运行
gatsby serve以确保您可以在没有问题的前提下使用该网站。 -
确保所有测试都通过. 确保您已经使用
npm run test运行了我们设置在 第七章 的单元测试,即 测试和审计您的网站,并确保它们都通过了。 -
注意您的 Node.js 版本. 截至 Gatsby 版本 4,您的 Node.js 版本应该是 14 或更高。您需要确保 Node.js 版本与您的部署平台相匹配,以避免兼容性问题。您可以在终端中运行
node -v来检查这一点。
现在我们已经完成了我们的检查清单,让我们看看我们可以使用各种平台部署我们的网站,从支持混合网站的平台开始。
重要提示
建议您只在单一部署平台上部署您的网站,而不是多个平台。当您可以用一个平台完成工作时,管理多个平台对您来说更容易维护。尝试尝试所有选项,以找到最适合您项目的最佳选择。
部署混合构建的平台
由于混合站点需要一个 Node.js 服务器,我们需要使用能够提供这些服务的平台。混合站点在 Gatsby 生态系统中也非常新。在撰写本文时,托管混合构建的唯一稳定选项是 Gatsby Cloud Hosting,因此让我们看看这个平台。
部署到 Gatsby Cloud Hosting
Gatsby Cloud 是由 Gatsby 组织专门设计和构建的云平台,专为 Gatsby 框架设计。因为他们专注于这个框架,所以他们擅长构建使您的构建尽可能快的科技。这包括以下内容:
-
增量构建:Gatsby Cloud 会观察 GraphQL 数据层并识别页面依赖关系。当您推送代码更改时,它会识别数据层更改,并且只重新构建依赖于该数据的页面。这可以大幅加快重复构建的速度——Gatsby 表示增量构建可以比传统构建快 1,000 倍。
-
智能缓存:当请求您的站点时,浏览器会收到特殊的缓存头。这些用于确保浏览器不会重新下载在构建之间没有更改的内容。
应该注意的是,平台免费层不支持增量构建。如果您想从中受益,您需要升级。
现在我们已经了解了使用该平台的好处,让我们看看如何将我们的站点部署到该平台。
快速笔记
在 Gatsby 平台上部署混合站点和静态站点的流程是相同的,因此这些说明在两种情况下都适用。
执行以下步骤将您的站点部署到 Gatsby Cloud 平台:
-
安装 Gatsby Cloud 插件:
npm install --save gatsby-plugin-gatsby-cloud在这里,我们正在安装 Gatsby Cloud 插件。这为 Gatsby Cloud 平台在构建过程中添加了基本的安全头。
-
在您的
gatsby-config.js文件中包含gatsby-plugin-gatsby-cloud插件:module.exports = { // rest of config plugins: [ `gatsby-plugin-gatsby-cloud`, // other plugins ] } -
将所有更改提交并推送到您选择的 Git 仓库。
-
打开浏览器并导航到
www.gatsbyjs.com/products/cloud。点击开始使用。 -
通过填写您的姓名、电子邮件和居住国家/地区来注册平台:![图 9.1 – 注册 Gatsby Cloud
![img/Figure_9.01_B15983.jpg]
图 9.1 – 注册 Gatsby Cloud
-
选择您的版本控制系统(VCS)提供商,登录,并批准 Gatsby Cloud 平台为了与其集成所请求的权限:![图 9.2 – Gatsby Cloud VCS 提供商授权步骤
![img/Figure_9.02_B15983.jpg]
图 9.2 – Gatsby Cloud VCS 提供商授权步骤
-
当您被重定向到 Gatsby Cloud 时,您将被询问是否想要试用 14 天升级。这取决于您。
-
然后,您将被导航到您的仪表板,它将是空的,因为我们还没有设置任何站点。现在让我们通过点击添加站点来添加我们的站点。
-
选择 从 Git 仓库导入,然后点击 下一步。
-
从列表中选择您的 Git 提供商,然后选择组织和存储库名称。如果由于某种原因,此列表尚未填充,请确保您已授予 Gatsby 从您的 Git 存储库读取的相关权限。
-
接下来,您需要提供网站详情,包括基础分支和基础目录。您的基础目录应指向存储库中 Gatsby 项目的根目录——这很可能是根目录或
/。点击 下一步。 -
然后,您将看到为您的网站提供的可选集成。这些集成可以帮助您的 CMS 与 Gatsby Cloud 通信。当您对 CMS 进行更改时,您可以通过 Gatsby Cloud 预览该内容的外观。如果您想这样做,您可以点击您所使用的 CMS 平台旁边的 连接 并按照步骤操作;否则,您可以点击 跳过此步骤。
-
最后,您将被要求添加网站构建所需的任何环境变量。Gatsby 会扫描您网站的综合和插件,以帮助填写它认为您需要的环境变量。务必与您本地的
.env文件进行交叉检查,以确保您拥有所需的一切。 -
点击 创建网站。这将提示 Gatsby 首次开始构建您的网站:

图 9.3 – Gatsby Cloud 网站仪表板
一旦构建完成,您可以通过在先前的截图下 HOSTED ON GATSBY CLOUD 标题下的紫色超链接中查看已部署的网站。
每次对基础分支的后续推送,Gatsby Cloud 都会自动构建和部署更改。
现在我们已经了解了如何部署混合构建,让我们看看我们有哪些选项可以用于部署静态构建。
静态构建的部署平台
由于静态构建是一个更常见且可预测的格式,因此有更多选项可以托管它们。我们已经了解了 Gatsby Cloud,它可以像混合构建一样部署静态网站。现在,让我们看看另外三个平台——Netlify、Render 和 Firebase。
部署到 Netlify
Netlify 是超过 500,000 个网站使用的部署平台。它因其易用性而受到开发者的喜爱。它还提供免费的 安全套接字层 (SSL)。让我们学习如何使用 Netlify 部署我们的网站:
-
安装 Netlify 插件:
npm install --save gatsby-plugin-netlify在这里,我们正在安装 Netlify 插件,该插件在构建过程中为 Netlify 平台添加基本的安全头。
-
在您的
gatsby-config.js文件中包含gatsby-plugin-netlify插件:module.exports = { // rest of config plugins: [ `gatsby-plugin-netlify`, // other plugins ] } -
将所有更改提交并推送到 Git 仓库。
-
在您的浏览器中导航到
app.netlify.com/signup。 -
使用您的 VCS 提供的第三方登录详情进行注册,并批准 Netlify 平台为与其集成所需的请求权限。
-
从您的仪表板点击创建新站点:![Figure 9.4 – The Netlify new site page
![img/Figure_9.04_B15983.jpg]()
图 9.4 – Netlify 新站点页面
选择存储您的仓库的 Git 提供商。然后,选择您想要构建的仓库。
-
您可以保留所有者选项的默认设置。但是,请确保部署分支与您的站点的主要生产分支匹配:![Figure 9.5 – The Netlify site creation settings
![img/Figure_9.05_B15983.jpg]()
图 9.5 – Netlify 站点创建设置
-
最后,分别提供
npm run build和public:![Figure 9.6 – The Netlify site creation build settings![img/Figure_9.06_B15983.jpg]()
图 9.6 – Netlify 站点创建构建设置
-
点击部署站点将启动构建过程。
-
当您的站点正在构建时,注意仪表板顶部的蓝色 URL。这就是您的站点将被部署的地方:

图 9.7 – Netlify 站点仪表板
如果一切顺利,您的站点应在几分钟内部署。随着对基础分支的每次后续推送,Netlify 将自动构建和部署更改。
现在我们已经了解了如何使用 Netlify 进行部署,让我们看看另一个替代方案——Render。
部署到 Render
Render 是一个云平台,可以免费 SSL 和全球 CDN 构建和运行 Gatsby 网站。让我们学习如何使用 Render 部署我们的站点:
-
将所有更改提交并推送到 Git 仓库。
-
导航到
dashboard.render.com/register,然后创建一个账户。 -
从仪表板中,点击新建:![Figure 9.8 – The Render site dashboard
![img/Figure_9.08_B15983.jpg]()
图 9.8 – 渲染站点仪表板
-
选择静态站点。
-
在这一点上,您将被要求提供一个仓库。但是,由于您尚未将 Render 连接到您的 VCS,列表将是空的。点击您的 VCS 的超链接,然后按照该第三方提供的 UI 旅程连接 Render 到该系统。在 GitHub 的情况下,它看起来可能像这样:![Figure 9.9 – GitHub's third-party installation
![img/Figure_9.09_B15983.jpg]()
图 9.9 – GitHub 的第三方安装
-
列表现在应该已经填充了您的仓库。选择包含您的 Gatsby 站点的那个。
-
接下来,配置您的站点设置:![Figure 9.10 – The Render site settings
![img/Figure_9.10_B15983.jpg]()
图 9.10 – Render 站点设置
npm run build和./public。 -
点击创建静态站点。
-
当您的站点正在构建时,注意仪表板顶部的蓝色 URL。这就是您的站点将被部署的地方:

图 9.11 – Render 网站仪表板
如果一切顺利,您的网站应在几分钟内部署。检查 URL 以确保无误。每次将更改推送到基础分支时,Render 都会自动构建和部署更改。
现在我们已经了解了如何使用 Render 进行部署,让我们看看另一种选择——Firebase。
部署到 Firebase
Firebase 是谷歌的移动开发应用程序。它允许您通过无代码/低代码开发 UI 管理后端基础设施,从而让您专注于应用程序的前端。Firebase 拥有大量功能,包括实时数据库、机器学习、Cloud Functions 身份验证,以及我们将要关注的特性——托管。让我们学习如何使用 Firebase 部署我们的网站:
-
导航到
console.firebase.google.com,并使用谷歌账户登录。 -
登录后,您将被引导到 Firebase 控制台。从那里,点击添加项目。
-
您将被提示为您的项目命名。输入后,注意您的项目 ID,然后点击继续:
![图 9.12 – Firebase 项目命名]()
图 9.12 – Firebase 项目命名
-
在此阶段,您可以可选地为您项目设置 Google Analytics。如果您在第八章“网络分析和性能监控”中将 Google Analytics 添加到您的网站,则在此处不要再次设置:
![图 9.13 – 设置 Firebase 项目分析]()
图 9.13 – 设置 Firebase 项目分析
-
点击创建项目——这将为您项目提供所需的 Google Cloud 服务。现在我们已经在 Firebase 平台上设置了所需的所有内容,可以返回代码。
-
安装 Firebase CLI:
npm install -g firebase-tools此软件包允许我们将本地项目与 Firebase 平台集成。我们可以使用
-g命令全局安装它。 -
运行
firebase login命令:firebase login这将打开一个浏览器窗口,提示您使用谷歌账户登录。使用您注册 Firebase 时使用的谷歌账户登录。
-
完成后,返回您的 Gatsby 项目根目录并运行以下命令:
firebase init这将在我们的 Gatsby 项目中触发 Firebase 初始化 UI,并显示以下内容:
![图 9.14 – Firebase CLI 项目初始化]()
图 9.14 – Firebase CLI 项目初始化
在此项目中,我们只使用托管,所以按向下箭头键直到托管被选中。然后,按空格键选择它,再按Enter键确认您的选择。
-
Firebase 将询问您要将哪个 Firebase 项目与该目录关联:
![图 9.15 – 设置 Firebase CLI 项目]()
图 9.15 – 设置 Firebase CLI 项目
我们已经创建了一个 Firebase 项目,所以请确保选择 使用现有项目 并按 Enter。
-
使用上下箭头键选择我们在 步骤 3 中创建的项目 ID(项目名称应显示在 ID 旁边的括号中)。然后,按 Enter。
-
在主机设置期间告诉 Firebase 静态构建的位置:
![图 9.16 – 设置 Firebase CLI 主机]()
图 9.16 – 设置 Firebase CLI 主机
默认情况下,Firebase 使用
public目录,因此我们可以按 Enter 而不更改此设置。 -
然后,它将询问您是否希望将其配置为单页应用程序。输入
n并按 Enter。 -
最后,它将询问您是否想设置与 GitHub 的自动部署。输入
n并按 Enter。如果需要,您可以在将来更改此设置,但现在我们将专注于手动部署。 -
我们现在已经准备好了一切,可以部署到 Firebase。运行以下命令:
gatsby build && firebase deploy如我们所知,
gatsby build将创建一个适合生产的网站构建。然后,firebase deploy命令将我们的构建上传到 Firebase 平台,准备好供网站访客访问:

图 9.17 – 部署 Firebase CLI
Firebase 部署完成后,它将在终端中记录一个 主机 URL。在浏览器中导航到这个链接,查看您部署的应用程序。
快速提示
如您从这些说明中注意到的,Firebase 是这个列表中唯一一个不需要您将代码推送到版本控制系统的平台。如果您有一个不想使用 VCS 的项目,这是一个很好的选择。需要注意的是,与其他平台不同,Firebase 不会自动部署您的项目,除非它已被设置为部署管道的一部分。
我们已经探讨了将我们的网站部署到互联网上的多种不同方法。如果您完成了前几节中讨论的任何实现,您应该能够将网站 URL 发送给朋友,他们应该能够看到它。然而,如果你不希望网站对所有人可见,而只想对少数人可见怎么办?接下来,让我们看看我们如何可以在需要时降低网站的访问级别。
为具有减少用户访问权限的 Gatsby 网站提供服务
您可能会问自己,为什么您会想要减少对网站的访问。一个词 – 安全性。在我们迄今为止看到的所有示例中,我们的网站是公开的,在互联网上供所有人查看,但如果你正在构建一个仅针对选定群体的应用程序呢?也许是你想将作品集锁定在密码后面,或者是一个仅对特定公司的同事可用的入职应用程序。我们可以使用大多数后端 Web 应用程序实现这些功能。
重要提示
这种类型的认证不要与第十一章中的认证相混淆,创建认证体验。在这里,我们限制了对整个网站的访问,除非你已被批准。在第十一章中,创建认证体验,访问仅部分受限,因为我们允许用户在不登录的情况下访问应用程序的某些部分。
例如,让我们探索如何使用 Express 在我们的网站上引入密码登录:
-
安装依赖项:
npm i express express-basic-auth我们将使用 Express 作为后端,并使用
express-basic-auth实现 HTTP 基本认证作为中间件。 -
在你的 Gatsby 项目根目录下创建一个
server.js文件,内容如下:const express = require("express"); const app = express(); const basicAuth = require("express-basic-auth"); const port = 3000; app.use( basicAuth({ challenge: true, users: { admin: "testing" }, }) ); app.use(express.static(`${__dirname}/public`)); app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`); });首先,我们创建一个
express应用。然后,我们指导它使用express-basic-auth中间件。你会看到我们传递了一个对象,指示中间件对用户进行挑战。当用户导航到网站时,在看到任何内容之前,他们将会看到一个以下对话框:![Figure 9.18 – Basic auth challenge dialog box
![img/Figure_9.18_B15983.jpg]()
Figure 9.18 – Basic auth challenge dialog box
只有当提供的凭据与传递给
basicAuth的users对象中的凭据匹配时,他们才被允许进入网站。假设他们成功通过了这个中间件的检查,我们然后允许他们使用
express.static()方法查看我们网站的静态内容。 -
修改
package.json中的脚本以包括一个start:server脚本:"scripts": { ... "start:server": "node server.js" },此脚本将使用 Node.js 运行我们的
server.js文件。 -
现在我们已经准备好尝试我们的服务器:
gatsby build && npm run start:server这将构建你的 Gatsby 项目,然后运行你的服务器代码,这将提供你的 Gatsby 构建内容。如果一切顺利,你应该能够访问
localhost:3000并看到这个实现正在工作。在输入服务器上指定的用户名和密码后,你应该能够看到你的 Gatsby 应用程序。
本章中我们探讨的所有其他静态部署方法都假设 Gatsby 项目是在其自己的专用服务器上托管,但有时,你并不总是有多个服务器的奢侈。这个例子也是一个很好的演示,说明你如何在单个服务器上结合后端和前端代码。你可以使用类似的方法来锁定你的网站到特定的 IP 范围。例如,我们可以扩展这个 Express 服务器,在同一个存储库中为我们的 Gatsby 项目提供 API 端点。
快速提示
你可能想知道如何使用此功能部署网站——Express 服务器的部署超出了本书的范围,但支持此功能的平台包括 Heroku、Render 和 Google Cloud。
现在,让我们花点时间总结一下我们在本章中学到的内容。
摘要
在本章中,我们探讨了 Gatsby 项目可以创建的构建类型及其之间的区别。我们研究了在构建过程中出现的常见错误以及如何调试它们。我们学习了如何使用 Gatsby Cloud 部署混合构建,以及如何使用 Netlify、Render 和 Firebase 部署静态构建。此外,我们还发现了如何通过将其作为 Express 服务器的一部分来提供服务,以锁定对网站的访问。现在,你应该对将网站上线的过程感到舒适。
在下一章中,我们将开始探讨更高级的概念。我们将从学习 Gatsby 插件创建开始。
第三部分:高级概念
到目前为止,你应该已经了解了如何使一个标准静态网站上线。在本部分,我们将介绍一些更高级的技术来处理 Gatsby 网站中稍微不那么常见的用例。
在本部分,我们包括以下章节:
-
第十章, 创建 Gatsby 插件
-
第十一章, 创建认证体验
-
第十二章, 使用实时数据
-
第十三章, 国际化与本地化
第十章:创建 Gatsby 插件
在本章中,我们将探讨 Gatsby 的插件生态系统。我们将从学习如何使我们的 Gatsby 网站在增长过程中变得更加模块化开始。然后,我们将创建我们的第一个源插件,从 GitHub 获取数据。我们还将创建我们的第一个主题插件,为我们的网站创建活动页面。最后,我们将学习如何通过 Gatsby 的插件生态系统将我们的插件与世界分享。
在本章中,我们将涵盖以下主题:
-
理解 Gatsby 插件
-
介绍本地插件开发
-
创建源插件
-
创建主题插件
-
贡献插件生态系统
技术要求
要完成本章,你需要完成 第九章,部署和托管。你还需要一个 GitHub 账户。
本章的代码可以在 github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter10 找到。
理解 Gatsby 插件
到这本书的这一阶段,你应该已经拥有了将 Gatsby 网站投入生产所需的所有工具。在本章中,我们将更进一步,讨论如何使用所谓的 Gatsby 插件 在多个 Gatsby 网站之间实现可重用性。Gatsby 插件是抽象化利用 Gatsby API 的常见网站功能的节点包。通过将功能打包到插件中,你只需几行代码就可以获取数据、创建页面、实施 SEO 以及更多操作。Gatsby 插件还充当了一种将大型网站模块化为更易管理的功能块的方式。
最常见的两种插件类型如下:
-
gatsby-node.js文件,以及在你的页面中。 -
Gatsby 主题插件:主题插件更多地关注应用程序的用户界面。通常,主题插件包含创建网站页面的代码,例如常见问题解答部分。它们将你的 Gatsby 网站分割成更小的、更易管理的项目,当有多个团队在同一网站上工作时,这非常有用。
这两种类型可以通过插件名称来识别,它将分别以 gatsby-source 或 gatsby-theme 开头。虽然这两种类型是最常见的,但它们并不是唯一的类型。封装其他任何功能的插件,其插件名称将以 gatsby-plugin 开头。
在我们深入探讨并开始创建插件之前,让我们先了解本地插件开发,这样我们可以避免常见的陷阱。
介绍本地插件开发
本地插件开发从名为plugins的新文件夹开始,您需要在您的根目录中创建此文件夹。这是我们将存放我们创建的插件的文件夹。当您将插件添加到 Gatsby 配置中时,Gatsby 首先会在您的node_modules文件夹内查找。如果在那里找不到插件,它将检查这个本地的plugins文件夹。如果在这个文件夹中找到了具有相同名称的插件,并且其package.json文件中也有相同的插件,它将使用它。
如您通过提及package.json文件所猜测的那样,插件以 npm 包的形式存在。npm 包负责处理它们的依赖项,因此当您为插件安装包时,您需要确保在插件的文件夹内而不是根目录中打开终端。否则,您网站和插件的依赖项可能不准确。
快速提示
如果您没有打算分享您创建的插件,您可以选择在根目录中安装插件所需的依赖项。如果您更喜欢有一个依赖项的单一来源,这可能更容易管理。但请注意 - 如果您认为插件可能在任何时刻被分享,请不要这样做,因为您将不得不手动整理您的依赖项并找到插件所需的那些。
在创建本地插件时,您可能会发现您的代码并不像您的其他项目那样表现。让我们看看我们如何调试常见的问题。
调试本地插件
Gatsby 不会将本地插件文件夹与其他代码库同等对待。对页面、模板和配置的更改在热重载时可能不会必然出现。这里有一些小贴士可以让您的生活更加轻松:
-
如果您做了更改,即使在重启服务器后也没有看到反映,请尝试使用
gatsby clean清除缓存。Gatsby 在.cache文件夹中缓存插件数据。为了使自身更快,Gatsby 使用这个缓存。 -
如果您不确定您的插件是否正在运行,请尝试将以下命令添加到您的插件的
gatsby-node.js文件中:exports.onPreInit = () => console.log("Plugin Started!")此命令将在 Gatsby 执行期间首先运行。如果 Gatsby 知道您的插件,您将在控制台看到Plugin Started!的日志。
现在我们知道了何时创建插件是个好主意,让我们学习如何创建它们。
创建源插件
正如在理解 Gatsby 插件部分中提到的,源插件是那些允许我们从新的数据源中摄入数据到我们的 GraphQL 层中的插件。通过创建源插件,我们将获取这些数据的逻辑从我们的网站中抽象出来,这样我们就可以在想要的情况下跨多个 Gatsby 项目重用它。为了理解源插件是如何工作的,让我们一起来构建一个。让我们从 GitHub 获取我们的总贡献量,以便我们可以在我们的关于页面上显示它们:
-
我们需要能够从 GitHub 拉取数据的第一件事是使用访问令牌。导航到
github.com/settings/tokens/new。 -
写一个 备注 帮助您稍后识别您的访问令牌:
![图 10.1 – GitHub 个人访问令牌生成]()
图 10.1 – GitHub 个人访问令牌生成
-
将 过期 属性更改为您希望的时间长度。一旦选择了时间长度,令牌将被删除并且不再工作。如果您希望它不失效,可以从此列表中选择 无过期。
-
滚动列表并检查 read:user:
![图 10.2 – GitHub 个人访问令牌生成(继续)]()
图 10.2 – GitHub 个人访问令牌生成(继续)
-
点击 生成令牌。
-
在下一屏,您将看到您的访问令牌 – 立即记下这个令牌,因为您将无法再次看到它。
重要备注
如果您丢失了访问令牌,您将无法再次看到它。GitHub 这样做是为了防止您的密钥被他人用于恶意目的。如果您丢失了密钥,您将不得不创建一个新的,具有相同的权限,并在使用的地方替换令牌。
-
在您的根目录中创建一个
.env文件并添加以下行:GITHUB_PROFILE_BEARER_TOKEN=your-token-here在这个阶段,您可能已经在项目中有一个
.env文件。如果是这种情况,只需将前面的代码块行追加到该文件中。 -
确保在您的项目根目录中安装了
dotenv作为依赖项。如果没有,请运行以下命令:npm i dotenv -
在您的
plugins文件夹中创建一个名为gatsby-source-github-profile的新文件夹。 -
在
gatsby-source-github-profile文件夹中打开终端并运行以下命令:npm init -y这初始化了我们插件的 npm 包。
-
安装
node-fetch包:npm i node-fetch@2.6.5node-fetch包将fetch浏览器 API 带到node。我在这个例子中使用它,因为我怀疑你们大多数人都会熟悉fetch,因为这本书的目标是针对 React 开发者。重要备注
Node Fetch 从版本 3.0 开始仅支持 ESM。这意味着它不会很好地与我们在 Gatsby 配置文件中使用的 ES5 格式兼容。维护者建议在我们的情况下使用版本 2.6.5。
-
在
gatsby-source-github-profile文件夹中创建一个gatsby-node.js文件,并将以下代码添加到其中:const fetch = require("node-fetch"); const crypto = require("crypto"); /* Code added here in the next step */在这里,我们正在将我们的最新安装的
node-fetch和crypto库(随 node 一起提供)导入到我们的项目中。crypto提供了加密功能,我们将在本文件的稍后部分使用它。 -
在您的导入中,添加以下代码:
exports.sourceNodes = async ({ actions }, configOptions) => { const { createNode } = actions; /* Code added here in the next step */ };在这里,我们正在使用
sourceNodesGatsby 节点 API。正如其名所示,我们将在此处添加代码以获取我们的数据,然后使用createNode操作创建节点。你可能也注意到了,我们正在将configOptions作为参数传递。此对象使我们能够访问我们在gatsby-config.js文件中使用插件时提供的任何选项。我们将传递我们的访问令牌和用户名作为选项。快速提示
为了提高此文件的可理解性,它已经被分解为其各个部分。如果你觉得难以理解,你可以在本章技术要求部分的仓库中查看整个文件。
-
在
sourceNodes内部创建一个如下的POST请求,用于 GitHub API:const headers = { Authorization: 'bearer ${configOptions.token}', }; const body = { query: 'query { user(login: "${configOptions.username}") { contributionsCollection { contributionCalendar { totalContributions } } } }', }; const response = await fetch("https://api.github.com/graphql", { method: "POST", body: JSON.stringify(body), headers: headers, }); const data = await response.json(); /* Code added here in the next step */我们使用
node-fetch向 GitHub API 发送POST请求。我们在请求头中提供令牌身份验证。在这里,你可以看到我们正在使用configOptions中提供的令牌。与 Gatsby 一样,GitHub API 使用 GraphQL。与任何 GraphQL API 一样,为了从 GitHub 选择我们想要的数据,我们必须将查询传递到请求体中。定义在body中的查询检索给定用户名(在这种情况下,是你的!)的总贡献。我们从configOptions中传递我们的用户名。 -
在你的请求后添加以下代码:
const { contributionsCollection } = data.data.user; const totalContributions = contributionsCollection.contributionCalendar.totalCont ributions; createNode({ totalContributions: Number(totalContributions), id: "Github-Contributions", internal: { type: 'GitHubContributions', contentDigest: crypto .createHash('md5') .update( JSON.stringify({ totalContributions, }) ) .digest('hex'), description: 'Github Contributions Information', }, });在这里,我们分解我们的请求以接收总贡献数据。然后,我们使用
createNode函数将此数据添加到我们的 GraphQL 数据层。让我们分解我传递给函数的对象:a.
totalContributions: 对象中的第一个键值是总贡献值。这是我们稍后尝试在我们的页面上检索此信息时将查询的变量。b.
id: 每个节点都必须有一个全局唯一的 ID。因为此节点类型只有一个实例,我们可以直接使用"Github-Contributions"字符串。c.
internal.type: 一个全局唯一的类型,我们可以用它来识别此数据源。d.
internal.contentDigest: 此字段帮助 Gatsby 在节点未更改时避免重新生成节点。在创建节点时,如果此字段保持不变,则不会重新生成。因此,我们需要确保如果我们的总贡献发生变化,那么这个contentDigest也会变化。为此,我正在使用crypto库创建我们总贡献的md5哈希。在这个特定的例子中,这可能会显得有些过度,但如果节点上的数据量超过一个键值对,那么它工作得很好,你只需将它们添加到传递给JSON.stringify的对象中即可。e.
internal.description: 此字段允许我们描述源类型,如果我们对任何时刻的此源感到困惑,这将很有帮助。此字段不是必需的,但拥有它很好。我们的插件现在已准备好使用 – 从这一点开始的过程与通过 npm 安装的插件相同。 -
导航到您项目根目录下的
gatsby-config.js文件并添加以下代码:require("dotenv").config({ path: '.env', }); module.exports = { // rest of config plugins: [ { resolve: 'gatsby-source-github-profile', options: { token: process.env.GITHUB_PROFILE_BEARER_TOKEN, username: "your-github-username-here", }, }, // other plugins ] }注意,我们正在将选项传递给插件,这些选项在插件的
gatsby-node.js文件中使用。我们从.env文件中获取令牌。你可以将 GitHub 用户名作为纯文本传递,因为这属于公开信息。快速提示
你可能会想在这里添加别人的用户名而不是自己的,但这会导致获取请求失败,因为你的访问令牌没有权限检索其他用户的数据。
-
启动您的开发服务器。导航到
http://localhost:8000/_graphql– 你应该可以使用以下查询查询你的总贡献:query Contributions { gitHubContributions { totalContributions } } -
让我们将这个新的数据源添加到我们的
about页面:export default function About({ data }) { const { markdownRemark: { html }, data prop and render it to the screen, as shown in the highlighted section of code.
恭喜你 – 你刚刚构建了你的第一个本地插件。你可以复制这里概述的方法从另一个 API 获取数据。所以,到目前为止,我们可以轻松创建源插件,但关于主题插件呢?
创建主题插件
正如我们所发现的,主题插件都是关于为我们的 Gatsby 网站添加视觉元素。主题插件独特之处在于它们必须包含一个gatsby-config.js文件。为了更好地理解主题插件,让我们看看最简单的例子。让我们使用一个插件来为我们的网站添加一个简单的示例页面:
-
在您的
plugins文件夹中创建一个名为gatsby-theme-sample-page的新文件夹。 -
在
gatsby-theme-sample-page文件夹中打开一个终端并运行以下命令:npm init -y -
在
/gatsby-theme-sample-page中创建一个src文件夹。 -
在您的
src文件夹中创建一个pages文件夹。 -
在您新的
pages文件夹内创建一个sample.js文件并添加以下代码:import React from "react"; const Sample = () => { return ( <div> <h1>Sample page</h1> </div> ); }; export default Sample;这个页面非常基础,只是在页面上渲染一个标题。
-
导航到您项目根目录下的
gatsby-config.js文件并添加以下代码:module.exports = { // rest of config plugins: [ 'gatsby-theme-sample-page', // other plugins ] } -
启动您的 Gatsby 开发服务器并导航到
/sample;你应该能看到你的示例页面。
你可能已经注意到,插件与你的 Gatsby 网站由相同的构建块组成。这也是在 Gatsby 中创建插件如此简单的原因之一。通过使用这个工具构建网站,你也继承了创建插件的能力。
现在我们已经看到了一个基本示例,让我们尝试构建一个更有用、更复杂的示例。让我们创建一个插件,它从一个事件文件夹(JSON 格式)中获取事件并为每个事件创建一个页面:
-
首先,我们需要在我们的插件中获取一些事件。假设每个事件都将包含一个
标题、描述、地点和日期。在您的根目录下创建一个名为events的文件夹。在这个文件夹内添加一些符合以下格式的 JSON 文件:{ "title": "Elevating Your Hack", "description": "Tips & tricks to make your hack stand out from the crowd.", "location": "King's College", "date": "2021-12-25" }确保 JSON 是有效的,因为错误会导致插件崩溃。
-
在您的
plugins文件夹中创建一个名为gatsby-theme-events-section的新文件夹。 -
在
gatsby-theme-events-section文件夹中打开一个终端并运行以下命令:npm init -y -
在
/gatsby-theme-events-section中创建一个src文件夹。 -
在
gatsby-theme-events-section文件夹中打开一个终端并运行以下命令:npm i gatsby-transformer-json如其名所示,这安装了用于处理 JSON 的转换器插件。
-
在
gatsby-config.js文件中添加以下代码:module.exports = { plugins: [ 'gatsby-transformer-json', { resolve: 'gatsby-source-filesystem', options: { path: './events', }, }, ], };在这里,我们添加了我们新安装的插件,并将插件指向
events目录中存在的文件系统中的源文件。这些插件将一起工作,为事件目录中的每个 JSON 文件创建一个新的节点。 -
在
/gatsby-theme-events-section/src/templates中创建一个gatsby-node.js文件并添加以下代码:const { createFilePath } = require('gatsby-source- filesystem'); exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField } = actions; if (node.internal.type === 'EventsJson') { const slug = createFilePath({ node, getNode }); createNodeField({ node, name: 'slug', value: slug, }); } };每当创建一个新节点时,都会调用
onCreateNode函数。使用此函数,我们可以通过添加、删除或操作它们的字段来转换节点。在这个特定的情况下,如果我们创建的节点是EventsJson类型,我们会添加一个slug字段。slug是我们网站上特定页面的地址,因此在我们的活动页面中,我们希望每个活动都有一个唯一的slug,它将在网站上渲染。 -
在
gatsby-node.js文件前面添加以下代码:exports.createPages = async ({ actions, graphql, reporter }) => { const { createPage } = actions; const EventTemplate = require.resolve('./src/templates/event'); const EventsQuery = await graphql(' { allEventsJson { nodes { fields { slug } } } } '); if (EventsQuery.errors) { reporter.panicOnBuild('Error while running GraphQL query.'); return; } const events = EventsQuery.data.allEventsJson.nodes; events.forEach(({ fields: { slug } }) => { createPage({ path: 'event${slug}', component: EventTemplate, context: { slug: slug, }, }); }); };这段代码看起来非常熟悉,因为它与我们之前在 第四章 的 创建模板和程序化页面生成 部分中看到的代码非常相似,即 创建可重用模板。在这里,我们正在使用
createPage函数,它允许我们动态地创建页面。在这个函数内部,我们解构actions对象以检索createPage函数。然后,我们告诉 Gatsby 在哪里可以找到我们的活动模板。有了这两部分,我们现在可以查询我们的数据了。当你从所有活动中选择slug属性时,你应该会看到一个熟悉的 GraphQL 查询。之后,我们可以遍历活动并为每个活动创建一个页面,提供slug属性作为上下文。 -
在
/gatsby-theme-events-section/src中创建一个templates文件夹。 -
在
/gatsby-theme-events-section/src/templates中创建一个event.js文件并添加以下代码:import React from "react"; import { graphql } from "gatsby"; export default function Event({ data }){ const { event: { description, title, location, date }, } = data; return ( <div className="prose max-w-5xl"> <h1>{title}</h1> <p> {date} - {location} </p> <p>{description}</p> </div> ); }在这里,我们取
title、location、description和date,这些将在页面查询中检索,并在屏幕上渲染。 -
将
events.js文件附加以下代码:export const pageQuery = graphql' query($slug: String!) { event: eventsJson(fields: { slug: { eq: $slug } }) { description title location date(formatString: "dddd Do MMMM yyyy") } } ';在这里,我们使用上下文中的
slug来查找与节点字段中的slug匹配的活动。我们通过检索title、location、description和date(这些已经被格式化)来查询所有我们需要填充此页面的数据。然后,通过data属性将这些传递到模板中。 -
现在,让我们创建一个列出所有活动的页面。在
/gatsby-theme-events-section/src中创建一个pages文件夹。 -
在
/gatsby-theme-events-section/src/pages中创建一个events.js文件并添加以下代码:import React from "react"; import { graphql, Link } from "gatsby"; const Events = ({ data }) => { const events = data.allEventsJson.nodes; return ( <div className="prose max-w-5xl"> <h1>Upcoming Events:</h1> {events.map(({ title, location, date, fields: { slug } }) => ( <Link to={'/event${slug}'}> <h2>{title}</h2> <p> {date} - {location} </p> </Link> ))} </div> ); }; export default Events在这里,我们正在遍历我们的活动并为每个活动创建一个带有
title、data和location的Link。 -
将以下代码附加到
events.js:export const query = graphql' { allEventsJson { nodes { location title date fields { slug } } } } ';此查询将检索所有事件并将它们返回到节点数组中,这些可以通过页面的
data属性检索。 -
您已完成所有操作 – 运行您的开发服务器并导航到
localhost:8000/events。您应该看到以下输出:

图 10.3 – 事件页面预览
点击事件应带您进入其专属页面:

图 10.4 – 事件页面预览
您刚刚制作了您的第一个本地主题插件。将事件添加到 events 文件夹中,它将被附加到列表中,并获得一个专属页面。如果我们发布这个插件,我们就可以在多个 Gatsby 网站中使用它,只需创建一个 events 文件夹并填充它。不需要额外的配置!
快速提示
您会注意到本章中提供的示例中缺少样式。本章重点介绍正在使用的 Gatsby API,而不是样式。到目前为止,您应该有足够的信心为这些页面创建样式。
现在我们已经了解了如何创建这两种类型的插件,让我们学习如何发布它们并将它们贡献给社区。
贡献插件生态系统
因此,您已经构建了一个插件,现在您想在单独的 Gatsby 项目中使用它?或者也许您认为这个插件可以帮助其他开发者?在任一情况下,您都需要发布您的插件。通过使用 npm 发布您的插件,您的插件将自动在 Gatsby 的网站插件页面上可见(www.gatsbyjs.com/plugins)。让我们从查看一个发布前清单开始这段旅程。
发布前清单
在我们发布插件之前,确保我们已经准备好这样做。以下是一个建议的发布前清单:
-
确保您的插件名称解释了它做什么。这可能看起来有点微不足道,但以使插件名称清晰表明其功能的方式命名插件将使其更容易在网上找到。
-
确保您的插件名称是唯一的。两个 npm 包不能有相同的名称,因此您不能尝试部署一个已经使用的名称的包。要检查您的名称是否已被使用,请访问
www.npmjs.com/并搜索您的插件名称。 -
确保您的插件遵循在 理解 Gatsby 插件 部分中概述的命名约定。这是 Gatsby 确定哪些 npm 包是 Gatsby 插件并添加到其网站的方式。
-
确保您的插件有一个全面的
README.md文件。此文件将被 Gatsby 捕获并包含在插件生态系统中,因此README.md文件解释您的插件做什么以及如何使用它至关重要。您应包括可能需要的特定配置选项。 -
检查 React 和 Gatsby 是否是同伴依赖项。
-
确保您的代码已经经过适当的测试。单元测试非常重要,但如果您即将将代码传递给他人,那么这一点尤为重要。争取达到 100% 的测试代码覆盖率。
重要提示
如果您更改了插件的名称,请确保这个新名称在
package.json文件以及文件夹名称中都有体现。如果您在以后使用/搜索插件时发现旧名称,可能会造成混淆。
现在我们已经完成了检查清单,让我们学习如何发布一个插件。
发布一个插件
发布 Gatsby 插件遵循与发布任何 npm 包相同的流程。让我们学习如何做这件事:
-
确保您有一个 npm 账户。如果您没有,您可以在
www.npmjs.com/signup创建一个。 -
通过在终端运行以下命令从 npm CLI 登录:
npm loginCLI 将在登录过程中要求您输入姓名、电子邮件和密码。
-
将您的终端导航到 Gatsby 插件的目录。这一点至关重要。如果您在这些步骤中继续在根目录下操作,您可能会意外地将整个网站作为一个包发布出去,所以请确保您已经导航到插件目录。
-
最后,运行
publish命令:package.json file and retry.Quick TipAfter your first publish, you will most likely find things you want to change. If you follow these instructions again, be sure to bump the version number in your `package.json` file as npm will reject a publish with the same version number.
现在您的插件已经发布,它应该在 24 小时内出现在 Gatsby 网站插件页面上。
摘要
在本章中,我们学习了什么是 Gatsby 插件以及有哪些类型。我们了解了本地插件开发以及如何创建源和主题插件。我们创建了源和主题插件,并在本地通过在我们的网站上包含它们来测试它们。然后我们学习了如何在网络上共享插件。我们讨论了在部署插件之前应该考虑的事项,然后学习了如何通过在 npm 上发布来共享插件。通过完成本章,您现在应该有信心轻松地创建和共享源和主题插件。这只是一个关于巨大主题的简要介绍,我希望您能在此基础上构建知识,为任何用例创建插件。
在下一章中,我们将探讨另一个高级概念——身份验证。我们将学习如何在您的网站上创建登录体验。
第十一章:创建认证体验
在本书的上下文中,认证是指验证用户在网站中是否是他们所说的那个人。一旦他们的身份得到验证,我们就可以展示只有他们才能看到的内容。这可能包括他们的个人资料页面、送货地址、银行详情等等。在本章中,我们将更多地关注如何实现用于认证服务的路由,而不是关注如何实现认证服务或用户认证时显示什么内容。在将此知识应用于具有两种不同客户端实现的 Gatsby 站点之前,我们将回顾一下在传统 React 应用程序中是如何做到这一点的。
在本章中,我们将涵盖以下主题:
-
React 应用程序中的路由和认证
-
在 Gatsby 中使用客户端路由进行认证
-
在 Gatsby 中使用上下文进行全局认证
技术要求
要完成本章,你需要完成第十章,创建 Gatsby 插件。你还需要一个 GitHub 账户。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter11找到。
React 应用程序中的路由和认证
要实现认证体验,我们将使用路由。在我们深入探讨如何在 Gatsby 中实现路由之前,让我们先熟悉一下 React 应用程序中的路由工作原理。路由是用户在应用程序的不同部分之间导航的过程。
对于这个示例,我将使用create-react-app启动一个 React 项目。我已经包括了其安装步骤,但你可以选择跳过它们并使用你自己的 React 实现。请将本节的示例与你的 Gatsby 项目分开。
重要提示
在以下示例中,我们将使用@reach/router包进行路由。Gatsby 底层使用@reach/router,因此在我们转向在 Gatsby 中实现它们时,使用这个包在 React 中会很容易识别模式。
作为 React 开发者,路由是构建应用程序的常见部分——让我们回顾一下路由基础知识:
-
为此示例创建一个新的文件夹。在这个新文件夹中打开一个终端,并运行以下命令:
npx create-react-app . -
在同一个终端中,运行以下命令:
@reach/router package within the project. -
打开
src/App.js,将其替换为以下代码:import { Router, Link } from "@reach/router"; const Nav = () => ( <nav> <Link to="/">Homepage</Link> | <Link to="about">About Me</Link> </nav> ); {/* Code continued in next step */}在这里,我们已从
@reach/router包中导入了Router和Link。我们还创建了一个Nav组件,我们可以用它来访问路由。这个Nav组件利用了@reach/router中的Link组件来提供路由间的导航。 -
将以下代码添加到
src/App.js中:const HomePage = () => ( <div> <Nav /> <h1>Homepage</h1> </div> ); const AboutPage = () => ( <div> <Nav /> <h1>About Me</h1> </div> ); {/* Code continued in next step */}在这里,我们定义了一些用于路由之间的虚拟组件——一个“主页”和一个“关于”页面。所有这些都应该对你来说非常熟悉。
-
最后,将以下代码添加到
src/App.js中:function App() { return ( <Router> <HomePage path="/" /> <AboutPage path="about" /> </Router> ); } export default App;这就是魔法发生的地方。通过将组件包裹在
Router组件中,我们可以根据当前的 URL 路径切换显示哪个组件。在这个例子中,如果用户位于/路径(路由 URL),他们将看到HomePage组件,而如果path是/about,他们将看到AboutPage组件。他们可以使用这两个页面中的Nav组件在它们之间导航。 -
通过在根目录下运行
npm start来启动项目以尝试它。
重要的是要记住,在路由之间导航是快速的,因为所有路由都是在路由器渲染时加载的。当我们进入 Gatsby 时,确保我们只在必要时使用路由器非常重要,因为我们可能会添加页面重量来包含用户可能永远不会看到的组件。
现在我们已经通过一个基本的路由示例,接下来让我们开始添加只有用户登录后才能访问的页面。我们将通过私有路由来实现这一点。
私有路由
私有路由的行为与其他被Router包裹的组件相同,除了它有一个认证条件。如果条件不满足,用户将不会看到请求的内容,而是会被重定向到登录屏幕进行认证。现在让我们通过将之前公开的关于页面转换为私有路由来尝试一下:
-
首先,我们需要定义我们的认证条件。在这个例子中,我们将保持简单。要被认为是“认证的”,用户必须已经调用了
login函数,我们将通过登录页面上的按钮来触发它。为了实现这个条件,我们将创建一个可以存储当前认证状态的上下文。创建一个名为auth-context.js的新文件,并添加以下代码:import React, { useState, useContext } from "react"; import { navigate } from "@reach/router"; const AuthContext = React.createContext(); export const AuthProvider = ({ …props }) => { {/* Code continued in next step */} }; export const useAuth = () => useContext(AuthContext); export default AuthContext;在这里,我们正在设置我们的授权上下文的模板。我们正在创建一个
useAuth钩子来访问我们将在下一步定义的上下文值。 -
在
auth-context.js文件中的AuthProvider中添加以下代码:Const [authenticated, setAuthenticated] = useState(false); const login = () => { // Make authentication request here and only trigger the following if successful. setAuthenticated(true); navigate("/"); }; const logout = () => { setAuthenticated(false); navigate("/login"); }; return ( <AuthContext.Provider value={{ login, logout, authenticated, }} {...props} /> );在这里,我们创建了一个名为
authenticated的useState值来跟踪用户是否已认证。然后我们创建了一个将authenticated设置为true的login函数。在这个函数中,你会向你的认证服务发送请求并验证用户,然后进行认证。很可能会也有一些关于用户的信息可以存储在你的状态或本地存储中。如果你添加了额外的信息,确保在logout函数中清除它。目前,logout函数只是将authenticated设置为false并将用户导航回登录页面。在AuthContext.Provider中,我们暴露了login和logout函数,以及authenticated状态,给应用程序的其他部分。 -
导航到你的 React 应用程序的
index.js文件,并使用以下代码进行修改:import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; AuthProvider, we would not be able to access the authentication context within the application. -
我们的认证条件现在已经定义,因此我们可以利用它来创建一个私有路由组件。创建一个名为
PrivateRoute.js的新文件,并添加以下代码:import React from "react"; import { navigate } from "@reach/router" import { useAuth } from "./auth-context"; const PrivateRoute = ({ component: Component, ...rest }) => { const { authenticated } = useAuth(); if (!authenticated) { navigate("/login"); return null; } return <Component {...rest} />; }; export default PrivateRoute;这个
PrivateRoute组件使用useAuth钩子中的authenticated状态来有条件地渲染一个给定的组件。如果用户已认证,该组件将被渲染。然而,如果他们未认证,用户将被导航到login路由。 -
返回你的
App.js文件并更新文件,以下为导入:import { useAuth } from "./auth-context"; import PrivateRoute from "./PrivateRoute";在这里,我们正在导入
useAuth钩子和我们新创建的PrivateRoute组件。 -
使用以下代码修改
HomePage组件:import { Router, Link } from "@reach/router"; // Predefined Nav Component Here. Const HomePage = () => { authenticated state from the useAuth hook to give the user some indication of their authentication status. -
使用以下代码修改
AboutPage组件:const AboutPage = () => { const { logout } = useAuth(); return ( <div> <Nav /> <h1>About Me</h1> <button onClick={logout}>Logout</button> </div> ); };这是我们在本演示中打算使其私有的路径。如果一个用户在这个页面上,我们可以假设他们已经认证,并渲染一个注销按钮,允许他们触发
logout函数。 -
将
LoginPage组件添加到App.js中:const LoginPage = () => { const { login } = useAuth(); return ( <div> <Nav /> <h1>Login Page</h1> <button onClick={login}>Login</button> </div> ); };这是一个基本的实现,它使用
useAuth中的login函数来登录用户。在你的应用程序中,你可能希望用用户输入电子邮件和密码的输入字段来完善它。然后你将传递这些信息给login函数,以便它可以用作授权请求的一部分。 -
最后,更新你的
App函数,使其包括你的PrivateRoute组件:function App() { return ( <Router> <HomePage path="/" /> <LoginPage path="login" /> <PrivateRoute component={AboutPage} path="about" /> </Router> ); } export default App; -
通过在根目录下运行
npm start来启动项目。如果你尝试导航到/about,你会注意到你将被重定向到/login,直到你点击了登录按钮。
我们现在对路由和私有路由有了牢固的掌握,所以让我们将本节中获得的知识应用到 Gatsby 上。
在 Gatsby 中使用仅客户端路由进行身份验证
虽然这不是常见的做法,但我们也可以在 Gatsby 页面中使用路由器。通常,Gatsby 会将所有路由抽象化,这样我们就不必担心它,但身份验证是我们需要将路由控制权重新掌握在我们手中的一个例子。我们将创建所谓的客户端路由。为了在我们的项目中演示这一点,我们将在/private创建一个页面。正如其名称可能暗示的那样,此路径包含一个受保护的页面,我们将通过身份验证来锁定它。让我们开始吧:
重要提示
此示例将与“在 Gatsby 中使用上下文进行全局身份验证”部分的代码发生冲突。最好选择这两种方法中的一种来实现,而不是尝试将它们结合起来。
-
在
src目录内创建一个名为context的新文件夹。 -
创建一个名为
auth-context.js的新文件,并添加以下代码:import React, { useState, useContext } from "react"; import { navigate } from "@reach/router"; const AuthContext = React.createContext(); export const AuthProvider = ({ ...props }) => { {/* Code continued in next step */} }; export const useAuth = () => useContext(AuthContext);在这里,我们正在以与“React 应用程序中的路由和身份验证”示例代码中相同的方式设置身份验证上下文。请注意,我们仍然从
@reach/router导入navigate,而不是从 Gatsby 库中导入。 -
在
AuthProvider中添加以下内容:const [authenticated, setAuthenticated] = useState(false); const login = async () => { // Make authentication request here and only trigger the following if successful. setAuthenticated(true); navigate("/private") }; const logout = () => { setAuthenticated(false); }; return ( <AuthContext.Provider value={{ login, logout, authenticated, }} {...props} /> );我们以与 React 演示相同的方式设置了
auth-context.js文件,但这次我们在登录成功后导航到/private。重要提示
在本节中,您将看到与上一节中的 React 演示代码非常相似的代码。请注意,尽管它们很相似,但它们并不相同。不要被诱惑从 React 示例中复制粘贴它们。
-
将以下内容添加到你的
gatsby-browser.js和gatsby-ssr.js文件中:import React from "react"; import { AuthProvider } from "./src/context/auth- context"; export const wrapPageElement = ({ element }) => { return <AuthProvider>{element}</AuthProvider>; };我们希望确保身份验证上下文在整个应用程序中可用。通过在
gatsby-browser.js和gatsby-ssr.js中添加前面的代码,我们可以确保它可以在任何地方访问。 -
在
src/components目录内创建一个名为PrivateRoute.js的新文件。 -
将以下代码添加到新创建的
PrivateRoute.js文件中:import React from "react"; import { navigate } from "gatsby"; import { useAuth } from "../context/auth-context"; const PrivateRoute = ({ component: Component, location, basepath, ...rest }) => { const { authenticated } = useAuth(); if (!authenticated) { navigate(basepath + "/login"); return null; } return <Component {...rest} />; }; export default PrivateRoute;这是
PrivateRoute组件的 Gatsby 友好实现。请注意,我们正在将navigate中的@reach/router部分替换为 Gatsby 的实现。这是因为 Gatsby 的实现将以适合 Gatsby 项目的方式处理重定向。如果不进行此替换,当调用navigate时,您将看到一个空白屏幕。您还会注意到我们传递了一个名为basepath的属性。因为我们的路由器不会位于应用程序的顶部,所以PrivateRoute组件必须知道路由器的基路径位置,以确保将相应的用户导航到那里。 -
在
src/pages目录内创建一个名为private的新文件夹。 -
在这个新文件夹中,创建一个名为
[...].js的新文件。使用 Gatsby 的/private。这一步至关重要,因为 Gatsby 不了解我们将设置的路由器,因此它需要理解如果它看到以/private开头的路径,例如/private/login,它需要由这个文件处理,而不是以 404 状态码报错。 -
将以下代码添加到
src/pages/private/[...].js文件中:import React from "react"; import { Router } from "@reach/router"; import Layout from "../components/layout/Layout"; import PrivateRoute from "../components/PrivateRoute"; import { useAuth } from "../context/auth-context"; const LoginPage = () => { const { login } = useAuth(); return ( <Layout> <h1>Login Page</h1> <button onClick={login}>Login</button> </Layout> ); }; const AuthenticatedPage = () => { const { logout } = useAuth(); return ( <Layout> <h1>Authenticated Page</h1> <button onClick={logout}>Logout</button> </Layout> ); };在这里,我们定义了两个可能可见的路径。要么你会看到
AuthenticatedPage,要么如果你没有登录,你会看到登录页面。这两个组件都使用了useAuth钩子来检索它们所需的函数。 -
将以下代码添加到
src/pages/private/[...].js文件中:function PageWithRouter() { const basepath = "/private"; return ( <Router basepath={basepath}> <LoginPage path="login" /> <PrivateRoute basepath={basepath} component={AuthenticatedPage} path="/" /> </Router> ); } export default PageWithRouter;在这一步中,我们定义了我们的
basepath—— 这必须与 Gatsby 页面的路径匹配(在这个例子中是/private)。我们将此值作为属性传递给Router和PrivateRoute。这个例子与 React 例子不同,因为基本路径是需要认证的路径。 -
通过在根目录中运行
npm start来启动项目。如果你尝试导航到/private,你会注意到你被重定向到/private/login,点击登录按钮将重定向你到/private。
有了这个,我们已经学会了如何在 Gatsby 网站的一个特定部分中添加路由。现在,让我们将注意力转向一个当你整个网站需要认证时可以使用的实现。
在 Gatsby 中使用上下文实现网站范围的认证
可能会有这样的情况,你希望你的整个 Gatsby 网站都在认证之后。例如,你可能只为你公司的员工创建了文档网站。让我们看看我们如何使用上下文将每个页面都转换为私有路由:
-
首先,让我们在
components文件夹中创建一个登录组件。将此文件命名为Login.js并添加以下代码:import React from "react"; const Login = ({login}) => { return <button onClick={login}>Login</button>; }; export default Login;你会注意到,与之前我们创建的
Login组件不同,我们不是从上下文中检索login函数。这个原因将在我们创建上下文时变得清晰。 -
在
src中创建一个名为context的文件夹。 -
在
context文件夹中创建一个名为auth-context.js的文件并添加以下代码:import React, { useState, useContext } from "react"; Login component into our authentication context. -
在
AuthProvider中添加以下代码:const [authenticated, setAuthenticated] = useState(false); const login = async () => { // Make authentication request here and only trigger the following if successful. setAuthenticated(true); }; const logout = () => { setAuthenticated(false); }; Login component, being sure to pass in the login function as a prop. This does not cause a route change, which can be a great benefit. When a user navigates to a page, their requested path is not lost by navigating away to a login page and, as such, when the user has successfully logged in, they will jump right back into the application in the place they intended to be. As a developer, this can stop you from having to pass redirect URLs around in the browser, which can be a hassle. If, for some reason, you want to keep a few pages public, you can check for the path in this conditional statement and allow some paths to be accessible, even without being authenticated. Note that even on these pages, the `Login` component will be loaded in, despite the fact it is not being used and will add unnecessary page weight. -
将以下代码添加到你的
gatsby-browser.js和gatsby-ssr.js文件中:import React from "react"; import { AuthProvider } from "./src/context/auth- context"; export const wrapPageElement = ({ element }) => { return <AuthProvider>{element}</AuthProvider>; };我们希望确保认证上下文在应用程序的任何地方都是可用的。通过将此文件添加到
gatsby-browser.js和gatsby-ssr.js,我们可以确保它在任何地方都可以访问。 -
通过在根目录中运行
npm start并导航到网站上的任何页面来启动项目。你应该会发现你需要在查看页面之前被提示登录。
现在我们已经探讨了两种在 Gatsby 应用程序中实现认证体验的方法,让我们总结一下我们学到了什么。
摘要
在本章中,我们探讨了路由和认证体验。我们回顾了 React 中的路由工作原理,并为@reach/router创建了私有路由。然后,我们将这些知识应用到 Gatsby 中,创建了一个只有通过登录才能访问的私有页面。最后,我们研究了如何使用上下文来为需要认证的情况将整个应用程序包裹在认证中。
在下一章中,我们将学习另一个高级概念——如何使用套接字来创建利用实时数据的体验。
第十二章:使用实时数据
您是否曾经点过餐,并看着它向您所在的位置靠近,而无需刷新页面?您可能也见过包裹递送或打车应用中的这种情况。所有这些都使用了实时数据。这是一种在获取后立即呈现的数据形式。因此,在这些例子中,一旦您所使用的服务有了食物、包裹或汽车的位置,它就会将此信息传达给您。便利网站和消息应用使实时数据成为可能的最常见方式是使用WebSocket。
在本章中,我们将涵盖以下主题:
-
WebSocket 简介
-
Socket.io 实战
-
实时网站访问者计数
-
通过房间获取更多见解
技术要求
要完成本章,您需要完成第十一章,创建认证体验。
本章的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-4/tree/main/Chapter12找到。
WebSocket 简介
WebSocket 是客户端和服务器之间的双向通信通道。与REST请求不同,WebSocket 连接的通道在客户端和服务器需要时始终开启,以便相互推送消息,而不是在收到响应时关闭。这种通信通常与低延迟相关联,这意味着它可以以最小的延迟处理大量数据。
那么,它是如何工作的呢?首先,客户端向服务器发送一个 HTTP 请求,请求它打开一个连接。如果服务器同意,它将发送一个状态为 101 的响应,表示它将切换协议。此时,握手完成,一个TCP/IP连接保持开启状态,允许两个设备之间来回传递消息。此连接将保持开启,直到其中一个设备断开连接或失去连接。
在 JavaScript 世界中,最受欢迎的 socket 实现之一是socket.io,它由两部分组成——一个 Node.js 服务器和一个 JavaScript 客户端库。我们将在下一节通过创建一个最小示例来查看 socket.io 的实战。
快速提示
注意,还有其他几种语言实现的socket.io服务器和客户端库可用。如果您想将 socket 服务器(我们将在本章创建)与 Gatsby 网站以外的其他内容结合使用,这可能很有帮助。
Socket.io 实战
在这个演示中,我们将创建一个接受 socket 连接的服务器。当它从客户端收到消息时,它将将其记录到控制台。让我们先创建服务器,然后再转到客户端:
-
在您的根目录下创建一个名为
server的文件夹。 -
在
server文件夹中打开一个终端,并运行以下命令:npm init -y这将在文件夹中设置一个空的 npm 包。
-
在同一终端中,运行以下命令:
express dependency for creating our server and the socket.io library. -
在
server文件夹中创建一个app.js文件,并添加以下代码:const PORT = 3000 const express = require("express"); const server = express() .listen(PORT, () => console.log('Listening on ${PORT}'));这创建了一个最小化的 Express 服务器,它监听端口
3000上的请求。正如我们已经学到的,这个套接字连接是通过 HTTP 请求建立的,并且需要一个 HTTP 服务器来完成这个操作。 -
通过在
server文件夹内打开一个终端并运行以下命令来验证服务器是否正常工作:Listening on 3000 printed to the console. -
使用以下代码更新
app.js文件:const PORT = 3000 const express = require("express"); const server = express() .listen(PORT, () => console.log('Listening on ${PORT}')); socket.io for the client-server handshake. We then tell our socket server how to handle events from clients. In this instance, if a client socket sends an event of the message type, we log it to the console. -
在继续之前,我们必须添加一个
server文件夹并运行以下命令:cors library, which acts as middleware within our Express application to enable CORS. -
现在,使用以下代码更新你的
app.js文件:const PORT = 3000; const express = require("express"); allowlist of origins that are allowed to access the server. The middleware checks the origin of any request to ensure that the origin is present in this list and is therefore allowed. If a request comes from an origin that is not on the list, the cross-origin request will be blocked. In this case, we have added localhost:8000, which is the default development port for Gatsby. If this changes or you are hosting the application, this list will need to be updated. -
现在我们已经设置了套接字服务器,让我们通过使用 Gatsby 作为套接字客户端与它交互。返回到你的 Gatsby 网站根目录。在这里打开一个终端并运行以下命令:
npm i socket.io-client如库的名称所暗示的,这安装了我们将用于与我们的 WebSocket 服务器通信的 socket.io 客户端库。
-
在你的
pages文件夹中创建一个新文件,命名为socket.js,并将以下代码添加到该文件中:import React from "react"; socket.io-client package. Inside a useEffect, we create the socket connection by using the default export from socket.io-client with the server URL string as an argument. In our case, the server port was defined as 3000, so we added http://localhost:3000. This one line of code abstracts all the logic around the client-server handshake, so all you need to focus on is firing the messages you want to send. We then set the socket in our useState so that we can use it within the page. It's best to create the socket connection in useEffect as we only want this connection to be established once. If the page re-renders, we do not want the socket to reconnect as this would be perceived as a new connection by the server. The return statement in our useEffect ensures that the socket connection is closed when the component dismounts. -
继续编辑
socket.js并添加以下代码:const sendMessage = () => { socket && socket.emit("message", value); }; return ( <Layout> <div className="max-w-5xl mx-auto py-16 lg:py-24 flex flex-col prose space-y-2 "> <h1>Message The Server</h1> <label htmlFor="message">Your Message:</label> <input id="message" className="border-blue-700 border-2" onChange={(e) => setValue(e.target.value)} /> <button onClick={sendMessage function is called, which uses socket.emit (if there is a socket available in the state), which emits a message from this client to the server. The first argument is the message type, while the second argument is the body of the message. In this case, we are just sending a string, but you could also send an object with multiple key-value pairs. If you were to send an object, there is no need to JSON.stringify it as the library handles all that for you. -
启动你的 Gatsby 开发服务器,并确保你的套接字服务器也在运行。导航到
localhost:8000/socket,输入一条消息,然后点击发送消息。如果有幸,你的消息内容现在应该已经记录在你的服务器终端中。恭喜你 – 你刚刚通过套接字发送了第一条消息!
现在,让我们扩展这个演示,以便客户端可以接收来自服务器的通信。作为一个例子,让我们让服务器在收到消息时返回三个随机问候语中的一个:
-
首先,我们需要修改服务器处理消息的方式。使用以下代码修改套接字服务器的连接配置:
io.on("connection", (socket) => { socket.on("message", (msg) => { console.log("message: " + msg); socket.emit( "message", ["Hi there!", "Hello!", "Howdy"][Math.floor(Math.random() * 3)] ); }); });现在,除了记录从客户端接收到的消息外,我们还会向同一客户端发送一些信息。在这种情况下,我们选择发送一个随机的问候语。
-
使用我们的 Gatsby 页面,我们需要告诉它期待并处理特定类型的消息。这就像事件监听器一样,所以这应该对你来说很熟悉:
export default function SocketDemo() { const [socket, setSocket] = React.useState(null); const [value, setValue] = React.useState(""); useState hook to store the server messages. As we may receive more than one, we set this to an empty array that we can push elements to. Then, we defined a second useEffect. If the socket connection has been established, this function listens for messages from the server of the message type. If it receives one, it adds the body of the message to the server message list. -
更新
page组件的渲染:return ( <Layout> <div className="max-w-5xl mx-auto py-16 lg:py-24 flex flex-col prose space-y-2 "> <h1>Message The Server</h1> <label htmlFor="message">Your Message:</label> <input id="message" className="border-blue-700 border-2" onChange={(e) => setValue(e.target.value)} /> <button onClick={sendMessage} className="btn"> Send message </button> <label>Server Messages:</label> <ul> {serverMessages.map((message, index) => ( <li key={index}>{message}</li> ))} </ul> </div> </Layout> ); }在渲染中,我们可以遍历服务器消息并将它们以项目符号列表的形式渲染到屏幕上。
-
启动你的 Gatsby 开发服务器,并确保你的套接字服务器也在运行。导航到
localhost:8000/socket,输入一条消息,然后点击发送消息:

图 12.1 – 套接字演示页面
你的消息应该在服务器的终端中记录,但此外,服务器还应该发送一条消息回来。它应该显示在 发送消息 按钮下方。这个过程发生的速度可能会感觉非常快。而且当连接良好时,几乎感觉就像服务器的消息是由你的按钮点击触发的。
我们现在对套接字连接的工作原理有了清晰的理解,并且我们已经成功地在客户端和服务器之间发送了消息。现在,让我们应用所学知识,用这项技术为我们的 Gatsby 网站构建一些有用的功能——在我们的网站页脚中实现实时访客计数。
实时网站访客计数
由于在 Socket.io in action 部分中,套接字连接被隔离在单个页面上,因此这个设置的配置需要与之前的例子略有不同。然而,我们的网站页脚不在单个页面上,而是在每个页面上!一个能够很好地实现这一点的方案是将网站包裹在某个上下文中。通过这样做,如果我们需要的话,我们就可以在其他应用程序的部分访问计数。让我们一起来尝试这种方法:
-
使用以下代码修改套接字服务器的连接配置:
io.on("connection", (socket) => { io.emit("count", io.engine.clientsCount); socket.on("disconnect", function () { io.emit("count", io.engine.clientsCount); }); });我们对这个配置做了相当大的改动,所以让我们来分解一下。当一个新套接字连接到服务器时,我们使用
io.emit。这个函数向所有已连接的客户端发送消息,而不是单个套接字。套接字类型是count,正文包含io.engine.clientsCount,这是已连接客户端的数量。如果你在每次新客户端连接时使用这个,每个人都会知道计数已经改变。然后,我们必须确保在客户端断开连接时也更新计数。为此,当服务器看到客户端掉线时,我们触发相同的io.emit。 -
如果你还没有创建,请在
src中创建一个名为context的新文件夹。 -
创建一个名为
stats-context.js的新文件,并添加以下代码:import React, { useState, useContext } from "react"; import openSocket from "socket.io-client"; const socket = openSocket("http://localhost:3000"); const StatsContext = React.createContext(); export const StatsProvider = ({ ...props }) => { {/* Code continued in next step */} }; export const useStats = () => useContext(StatsContext); export default StatsContext;在这里,我们正在设置我们统计上下文的模板。我们创建一个
useStats钩子来访问我们将在下一步定义的上下文值。 -
在
StatsProvider中添加以下代码:const [socket, setSocket] = React.useState(null); const [liveVisitorCount, setLiveVisitorCount] = useState(0); React.useEffect(() => { const newSocket = openSocket("http://localhost:3000"); setSocket(newSocket); return () => newSocket.close(); }, [setSocket]); React.useEffect(() => { if (socket) { socket.on("count", (count) => { setLiveVisitorCount(count); }); } }, [socket, setLiveVisitorCount]); return ( <StatsContext.Provider value={{ liveVisitorCount, connected: socket && socket.connected, }} {...props} /> );在页面级别的演示中,我们使用
useEffect设置套接字。我们在这里做同样的事情,以确保它只发生一次。然后,我们创建第二个useEffect,当连接到服务器时,将监听count类型的消息。如果收到这样的消息,它将更新状态中的计数,然后通过useStats钩子在整个应用程序中可用。 -
更新你的
gatsby-browser.js和gatbsy-ssr.js文件,添加以下代码:import React from "react"; import { StatsProvider } from "./src/context/stats- context"; export const wrapPageElement = ({ element }) => { return <StatsProvider>{element}</StatsProvider>; };我们希望确保计数的上下文在整个应用程序中可用。通过将此文件添加到
gatsby-browser.js和gatsby-ssr.js文件中,我们可以确保它在任何地方都可以访问。 -
在
src/components/layout中创建一个名为VisitorCountBadge.js的文件,并添加以下代码到其中:import React from "react"; import { useStats } from "../../context/stats- context"; const VisitorCountBadge = () => { const { liveVisitorCount, connected } = useStats(); return ( <p className={'${connected? "bg-blue-200" :"bg- red-200"} px-2 py-1 inline-block rounded'}> Visitors: {liveVisitorCount} </p> ); }; export default VisitorCountBadge;在这里,我们使用
useStats钩子来检索liveVistorCount和连接状态。徽章的颜色取决于连接状态——如果是蓝色,则表示我们已连接到服务器;如果不是,它将是红色。然后,我们在徽章中渲染liveVistorCount,以便用户可以看到。重要提示
在这里,我们仅用颜色作为示例来表示应用程序状态。在生产环境中,单独使用颜色来表示应用程序状态是不应该的,因为它可能会使色盲用户无法访问你的应用程序。更好的做法是将颜色与另一个视觉指示器结合使用,例如文本,或者至少是一个
aria-label。 -
使用以下代码更新你的
Footer组件文件:import React from "react"; Footer component, it will be visible on every page that utilizes our Layout component. -
启动你的 Gatsby 开发服务器,并确保你的 socket 服务器也在运行。导航到
localhost:8000,你应该能看到访问者计数。如果你复制浏览器标签页,访问者计数会上升,而如果你关闭一个标签页,计数会下降。最后,如果你关闭运行 socket 服务器的终端,你应该看到徽章变为红色,表示它已与服务器断开连接。
我们现在已经实现了一个工作状态下的当前访问者计数。让我们通过使用房间来扩展这个功能。
通过房间获得更深入的见解
我们还没有讨论过 socket.io 事件中的一个元素,但在我们的应用程序中可能会有很大的好处——join 和 leave。服务器可以向房间发出消息,向连接到服务器的客户端子集广播事件。
为了演示房间的概念,我们将把我们的访问者计数分解成更细粒度的统计数据。我们不仅会向用户显示网站上总用户数,还会提供他们当前页面上有多少人的详细信息。让我们开始吧:
-
更新你的
server/app.js文件的 socket 代码,使其包括一个新的事件:// defined at top of file const pathToRoom = (path) => 'Page-${path}'; // defined in socket configuration socket.on("page-update", ({ currentPage, previousPage }) => { if (previousPage) { const previousRoom = pathToRoom(previousPage); socket.leave(previousRoom); io.to(previousRoom).emit( "page-count", io.sockets.adapter.rooms.get(previousRoom)?.size ); } const roomToJoin = pathToRoom(currentPage); socket.join(roomToJoin); io.to(roomToJoin).emit( "page-count", io.sockets.adapter.rooms.get(roomToJoin).size ); });现在,我们期望客户端发送给我们一个新的
page-update类型的事件。正文包含一个currentPage和一个可选的previousPage。我们将使用这两条信息让他们加入当前页面的房间,并从他们之前页面的房间中移除。我们定义了一个名为
pathToRoom的函数,我们用它来获取用户所在路径并将其转换为可以用来作为房间标识符的字符串。如果客户端已经发送了前一页,我们知道这不是他们访问该网站的第一个页面,因此他们需要从previousPage房间中移除。为此,我们可以使用带有房间标识符作为参数的socket.leave函数。然后,我们可以使用io.to(previousRoom).emit向仍然在该页面的用户发出新的减少计数。之后,我们可以使用currentPage来确定用户应该加入的新房间,并向该房间(包括新用户)的用户发出新的计数。快速提示
socket.leave和socket.join仅在服务器端。套接字不能在客户端离开和加入房间。 -
使用以下代码更新
disconnect事件:socket.on("disconnect", function () { io.emit("count", io.engine.clientsCount); for(room of io.sockets.adapter.rooms){ io.to(room[0]).emit( "page-count", io.sockets.adapter.rooms.get(room[0])?.size ); }; });当套接字断开连接时,我们遍历所有打开的房间,并将新的客户端数量发送给它们。
-
使用以下代码更新你的
gatsby-browser.js和gatbsy-ssr.js文件:import React from "react"; import { StatsProvider } from "./src/context/stats- context"; export const wrapPageElement = ({ element, props }) => { return <StatsProvider location object that Gatsby provides via props to StatsProvider. The location object contains a pathname variable, which will tell us what path the user is currently at. -
导航到你的
stats-context.js文件并更新StatsProvider参数:export const StatsProvider = ({ location, ...props }) => { // Code continued in next step }我们将需要使用我们现在传递的位置,所以让我们用 props 解构它。
-
在
StatsProvider的顶部添加两个新的 React 钩子:useState hook. We will also need to keep a record of the previous location, which we can do using a useRef React hook. -
更新与
StatsProvider中传入的套接字事件相关的useEffect:React.useEffect(() => { if (socket) { socket.on("count", (count) => { setLiveVisitorCount(count); }); page-count event, we update the pageVisitorCount value in the state using the setPageVisitorCount function. -
在
StatsProvider中创建一个新的useEffect:React.useEffect(() => { if (socket && previousLocation.current !== location.pathname) { socket.emit("page-update", { currentPage: location.pathname, previousPage: previousLocation.current, }); previousLocation.current = location.pathname; } }, [location, socket]);这里是代码中最关键的部分之一。我们将位置添加到
useEffect依赖数组中,这样每当用户在页面之间导航时,这段代码就会运行。在useEffect中,我们检查套接字是否在状态中可用,并且位置更新不匹配当前位置。如果这两个条件都满足,我们就向服务器发出page-update,告诉它我们移动到了哪里,以便它可以跟踪位置。 -
更新
StatsProvider.js文件的渲染:return ( <StatsContext.Provider value={{ liveVisitorCount, pageVisitorCount in the provider's value prop, we can access it via the useStats hook in our components. -
使用以下代码更新
components/VistorCountBadge.js:const VisitorCountBadge = () => { const { liveVisitorCount, pageVisitorCount, connected } = useStats(); return ( <p className={'${connected? "bg-blue-200" :"bg- red-200"} px-2 py-1 inline-block rounded'}> {pageVisitorCount} of {liveVisitorCount} visitors on this page </p> ); };在这里,我们正在从
useStats钩子中检索pageVisitorCount并将其渲染到屏幕上,以便用户可以在徽章中看到该值。 -
启动你的 Gatsby 开发服务器并确保你的套接字服务器也在运行。导航到
localhost:8000;你应该能看到访问者和页面计数。如果你复制浏览器标签页,这两个数字都应该增加,如果你将其中一个标签页导航到网站上的另一个页面,你应该看到两个标签页的页面访问者计数更新。
现在我们已经使用套接字实现了一个完整的功能,让我们总结一下我们学到了什么。
摘要
在本章中,我们学习了有关网络套接字的所有知识,以及我们如何使用它们在我们的 Gatsby 应用程序中利用实时数据。然后,我们实现了一个显示当前页面以及整个网站人数的工作访问者计数器。访问者计数器统计是个人网站中网络套接字可能应用的一大类中的一种。也许你可以将在这里学到的知识尝试应用于实现文章反应、投票甚至聊天应用程序?
在下一章中,我们将学习我们的最后一个高级概念——本地化。我们将学习如何使我们的 Gatsby 网站支持多语言以适应国际受众。
第十三章:国际化与本地化
本章全部关于向国际受众开放您的网站。我们将讨论您可以使用以使网站翻译在扩展时变得简单的模式!以英语创建您的网站使其对世界上说英语的 13 亿人可访问。然而,如果我们为用户提供本地化选项,我们可以将网站翻译成任何语言,因此使我们的网站对所有用户可访问。
在本章中,我们将涵盖以下主题:
-
理解本地化和国际化
-
实施国际化路由
-
程序化页面的页面翻译
-
为单实例页面提供区域翻译
技术要求
为了导航本章,您需要完成第十二章**,使用实时数据。
本章中提供的代码可以在github.com/PacktPublishing/Elevating-React-Web-Development-with-Gatsby-3/tree/main/Chapter13找到。
理解本地化和国际化
尽管本章的最终目标是设置本地化,但我们可以通过首先实施国际化来简化事情。本地化和国际化的术语经常被混淆,所以让我们正确地定义这些术语:
-
国际化:确保您的网站以支持不同语言、区域和文化的这种方式创建的过程。国际化完全是关于在您网站的设计和开发中采取主动,以确保您在将其引入新市场时不必完全重新设计它。例如,这可能包括允许文本从右到左以及从左到右显示。
-
本地化:通常在国际化之后进行,本地化是将您的网站适应以满足新的区域要求的过程。这可能包括添加语言或文化要求。
通过在项目初期投入时间确保国际化正确,当您稍后需要添加新的区域设置时,您可以节省大量时间。现在让我们看看我们如何通过国际化策略修改我们的项目。
实施国际化路由
大型网站为了适应本地化,通常会在所有路径前加上语言代码。以我们的关于页面为例——英文(默认)语言的页面位于/about,但法语的页面可能位于/fr/about,德语的页面可能位于/de/about。
现在我们为我们的默认语言英语实现此模式,并添加法语作为次要语言。我们可以借助gatsby-theme-i18n轻松实现这一点:
-
安装新的依赖项:
npm install gatsby-theme-i18n gatsby-plugin-react- helmet react-helmet在这里,我们正在安装
gatsby-theme-i18n包及其依赖项。此包会自动为我们创建路由前缀。它还会向文档头部添加language和alternate标签。这有助于 Google 识别两个页面包含相同内容的不同语言。重要提示
此主题使用
react-helmet,这可能与我们在其他章节中使用的react-helmet-async包冲突。确保在使用两者时,你的文档头部设置如预期,如果遇到问题,请坚持使用单个包。 -
在你的
gatsby-config.js文件中包含gatsby-theme-i18n插件:{ resolve: 'gatsby-theme-i18n', options: { defaultLang: 'en', configPath: require.resolve('./i18n/config.json'), }, },作为此配置的一部分,我们添加了一些选项。
defaultLang指的是网站上将使用的默认语言——在这种情况下,这是英语,所以我们使用语言代码en。configPath是我们将设置i18n的配置路径。这通常是在其自己的文件夹中,我们将在下一步创建该文件夹。 -
在你的根目录中创建一个名为
i18n的文件夹。 -
在
i18n文件夹中创建一个名为config.json的新文件,并添加以下内容:[ { "code": "en", "hrefLang": "en-US", "name": "English", "localName": "English", "langDir": "ltr", "dateFormat": "MM/DD/YYYY" }, { "code": "fr", "hrefLang": "fr-FR", "name": "French", "localName": "Francais", "langDir": "ltr", "dateFormat": "DD/MM/YYYY" } ]在这里,我们为我们要支持的每个区域设置定义一个配置非常重要。每个配置对象必须包含以下内容:
a.
code:这指的是你将用于访问此区域设置的语言的代码。虽然你可以为每种语言设置任何你想要的代码,但最好保持它们易于识别,例如,fr用于法语,en用于英语。b.
hrefLang:这是用于 HTML 头部hrefLang标签属性的值。它用于告诉 Google 你在特定页面上使用的是哪种语言。c.
name:开发者母语中该语言的名字。d.
localName:这是母语使用者拼写该语言名字的方式。e.
langDir:这是给定区域设置中文字的阅读方向。这可能是ltr(从左到右)或rtl(从右到左)。f.
dateFormat:这是区域设置中使用的日期格式。快速提示
在稍后添加区域设置时,这是唯一需要更新的文件,以创建所需的路由来支持该区域设置。
-
最后,将你网站上 Gatsby
Link组件的实例替换为来自gatbsy-theme-i18n的LocalizedLink组件:LocalizedLink is a component that extends the Link component with a language prop. By specifying a language's code (from the i18n/config.json file), we route the user to the corresponding page in that specific language. If no language is specified, it will keep the user in their currently active locale. In the preceding example code, we have modified the footer to include links to the index page for both English and French visitors. This will allow site visitors to switch between the locales on any page. -
让我们验证之前的步骤。首先,启动你的 Gatsby 开发服务器 GraphQL 层(通常位于
http://localhost:8000/_graphql)并运行以下查询:query MyQuery { allSitePage { nodes { path } } }在返回的数据对象中,你应该能够看到所有区域设置的路径节点:
{ "data": { "allSitePage": { "nodes": [ { "path": "/" }, { "path": "/fr/ " }, { "path": "/blog" }, { "path": "/fr/blog" }, // Continued list ] } }, "extensions": {} } -
最后,导航到你的 Gatsby 开发站点。现在你的页脚应该包含指向两种语言的链接:

图 13.1 – 带有语言切换功能的站点页脚
点击 /fr/ 和点击 /。
现在我们已经为不同的地区设置了页面,让我们确保在这些页面上显示与语言适当的内容。让我们首先查看由程序生成的页面。
程序性页面的页面翻译
为了能够提供翻译成两种语言的页面,如文章和博客文章,我们需要提供两种语言的内容。让我们看看我们如何构建我们的项目,以便不同语言的帖子可供网站访客访问。
gatsby-theme-i18n 内置了对处理 gatsby-plugin-mdx 插件的支持,如果插件配置选项中添加了扩展名,则将 .md 文件视为 .mdx:
{
resolve: 'gatsby-plugin-mdx',
options: {
extensions: ['.mdx', '.md'],
},
}
我将使用此演示中的本地文件帖子,但一旦内容被摄入到 Gatsby 的数据层,相同的步骤也适用于 CMS 内容:
-
首先,我们需要以使我们能够轻松识别不同语言中的重复内容的方式重新组织我们的博客文章文件夹。而不是使用文件名,使用文件夹名来分组它们。在
/blog-posts中为每篇文章创建一个文件夹。这些文件夹的好名称格式可能是YYYY-mm-DD-Post-Title。这使得文件夹可以按日期排序,同时也告诉你文件夹的内容,而无需打开文件夹。 -
在此文件夹中,放置相应的默认语言博客文章并将其重命名为
index.mdx。确保 MDX 文件在前端包含一个slug。一个例子可能如下所示:--- type: Blog title: My First Hackathon Experience desc: This post is all about my learnings from my first hackathon experience in London. date: 2020-06-20 hero: ../../../assets/images/cover-1.jpeg tags: [hackathon, webdev, ux] slug: /my-first-post/ --- My First Hackathon Experience was great! Rest of content... -
为你希望添加的每个博客文章重复 步骤 2。
-
在此文件夹中创建第二个文件,命名为
index.fr.mdx。此文件名在文件名和扩展名之间添加了地区代码。在本例中,我们使用的是法国(法语),因此地区代码是fr。通过将所有文本值翻译成法语来复制index.mdx文件的 frontmatter。slug、type、hero、date和tags必须在这两个文件中保持相同。示例中从 步骤 2 开始生成的结果文件如下所示:--- type: Blog title: Ma première expérience de hackathon desc: Ce post est tout sur mes apprentissages de ma première expérience de hackathon à Londres. date: 2020-06-20 hero: ../../../assets/images/cover-1.jpeg tags: [hackathon, webdev, ux] slug: /my-first-post/ --- Ma première expérience de hackathon était super ! Rest of content... -
为你希望支持的任何其他语言和地区重复 步骤 4。
-
使用以下配置更新你的
gatsby-node.js文件中的博客文章页面创建配置:exports.createPages = async ({ actions, graphql, reporter }) => { const { createPage } = actions; const BlogPostTemplate = path.resolve('./src/templates/blog-page.js'); const BlogPostQuery = await graphql(' { allMdx(filter: { frontmatter: { type: { eq: "Blog" } } }) { nodes { slug from the frontmatter of the MDX files. We then use this to create the page with the createPage function, ensuring that we also provide the slug to the component as context. atsby-theme-i18n listens for page creation and will additionally create the same page for each locale without any additional configuration! It will also add two fields to the MDX nodes in our GraphQL data layer – locale and isDefault, which tell you what locale the MDX is and whether the MDX is the default locale, respectively. -
现在我们需要告诉 Gatsby 在正确的地区路径上使用正确的 MDX 文件。如果不执行此步骤,你的网站在创建博客文章时将找到第一个匹配
slug的 MDX 文件。这可能与地区不匹配,因为我们有多个具有相同slug的文件,可能会导致我们在翻译中迷失方向。首先,更新博客页面模板(位于src/templates/blog-page.js)查询,如下所示:export const pageQuery = graphql' query(gatsby-theme-i18n plugin and use it to filter the MDX blog posts to those that match the specified locale. This will ensure that we render the blog post in the correct language on any blog page. -
在
src/templates/blog-preview.js文件中执行完全相同的步骤:export const pageQuery = graphql' query($locale: String!,$skip: Int!, $limit: Int!) { blogposts: allMdx( limit: $limit skip: $skip filter: {frontmatter: {type: {eq: "Blog"}}, fields: {locale: { eq: $locale }}} sort: { fields: frontmatter___date, order: DESC } ) { nodes { frontmatter { date title tags desc slug hero { childImageSharp { gatsbyImageData(width: 240, height: 160, placeholder: BLURRED) } } } } } } '; -
让我们验证之前的步骤。首先,启动你的 Gatsby 开发服务器 GraphQL 层(通常位于
http://localhost:8000/_graphql)并运行以下查询:query MyQuery { blogposts: allMdx(filter: {frontmatter: {type: {eq: "Blog"}}}) { nodes { fields { locale isDefault } frontmatter { slug } } } }在这里,我们正在查询所有类型为
Blog的 MDX,并检索地区信息,无论该地区是否为默认地区,以及slug。结果应如下所示:{ "data": { "blogposts": { "nodes": [ { "fields": { "locale": "en", "isDefault": true }, "frontmatter": { "slug": "/my-first-post/" } }, { "fields": { "locale": "fr", "isDefault": false }, "frontmatter": { "slug": "/my-first-post/" } } ] } } }对于任何给定的
slug,每个区域都应该有一个结果。假设情况是这样,您可以在 Gatsby 开发站点的/blog上导航。您应该看到您默认语言的博客内容:
![Figure 13.2 – 英语博客页面
![img/Figure_13.02_B15983.jpg]
图 13.2 – 英语博客页面
导航到 /fr/blog,您应该看到法语的内容:
![Figure 13.3 – 法语博客页面
![img/Figure_13.03_B15983.jpg]
图 13.3 – 法语博客页面
快速提示
如果您在点击博客文章时总是收到默认区域版本的版本,最可能的原因是您在导航到页面时使用的是 Link 而不是 LocalizedLink。请参阅本章 实现国际化路由 部分的 步骤 5。
我们可以使用这种策略轻松地为我们的程序生成页面提供翻译。现在让我们学习如何为单个实例页面设置翻译。
为单个实例页面提供区域翻译
对于静态页面,我们需要采取不同的方法来提供翻译。对于任何需要翻译的字符串,我们不能再将其值放在我们的 JSX 中。一个非常常见的方法是使用 react-i18next,它有一个名为 useTranslation 的优秀钩子,允许您根据区域切换字符串。现在让我们使用这个钩子来为网站访客翻译首页上的内容:
-
在您的根目录中打开终端并添加以下新依赖项:
npm install gatsby-theme-i18n-react-i18next react- i18next i18next在这里,我们正在安装
gatsby-theme-i18n-react-i18next包及其依赖项。此包是一个 Gatsby 主题插件,通过将我们的站点包裹在react-i18next的上下文提供者中来为我们提供区域支持。在底层,此包使用wrapPageElement在gatsby-browser.js中包裹站点,就像我们在 第十二章**,使用实时数据* 中所做的那样。 -
在您的
gatsby-config.js文件中包含gatsby-theme-i18n-react-i18next插件:{ resolve: 'gatsby-theme-i18n-react-i18next', options: { locales: './i18n/locales', i18nextOptions: { ns: ["globals"], }, }, },作为此配置的一部分,我们添加了一些选项。
locales指的是我们将存储翻译的位置。i18nextOptions接受i18next接受的任何配置选项(完整列表可在www.i18next.com/overview/configuration-options找到)。在这里,我们传递了ns选项,它是一个要使用的命名空间数组。对于这个例子,我们只将创建一个名为globals的单个命名空间,但随着您站点的增长,您可能希望添加更多。重要提示
gatsby-theme-i18n-react-18next是一个附加包,它只能与gatsby-theme-i18n一起使用。确保按照 实现国际化路由 部分的步骤安装此包。 -
在
i18n中创建一个名为locales的新文件夹。 -
在
locales中,为您的站点支持的每个区域创建一个新的文件夹,例如,en和fr。 -
对于每个命名空间,在
locale文件夹中创建一个 JSON 文件。在我们的例子中,我们需要在每个文件夹中为我们的globals命名空间创建一个名为globals.json的单个文件。该文件应包含您需要的任何翻译,可以通过在所有文件中保持一致的键来检索。您的英语文件(应位于i18n/locales/en/globals.json)应包含以下内容:{ "header": "Site Header", "yourName": "Your Name", "aboutMe": "About Me", "location": "London, UK", "bio": "A short biography about me" }您的法语文件(应位于
i18n/locales/fr/globals.json)应包含以下内容:{ "header": "En-tête du site", "yourName": "Votre nom", "aboutMe": "À propos de moi", "location": "France, Paris", "bio": "Une courte biographie sur moi." } -
要在 Gatsby 页面组件中使用翻译,我们可以使用来自
react-i18next的useTranslation钩子。以索引页面上的“关于我”链接(位于src/pages/index.js)为例:import React from "react"; useTranslation from reacti18next. Then within the page component, we invoke the hook specifying the namespace we wish to use. In our case, this is the globals namespace we have created. The t function can be used to retrieve the translation from the namespace by passing in a valid key from the globals.json objects created in *Step 5*. t("aboutMe") will return *About me* when on the en locale and *À propos de moi* when on the fr locale. -
我们也可以在任何一个其他组件中使用完全相同的流程,例如我们的页眉,例如:
import React from "react"; import { useTranslation } from "react-i18next"; import { LocalizedLink } from "gatsby-theme-i18n" const Header = () => { const { t } = useTranslation("globals"); return( <header className="px-2 border-b w-full max-w-7xl mx-auto py-4 flex items-center justify-between"> <LocalizedLink to="/"> <div className="flex items-center space-x-2 hover:text-blue-600"> <p className="font-bold text- 2xl">{t("header")}</p> </div> </LocalizedLink> </header> ) }; export default Header;如果需要,您甚至可以在 MDX 内容中使用的 React 组件内部使用此功能!
-
通过导航到您的 Gatsby 开发站点的索引页面来验证您的实现。通过修改路径或使用
Footer组件来切换区域设置,您应该会看到任何使用useTranslation的副本更新。
我们只是刚刚触及了 i18next 提供的功能的表面。访问他们的文档www.i18next.com/以了解更多他们提供的强大功能。使用这种策略和前面的章节,您现在应该对翻译您网站的任何方面都感到自信。现在让我们总结一下我们学到了什么。
摘要
在这一章的最后,我们学习了如何使我们的网站对全球受众可访问。我们首先确定了国际化与本地化的区别。然后我们使用 gatsby-theme-i18n 插件为我们的区域创建路由。我们创建了不同语言的程序性博客文章,并确保在访问区域时可以看到正确的翻译。最后,我们还使用 gatsby-theme-i18n-react-i18next 插件翻译了我们的静态页面。在这两个插件之间,您现在可以翻译您网站上的任何内容。
嗨!
我是塞缪尔·拉尔森-迪士尼,著有《提升 React Web 开发与 Gatsby》。我真心希望您喜欢阅读这本书,并发现它对提高您在 Gatsby 中的生产力和效率很有用。
如果您能在亚马逊上分享您对《提升 React Web 开发与 Gatsby》的看法并留下评论,这将对我(以及其他潜在读者!)真的非常有帮助。
点击以下链接或扫描二维码留下您的评论:

您的评论将帮助我了解这本书中哪些地方做得好,以及未来版本中哪些地方可以改进,所以这真的非常感谢。
祝好运,

塞缪尔·拉尔森-迪士尼

订阅我们的在线数字图书馆,全面访问超过 7,000 本书和视频,以及行业领先的工具,帮助你规划个人发展并推进职业生涯。欲了解更多信息,请访问我们的网站。
第十四章:为什么订阅?
-
使用来自 4,000 多位行业专业人士的实用电子书和视频,节省学习时间,多花时间编码
-
通过为你量身定制的技能计划提高你的学习效果
-
每月免费获得一本电子书或视频
-
完全可搜索,便于快速访问关键信息
-
复制粘贴、打印和收藏内容
你知道 Packt 为每本书都提供 eBook 版本,包括 PDF 和 ePub 文件吗?你可以在 packt.com 升级到 eBook 版本,并且作为印刷版书籍的顾客,你有权获得 eBook 复印本的折扣。如需了解更多详情,请联系我们 customercare@packtpub.com。
在 www.packt.com,你还可以阅读一系列免费的技术文章,注册各种免费通讯,并享受 Packt 书籍和电子书的独家折扣和优惠。
其他你可能喜欢的书籍
如果你喜欢这本书,你可能对 Packt 的其他书籍也感兴趣:
全栈 React 项目
萨玛·胡克
ISBN: 978-1-83921-541-4
-
将基于 MERN 的基本应用程序扩展以构建各种应用程序
-
使用 Socket.IO 添加实时通信功能
-
使用 Victory 为 React 应用程序实现数据可视化功能
-
使用 MongoDB GridFS 开发媒体流应用程序
-
通过实现具有数据的服务器端渲染来提高你的 MERN 应用程序的 SEO
-
使用 JSON Web Tokens 实现用户身份验证和授权
-
设置并使用 React 360 开发具有 VR 功能的用户界面
-
使用行业最佳实践使你的 MERN 栈应用程序可靠且可扩展
React 项目
罗伊·德克斯
ISBN: 978-1-80107-063-8
-
使用各种现代 React 工具和框架创建各种应用程序
-
了解 React Hooks 如何使 React 应用程序的状态管理现代化
-
使用样式化和可重用的 React 组件开发 Web 应用程序
-
使用 Jest、React Testing Library 和 Cypress 构建 test-driven 的 React 应用程序
-
使用 GraphQL、Apollo 和 React 理解全栈开发
-
使用 React 和 Next.js 进行服务器端渲染
-
使用 React Native 和 Expo 创建动画游戏
-
使用 React Native 设计跨平台游戏的手势和动画
Packt 正在寻找像你这样的作者
如果您有兴趣成为 Packt 的作者,请访问authors.packtpub.com并今天申请。我们已与成千上万的开发者和技术专业人士合作,就像您一样,帮助他们将见解分享给全球技术社区。您可以提交一般申请,申请我们正在招募作者的特定热门话题,或者提交您自己的想法。





























浙公网安备 33010602011771号