如何用Gatsby和TypeScript建立一个书店的登陆页面

171 阅读13分钟

作为Write for DOnations计划的一部分,作者选择了科技多样性基金来接受捐赠。

简介

着陆页是推广产品或服务的网页,为客户在到达网站时提供一个着陆点。对企业来说,它们通常是在线广告和营销电子邮件中的链接的目的地。商业登陆页面的主要目标是将访问者变成潜在的客户或顾客。正因为如此,建立着陆页是网站开发者的一项宝贵技能。

在本教程中,你将用以下两种技术建立一个登陆页面。

  • Gatsby,一个基于React的前端框架,旨在生成静态网站。Gatsby允许你快速生成着陆页,这在为不同项目创建许多着陆页时很有用。

  • TypeScript是一个JavaScript的超集,在构建时引入了静态类型和类型检查。TypeScript已经成为JavaScript最广泛使用的替代品之一,因为它有强大的类型系统,在代码进入骄傲的ction之前就会提醒开发人员代码中的问题。对于登陆页面,TypeScript将帮助防止动态值的无效键入数据,如销售宣传文本或注册表单输入。

你将在本教程中建立的登陆页面示例将推广一家书店,并包括登陆页面的以下常见组件。

  • 书店的页眉
  • 一张与书店有关的英雄图片
  • 一个带有功能/服务清单的销售推介
  • 一个电子邮件注册表,可用于将读者加入关于该业务的邮件列表。

在本教程结束时,该项目样本将如下图所示。

Resulting landing page from following this tutorial, displaying the header, hero image, and sales pitch

先决条件

第1步 - 重构HeaderLayout 组件

在这一步,你将开始重构你在先决条件教程中创建的bookstore-landing-page 项目的现有header.tsxlayout.tsx 组件。这将包括用自定义类型接口替换默认的类型定义,并修改一些GraphQL查询。一旦你完成了这一步,你将用页面的标题和描述填充你的登陆页面的标题,并创建一个布局组件来实现未来的组件。

Header

Header 组件将在浏览器窗口的顶部显示你的页面的标题和描述。但是在重构这个组件之前,你将打开项目根目录下的gatsby-config.js 文件,以更新网站的元数据。稍后,你将从Layout 组件中查询gatsby-config.js ,以检索这些数据。

在你选择的文本编辑器中打开gatsby-config.js 。在导出模块的siteMetaData 下,将titledescription 的值改为书店的名称和商业标语,如以下高亮代码所示。

bookstore-landing-page/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 。对你的代码做如下修改。

bookstore-landing-page/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.PropTypesHeader.defaultProps 对象,并用一个自定义类型的接口HeaderProps 取代它们,使用siteTitledescription 属性。然后,你将description 加入到传递给功能组件的参数列表中,并将它们分配给HeaderProps 类型。新定义的HeaderProps 接口将作为一个自定义类型,用于从<Layout/> 组件中的GraphQL查询传递给<Header/> 组件的参数。

接下来,在<Header /> 组件的JSX中,改变开头header 标签的样式,使背景颜色为蓝色,文本居中对齐。在嵌入式<Link/> 组件中保留siteTitle ,但将description 添加到一个单独的<h3/> 标签中,并给它一个白色的字体颜色。

bookstore-landing-page/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 。页面将看起来像下面这样。

Landing page with title rendered in header

注意描述还没有被渲染。在下一步,你将把它添加到layout.tsx 的GraphQL查询中,以确保它被显示出来。

随着<Header/> 组件的准备,你现在可以重构登陆页面的默认<Layout/> 组件。

Layout

<Layout /> 组件将包裹你的着陆页,并可以帮助分享你网站上未来页面的样式和格式。

要开始编辑这个组件,在你的文本编辑器中打开layout.tsx 。删除文件末尾的默认类型定义,并在import 语句后定义一个新的接口,名为LayoutProps 。然后,将接口类型分配给传递给<Layout/> 的参数。

bookstore-landing-page/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

该接口使用ReactNode 类型,你用React库导入该类型。这个类型定义适用于大多数React子组件,也就是<Layout/> 默认渲染的类型。这将使你能够为<Layout/> 定义一个自定义类型的接口。

接下来,修改位于<Layout/> 组件内的默认 GraphQL 查询。在siteMetaData 对象的内部,添加在gatsby-config.js 中设置的description 。然后,像siteTitle ,将获取的值存储在一个新的description 变量中。

bookstore-landing-page/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 接口中的一个必要属性。

bookstore-landing-page/src/components/layout.tsx


...

  return (
    <>
      <Header siteTitle={siteTitle} description={description}/>
      ...
    </>
  )

export default Layout

保存并退出layout.tsx 文件。

作为对你的布局的最后改动,进入layouts.css ,对页面的body ,将所有的文本居中,进行样式上的改动。

bookstore-landing-page/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 的值被呈现在页眉。

Landing page with rendered header but no content

现在你已经为你的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/> 的组件,所以替换属性以指向你新下载的图片。

bookstore-landing-page/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 属性来为图片提供替代文本。

保存并关闭该文件,然后重新启动开发服务器。你的页面现在将在中心位置呈现下载的书架图像。

Rendered landing page with bookshelf image.

现在你的图片已经在你的网站上呈现了,你可以继续在页面上添加一些内容。

第3步--创建一个销售推介和功能组件

对于登陆页面的下一部分,你将建立一个新的组件,为你的书店提供销售宣传。这将解释为什么你的客户应该到你的商店来。

bookstore-landing-page/src/components ,继续创建一个新的文件,名为salesPitchAndFeatures.tsx 。在这个新文件中,导入React ,创建一个新的功能组件,名为SalesPitchAndFeatures ,并将其导出。

bookstore-landing-page/src/components/salesPitchAndFeatures.tsx

import * as React from "react"

const SalesPitchAndFeatures = () => {
  <>
  </>
}

export default SalesPitchAndFeatures

这个组件的接口将包括一个可选的salesPitch 属性,类型为string 。它还将有一个类型为Array<string>features 列表,这是必须的。

bookstore-landing-page/src/components/salesPitchAndFeatures.tsx

import * as React from "react"

interface SalesPitchAndFeaturesProps {
  salesPitch?: string
  features: Array<string>
}
...

salesPitchfeatures 的数据将被硬编码在salesPitchAndFeatures.tsx 中,但你也可以把它存储在另一个地方(如gatsby-config.js ),并用 GraphQL 查询需要的数据。content 对象的类型将是SalesPitchAndFeaturesProps

bookstore-landing-page/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)函数。

bookstore-landing-page/src/components/salesPitchAndFeatures.tsx

...

const showFeatures: any = (f: string[]) => {
    return f.map(feature => <li>{feature}</li>)
}

const SalesPitchAndFeatures = () => {
    return (
        <>

          ...

传入showFeatures 的参数f 是类型Array<string> ,以便与类型string 的特征数组一致。为了返回转为渲染的JJSX的列表,你使用.map() 数组方法。

用你的内容填充到return 语句中,用指定的类名包装在divs 中,以便进行样式设计。

bookstore-landing-page/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/> 组件中添加的类名添加样式。

bookstore-landing-page/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/> 组件添加到渲染的布局子目录中。

bookstore-landing-page/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 ,对图片和推销的内容都应用了一些样式。

保存并退出该文件。重新启动你的开发服务器,你会发现你的销售广告和功能列表呈现在你的图片下面。

Rendered page with sales pitch and features added

你现在在你的登陆页面上有一个销售宣传,这将有助于向潜在客户传达他们为什么应该去你的企业。接下来,你将为着陆页建立最后一个组件:一个电子邮件注册表。

第4步 - 创建一个注册表单组件

电子邮件注册按钮是一个常见的着陆页组件,可以让用户输入他们的电子邮件地址,并注册获得更多关于产品或业务的新闻和信息。在你的着陆页上添加这个组件将给用户一个可操作的步骤,让他们可以成为你的客户。

首先,在bookstore-landing-page/src/components 中创建一个新的文件,名为signupForm.tsx 。这个组件不会有任何自定义类型,但会有一个事件处理程序,它有自己的基于React的特殊类型。

首先,建立<SignUpForm/> 组件和它的return 语句,里面有一个头。

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 标签完成表单的编写。

bookstore-landing-page/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

如果你现在在文本字段中输入你的电子邮件地址,并在渲染好的登陆页面上点击Sign Up,什么也不会发生。这是因为你仍然需要写一个事件处理程序,每当表单被提交时就会触发。最好的做法是在return 语句之外为事件处理程序编写一个单独的函数。这个函数通常有一个特殊的e 对象,代表被触发的事件。

在写这个单独的函数之前,你要在线写函数,弄清楚事件对象的静态类型是什么,以便以后使用这个类型。

bookstore-landing-page/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

bookstore-landing-page/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/> 组件。

bookstore-landing-page/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

保存并退出该文件。

重新启动你的开发服务器,你会发现你完成的登陆页面在浏览器中呈现,同时还有电子邮件注册按钮。

Email signup button rendered below the feature list on the landing page.

现在你已经完成了你的书店登陆页面的所有核心组件的建设。

结论

因为Gatsby可以创建快速的静态网站,而TypeScript允许数据被静态类型化,所以建立一个登陆页面是一个很好的用例。你可以塑造其常见元素的类型(标题、英雄图像、电子邮件注册等),这样,不正确的数据形式将在进入生产之前触发错误。Gatsby提供了大部分的结构和页面的风格,允许你在其基础上进行构建。你可以利用这些知识来建立其他登陆页面,更快、更有效地推广其他产品和服务。

如果你想了解更多关于TypeScript的信息,请查看我们的How To Code in TypeScript系列,或者尝试我们的How To Create Static Web Sites with Gatsby.js系列,了解更多关于Gatsby的信息。