如何使用Gatsby和TypeScript构建书店登陆页面
作为 Write for DOnations 计划的一部分,作者选择了 Diversity in Tech Fund 来接受捐赠。
介绍
登陆页面是宣传产品或服务的网页,为客户在到达网站时提供登陆的地方。 对于企业而言,它们通常是在线广告和营销电子邮件中链接的目的地。 商业登陆页面的主要目标是将访问者转变为潜在客户或客户。 因此,构建登录页面对于 Web 开发人员来说是一项宝贵的技能。
在本教程中,您将使用以下两种技术构建登录页面:
- Gatsby,一个基于 React 的前端框架,旨在生成静态网站。 Gatsby 允许您快速生成登录页面,这在为不同项目创建多个登录页面时非常有用。
- TypeScript 是 JavaScript 的超集,它在构建时引入了静态类型和类型检查。 TypeScript 已成为 JavaScript 最广泛使用的替代品之一,因为它具有强大的类型系统,可以在代码开始使用之前提醒开发人员注意代码中的问题。 对于登录页面,TypeScript 将有助于防止为动态值输入无效数据,例如推销文本或注册表单输入。
您将在本教程中构建的示例登录页面将推广书店,并将包括登录页面的以下常见组件:
- 书店的标题
- 与商店相关的英雄形象
- 带有功能/服务列表的销售宣传
- 可用于将读者添加到有关业务的邮件列表的电子邮件注册表单
在教程结束时,示例项目将如下图所示:
先决条件
- 您需要在您的机器上安装 Node 和 npm 来运行开发服务器并使用包。 本教程已使用 Node.js 版本 14.17.2 和 npm 6.14.13 进行了测试。 要在 macOS 或 Ubuntu 20.04 上安装,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 How 的 使用 PPA 安装 部分中的步骤进行操作在 Ubuntu 20.04 上安装 Node.js。
- 您将需要安装 Gatsby CLI 工具 并使用
gatsby-starter-default模板创建一个新的 Gatsby 站点。 按照 如何设置您的第一个 Gatsby 网站 中的 Step 1 开始一个新项目。 本教程将把这个项目称为bookstore-landing-page。 - 您需要设置 Gatsby 项目以使用 TypeScript。 按照 How To Set Up a Gatsby Project with TypeScript 中的 Step 1 到 Step 4 安装适当的依赖项,创建一个新的
tsconfig.json文件,并在 TypeScript 中重构seo.tsx文件。 - 你需要熟悉 React 组件 和 JSX,因为 Gatsby 是一个基于 React 的框架。 本教程还将包括在表单 中使用 事件处理程序。 您可以在我们的 如何在 React.js 系列中学习这些概念和更多内容。
第 1 步——重构 Header 和 Layout 组件
在此步骤中,您将首先重构您在先决条件教程中创建的 bookstore-landing-page 项目的现有 header.tsx 和 layout.tsx 组件。 这将包括用自定义类型接口替换默认类型定义和修改一些 GraphQL 查询。 完成此步骤后,您将使用页面的标题和描述填充登录页面的标题,并创建一个布局组件来实现未来的组件。
Header
Header 组件将在浏览器窗口顶部显示页面的标题和描述。 但是在重构这个组件之前,你需要打开项目根目录下的 gatsby-config.js 文件来更新站点的元数据。 稍后,您将从 Layout 组件中查询 gatsby-config.js 以检索此数据。
在您选择的文本编辑器中打开 gatsby-config.js。 在导出的模块中的siteMetaData下,将title和description的值改为书店名称和商业标语,如下高亮代码所示:
书店登陆页面/gatsby-config.js
module.exports = {
siteMetadata: {
title: `The Page Turner`,
description: `Explore the world through the written word!`,
author: `@gatsbyjs`,
},
plugins: [
...
进行这些更改后,保存并关闭 gatsby-config.js 文件。
接下来,在 bookstore-landing-page/src/components 目录中,打开 header.tsx 文件。 从这里,您将重构 <Header /> 组件以使用 TypeScript 键入而不是默认的 PropTypes。 对您的代码进行以下更改:
书店登陆页面/src/components/header.tsx
import * as React from "react"
import { Link } from "gatsby"
interface HeaderProps {
siteTitle: string,
description: string
}
const Header = ({ siteTitle, description }: HeaderProps) => (
...
)
export default Header
您在 Header 声明之后删除了 Header.PropTypes 和 Header.defaultProps 对象,并使用 siteTitle 将它们替换为自定义类型接口 HeaderProps和 description 属性。 然后,您将 description 添加到传递给功能组件的参数列表中,并将它们分配给 HeaderProps 类型。 新定义的 HeaderProps 接口将充当从 <Layout/> 组件中的 GraphQL 查询传递给 <Header/> 组件的参数的自定义类型。
接下来,在 <Header /> 组件的 JSX 中,更改开始 header 标记中的样式,使背景颜色为蓝色,文本居中对齐。 将 siteTitle 保留在嵌入的 <Link/> 组件中,但将 description 添加到单独的 <h3/> 标记中,并将其字体颜色设为白色:
书店登陆页面/src/components/header.tsx
...
const Header = ({ siteTitle, description }: HeaderProps) => (
<header
style={{
background: `#0069ff`,
textAlign: `center`,
}}
>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<h1 style={{ margin: 0 }}>
<Link
to="/"
style={{
color: `white`,
textDecoration: `none`,
}}
>
{siteTitle}
</Link>
</h1>
<h3 style={{
color: 'white'
}}>
{description}
</h3>
</div>
</header>
)
export default Header
现在,当数据传递给该组件时,您将拥有内联样式。
将更改保存在 header.tsx 文件中,然后运行 gatsby develop 并在浏览器上转到 localhost:8000。 该页面将如下所示:
请注意,描述尚未呈现。 在下一步中,您将把它添加到 layout.tsx 中的 GraphQL 查询中,以确保它显示出来。
准备好 <Header/> 组件后,您现在可以重构登录页面的默认 <Layout/> 组件。
Layout
<Layout /> 组件将包装您的登录页面,并有助于为您网站上的未来页面共享样式和格式。
要开始编辑此组件,请在文本编辑器中打开 layout.tsx。 删除文件末尾的默认类型定义,并在 import 语句之后定义一个名为 LayoutProps 的新接口。 然后,将接口类型分配给传递给 <Layout/> 的参数:
书店登陆页面/src/components/layout.tsx
/**
* Layout component that queries for data
* with Gatsby's useStaticQuery component
*
* See: https://www.gatsbyjs.com/docs/use-static-query/
*/
import * as React from "react"
import { useStaticQuery, graphql } from "gatsby"
import Header from "./header"
import "./layout.css"
interface LayoutProps {
children: ReactNode
}
const Layout = ({ children }: LayoutProps) => {
...
}
default export Layout
该接口使用您使用 React 库导入的 ReactNode 类型。 这种类型定义适用于大多数 React 子组件,这是 <Layout/> 默认呈现的。 这将使您能够为 <Layout/> 定义自定义类型的接口。
接下来,修改位于 <Layout/> 组件内的默认 GraphQL 查询。 在 siteMetaData 对象内,添加在 gatsby-config.js 中设置的 description。 然后,与 siteTitle 一样,将获取的值存储在新的 description 变量中:
书店登陆页面/src/components/layout.tsx
...
const Layout = ({ children }: LayoutProps) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
description
}
}
}
`)
const siteTitle = data.site.siteMetadata?.title || `Title`
const description = data.site.siteMetadata?.description || 'Description'
...
现在您可以将 description 作为道具传递给布局返回的 JSX 中的 <Header/> 组件。 这很重要,因为 description 被定义为 HeaderProps 接口中的必需属性:
书店登陆页面/src/components/layout.tsx
...
return (
<>
<Header siteTitle={siteTitle} description={description}/>
...
</>
)
export default Layout
保存并退出 layout.tsx 文件。
作为对布局的最后更改,进入 layouts.css 通过将页面的 body 中的所有文本居中来进行样式更改:
书店登陆页面/src/components/layout.css
...
/* Custom Styles */
body {
margin: 0;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: hsla(0, 0%, 0%, 0.8);
font-family: georgia, serif;
font-weight: normal;
word-wrap: break-word;
font-kerning: normal;
-moz-font-feature-settings: "kern", "liga", "clig", "calt";
-ms-font-feature-settings: "kern", "liga", "clig", "calt";
-webkit-font-feature-settings: "kern", "liga", "clig", "calt";
font-feature-settings: "kern", "liga", "clig", "calt";
text-align: center;
}
...
保存并关闭 layout.css 文件,然后启动开发服务器并在浏览器中呈现您的站点。 您现在将在标题中找到 description 值:
现在您已经重构了 Gatsby 站点的基本文件,您可以在页面中添加一个主图,使其在视觉上对客户更具吸引力。
第 2 步 — 添加英雄形象
主图是一种视觉效果,可以为登录页面中的产品或服务提供支持。 在此步骤中,您将下载书店登录页面的图像,并使用 gatsby-plugin-image 插件的 <StaticImage /> 组件将其呈现在网站上。
注意:这个项目使用的是 Gatsby 3.9.0 版本,所以你将无法使用已弃用的 gatsby-image 包。 此软件包已替换为 gatsby-plugin-image。 这个新插件,在 gatsby-plugin-sharp 的帮助下,将渲染具有处理功能的响应式图像。
首先,从 Unsplash 下载书架图片,该网站提供可以免费使用的图片:
curl https://images.unsplash.com/photo-1507842217343-583bb7270b66 -o src/images/bookshelf.png
此命令使用 curl 下载图像。 -o 标志指定输出,您已将其设置为 images 目录中名为 bookshelf.png 的文件。
现在打开 src/pages/index.tsx 文件。 Gatsby 默认启动模板已经有一个 <StaticImage/> 组件,所以替换属性以指向您新下载的图像:
书店登陆页面/src/pages/index.tsx
import * as React from "react"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import Seo from "../components/seo"
const IndexPage = () => (
<Layout>
<Seo title="Home" />
<StaticImage
src="../images/bookshelf.png"
alt="Bookshelf hero image"
/>
</Layout>
)
export default IndexPage
您添加了 src 属性以将 Gatsby 引导到 images 目录中的正确图像,然后添加 alt 属性以提供图像的替代文本。
保存并关闭文件,然后重新启动开发服务器。 您的页面现在将在其中心呈现下载的书架图像:
随着您的图像现在呈现在您的网站上,您可以继续向页面添加一些内容。
第 3 步 - 创建销售宣传和功能组件
对于登陆页面的下一部分,您将构建一个新组件来保存您的书店的销售宣传。 这将解释为什么您的客户应该来您的商店。
在 bookstore-landing-page/src/components 内部,继续创建一个名为 salesPitchAndFeatures.tsx 的新文件。 在新文件中,导入React,新建一个名为SalesPitchAndFeatures的函数组件,并导出:
书店登陆页面/src/components/salesPitchAndFeatures.tsx
import * as React from "react"
const SalesPitchAndFeatures = () => {
<>
</>
}
export default SalesPitchAndFeatures
该组件的接口将包含一个可选的 salesPitch 属性,类型为 string。 它还将具有 Array<string> 类型的 features 列表,这是必需的:
书店登陆页面/src/components/salesPitchAndFeatures.tsx
import * as React from "react"
interface SalesPitchAndFeaturesProps {
salesPitch?: string
features: Array<string>
}
...
salesPitch 和 features 的数据将在 salesPitchAndFeatures.tsx 中进行硬编码,但您也可以将其存储在其他位置(如 gatsby-config.js)并查询GraphQL 所需的数据。 content 对象的类型为 SalesPitchAndFeaturesProps:
书店登陆页面/src/components/salesPitchAndFeatures.tsx
...
interface salesPitchAndFeaturesProps {
salesPitch?: string
features: Array<string>
}
const content: SalesPitchAndFeaturesProps = {
salesPitch: "Come and expand your world at our bookstore! We are always getting new titles for you to see. Everything you need is here at an unbeatable price!",
features: [
"Tens of thousands of books to browse through",
"Engage with the community at a book club meeting",
"From the classics to the newest publications, there's something for everybody!"
]}
const SalesPitchAndFeatures = () => {
return (
<>
...
请注意,salesPitch 属性是一个字符串,而 features 属性是一个字符串数组,就像您在界面中设置它们一样。
您还需要一个显示功能列表的功能。 创建一个 showFeatures(f) 函数。
书店登陆页面/src/components/salesPitchAndFeatures.tsx
...
const showFeatures: any = (f: string[]) => {
return f.map(feature => <li>{feature}</li>)
}
const SalesPitchAndFeatures = () => {
return (
<>
...
传递给 showFeatures 的参数 f 是 Array<string> 类型,以与 string 类型的特征数组一致。 要返回转换为呈现 JSX 的列表,请使用 .map() 数组方法。
使用您的内容填充 return 语句,包裹在 divs 中,并为样式指定类名:
书店登陆页面/src/components/salesPitchAndFeatures.tsx
...
const SalesPitchAndFeatures = () => {
return (
<div className='features-container'>
<p className='features-info'>
{content.salesPitch}
</p>
<ul className='features-list'>
{showFeatures(content.features)}
</ul>
</div>
)
}
export default SalesPitchAndFeatures
保存并关闭 salesPitchAndFeatures.tsx。
接下来,打开 layout.css 为在 <SalesPitchAndFeatures/> 组件中添加的类名添加样式:
书店登陆页面/src/components/layout.css
...
.features-container {
border: 1px solid indigo;
border-radius: 0.25em;
padding: 2em;
margin: 1em auto;
}
.features-list {
text-align: left;
margin: auto;
}
这会在销售宣传和功能列表周围添加边框,然后在元素之间添加间距以提高可读性。
保存并关闭 layout.css。
最后,您将在登录页面上呈现此组件。 打开src/pages/目录下的index.tsx。 将 <SalesPitchAndFeatures/> 组件添加到渲染的布局子项中:
书店登陆页面/src/pages/index.tsx
import * as React from "react"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import SalesPitchAndFeatures from "../components/salesPitchAndFeatures"
import SEO from "../components/seo"
const IndexPage = () => (
<Layout>
<SEO title="Home" />
<div style={{ maxWidth: `450px`, margin: ' 1em auto'}}>
<StaticImage
src="../images/bookshelf.png"
alt="Bookshelf hero image"
/>
<SalesPitchAndFeatures/>
</div>
</Layout>
)
export default IndexPage
您还添加了 div 以将一些样式应用于图像和销售宣传。
保存并退出文件。 重新启动您的开发服务器,您会发现您的销售宣传和功能列表呈现在您的图像下方:
现在,您的目标网页上有一个销售宣传,这将有助于向潜在客户传达他们为什么应该去您的业务。 接下来,您将为登录页面构建最后一个组件:电子邮件注册表单。
第 4 步 - 创建注册表单组件
电子邮件注册按钮是一个常见的登录页面组件,可让用户输入他们的电子邮件地址并注册以获取有关产品或业务的更多新闻和信息。 将此添加到您的目标网页将为用户提供一个可行的步骤,他们可以采取这些步骤成为您的客户。
首先,在 bookstore-landing-page/src/components 中创建一个名为 signupForm.tsx 的新文件。 这个组件不会有任何自定义类型,但会有一个 事件处理程序,它有自己特殊的基于 React 的类型。
首先,构建 <SignUpForm/> 组件及其 return 语句,其中包含一个标题:
[label bookstore-landing-page/src/components/signupForm.tsx]
import * as React from "react"
const SignUpForm = () => {
return (
<h3>Sign up for our newsletter!</h3>
)
}
export default SignupForm
接下来,添加一些标记以创建具有 onSubmit 属性的 form 元素,现在初始化为 null。 很快这将包含事件处理程序,但现在,用 label、input 和 button 标签完成表单的编写:
书店登陆页面/src/components/signupForm.tsx
import * as React from "react"
const SignUpForm = () => {
return (
<React.Fragment>
<h3>Sign up for our newsletter!</h3>
<form onSubmit={null}>
<div style={{display: 'flex'}}>
<input type='email' placeholder='email@here'/>
<button type='submit'>Submit</button>
</div>
</form>
<React.Fragment>
)
}
export default SignupForm
如果您在文本字段中输入您的电子邮件地址并立即在呈现的登录页面上单击 注册 ,则不会发生任何事情。 这是因为您仍然需要编写一个在提交表单时触发的事件处理程序。 最佳实践是在事件处理程序的 return 语句之外编写一个单独的函数。 这个函数通常有一个特殊的 e 对象来表示触发的事件。
在编写单独的函数之前,您将在行内编写函数以弄清楚事件对象的静态类型是什么,以便您以后可以使用该类型:
书店登陆页面/src/components/signupForm.tsx
...
return (
<React.Fragment>
<h3>Sign up for our newsletter!</h3>
<form onSubmit={(e) => null}>
<div style={{display: 'flex'}}>
<input type='email' placeholder='email@here' style={{width: '100%'}}/>
<button type="submit">Submit</button>
</div>
</form>
</React.Fragment>
)
...
}
export default SignupForm
如果您使用 Visual Studio Code 之类的文本编辑器,将光标悬停在 e 参数上将使用 TypeScript 的 IntelliSense 向您显示其预期类型,在此案例为 React.FormEvent<HTMLFormElement>。
既然你知道你的独立函数的预期类型是什么,继续使用它来编写一个新的、名为 handleSubmit 的独立函数:
书店登陆页面/src/components/signupForm.tsx
import * as React from "react"
const SignupForm = () => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
alert(alert('The submit button was clicked! You\'re signed up!'))
}
return (
<React.Fragment>
<h3>Sign up for our newsletter!</h3>
<form onSubmit={handleSubmit}>
<div style={{display: 'flex'}}>
<input type='email' placeholder='email@here'/>
<button type='submit'>Submit</button>
</div>
</form>
</React.Fragment>
)
}
export default SignupForm
handleSubmit 函数现在将在提交表单时触发浏览器警报。 请注意,这是一个临时占位符; 要将用户实际添加到电子邮件列表,您必须将其连接到后端数据库,这超出了本教程的范围。
保存并关闭 signupForm.tsx 文件。
现在,打开 index.tsx 文件并添加新的 <SignupForm/> 组件:
书店登陆页面/src/pages/index.tsx
import * as React from "react"
import { StaticImage } from "gatsby-plugin-image"
import Layout from "../components/layout"
import SalesPitchAndFeatures from "../components/salesPitchAndFeatures"
import SignupForm from "../components/signupForm"
import Seo from "../components/seo"
const IndexPage = () => (
<Layout>
<Seo title="Home" />
<div style={{ maxWidth: `450px`, margin: ' 1em auto'}}>
<HeroImage />
<SalesPitchAndFeatures />
<SignupForm />
</div>
</Layout>
)
export default IndexPage
保存并退出文件。
重新启动您的开发服务器,您将在浏览器中找到已完成的登录页面,以及电子邮件注册按钮:
您现在已经完成了书店登录页面的所有核心组件的构建。
结论
因为 Gatsby 创建了快速的静态网站,而 TypeScript 允许对数据进行静态类型化,所以构建登录页面是一个很好的用例。 您可以塑造其常见元素的类型(标题、主图、电子邮件注册等),以便不正确的数据形式在投入生产之前触发错误。 Gatsby 提供了页面的大部分结构和样式,允许您在其之上进行构建。 您可以利用这些知识构建其他登录页面,以更快、更有效地推广其他产品和服务。
如果您想了解有关 TypeScript 的更多信息,请查看我们的 如何在 TypeScript 中编码系列,或尝试我们的 如何使用 Gatsby.js 创建静态网站系列 以了解更多信息关于盖茨比。