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/err… 并填写表格。
盗版:如果您在互联网上发现我们作品的任何非法副本,我们将非常感激您能提供位置地址或网站名称。请通过电子邮件发送至 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-fir…。虽然我们现在不会在这个 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 – 着陆页预览
图 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 中创建字段
输入一个合适的显示名称和 API ID,例如hobbies。将描述写为我拥有的爱好集合。我还勾选了允许多个值,这样我们就可以存储一个爱好列表而不是单个爱好。点击更新以保存此配置。
-
导航到网站的“内容”部分。在页面右上角点击创建项目。这将打开以下窗口:
图 3.4 – 在 GraphCMS 中填充内容
我们现在可以开始填写我们的爱好,在添加时将它们添加到列表中。一旦完成,请点击页面右上角的保存。
-
返回到内容窗口,您会看到您创建的 Icebreaker 处于草稿模式。这意味着我们还不满意内容,而且我们还不能从 API 中检索它:
图 3.5 – GraphCMS 内容和其草稿状态
-
要使内容生效,我们需要通过选择项目然后点击发布按钮来发布它。
-
接下来,我们需要修改端点设置以允许公共 API 访问。默认情况下,您的 GraphCMS API 无法从其平台外部访问。您可以更改公共 API 访问的设置或创建具有访问权限的永久性认证令牌。通常,我倾向于保持我的数据公开,因为即使不知道 API 的 URL,它仍然可以检索。由于默认情况下无法编辑,所以所有内容仍然会在我网站上公开显示。
导航到设置,然后是API 访问,并修改您的公共 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 – 分页组件状态
在第一种情况下,当前页面是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 网站中的艺术。首先,我们将了解为什么导入图片并不那么简单,然后再创建那些渐进式加载且性能良好的图片。