第 3 章 使用 React Hook 编写第一个应用程序

第 3 章 使用 React Hook 编写第一个应用程序

Flying
2021-01-08 / 0 评论 / 161 阅读 / 正在检测是否收录...

在深入了解了 State Hook 之后,我们现在将通过从头开始创建博客应用程序来使用它。在本章中,我们将学习如何以良好的扩展方式构建 React 应用程序,如何使用多个 Hook,在哪里存储状态,以及如何使用 Hook 解决常见用例。在本章结束时,我们将拥有一个基本的博客应用程序,我们可以在其中登录、注册和创建文章。

本章将介绍以下主题:

  • 以可扩展的方式构建 React 项目
  • 从模型中实现静态 React 组件
  • 使用 Hook 实现有状态组件

技术要求

应该已经安装了相当新版本的 Node.js(vl1.l2.0 或更高版本)。还需要安装 Node.js 的 npm 包管理器。

本章的代码可以在 GitHub 存储库中找到:https://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter03

观看以下视频,了解代码的实际应用:

http://bit.ly/2Mm9yoC

请注意,强烈建议您自己编写代码。不要简单地运行以前提供的代码示例。重要的是你自己编写代码,以便你正确地学习和理解它。但是,如果遇到任何问题,始终可以参考代码示例。

现在,让我们从本章开始。

构建 React 项目

在了解了 React 的原理、如何使用 useState Hook 以及 Hook 如何在内部工作之后,我们现在将真正使用 State Hook 来开发博客应用程序。首先,我们将创建一个新项目,并以一种允许我们稍后扩展项目的方式构建文件夹。然后,我们将定义我们需要的组件,以涵盖博客应用程序的基本功能。最后,我们将使用 Hook 为我们的应用程序引入状态。在本章中,我们还将学习 JSX 以及 ES6 中引入的新 JavaScript 功能,直到 ES2018

文件夹结构

项目结构有很多种方式,并且不同结构可以很好地适用于不同的项目。通常,我们会创建一个 src/ 文件夹,并按功能将我们的文件分组放在那里。构建项目的另一种流行方法是按路由对项目进行分组。对于某些项目,按代码类型进行额外分离可能是有意义的,例如 src/api/src/components/。但我们的项目将主要关注用户界面(UI)。因此,我们将将在 src/ 文件夹中按功能对文件进行分组。

最好一开始从一个简单的结构开始,只有在实际需要时才使用更深的嵌套。在启动项目时,不要花太多时间考虑文件结构,因为通常情况下,您事先不知道应该如何对文件进行分组。

选择功能

我们首先必须考虑要在博客应用程序中实现哪些功能。至少,我们希望实现以下功能:

  • 注册用户
  • 登录/注销
  • 查看单篇文章
  • 创建新文章
  • 列出文章

现在我们已经选择了功能,让我们想出一个初始文件夹结构。

提出初始结构

从我们之前的功能中,我们可以抽象出几个功能组:

  • 用户(注册、登录/注销)
  • 发布(创建、查看、列出)

我们现在可以让它变得非常简单,在 src/ 文件夹中创建所有组件,而无需任何嵌套。但是,由于我们已经对博客应用程序所需的功能有了相当清晰的了解,因此我们现在可以提出一个简单的文件夹结构:

  • src/
  • src/user/
  • src/post/

定义文件夹结构后,我们来看看组件结构。

组件结构

React 中组件的思想是让每个组件处理单个任务或 UI 元素。我们应该尝试使组件尽可能细粒度,以便能够重用代码。如果我们发现自己将代码从一个组件复制并粘贴到另一个组件,那么创建一个新组件并在多个其他组件中重用它可能是一个好主意。

通常,在开发软件时,我们从 UI 模型开始。对于我们的博客应用程序,模型如下所示:

mock-up.png
我们博客应用程序的初始模型

在拆分组件时,我们使用单一责任原则,该原则指出每个模块都应该对功能的单个封装部分负责。

在这个模型中,我们可以在每个组件和子组件周围绘制框,并给它们命名。请记住,每个组件应该只有一个责任。我们从构成此应用程序的基本组件开始:

fundamental-components.png
从我们的模型定义基本组件

我们为注销功能定义了 Logout 组件、CreatePost 组件(包含创建新文章的表单)以及一个用于显示实际文章的 Post 组件。

现在我们已经定义了基本组件,我们将查看哪些组件在逻辑上属于一起的,从而形成一个组。为此,我们现在需要定义容器组件,以便将组件分组在一起:

container-components.png
从我们的模型中定义容器组件

我们定义了一个 PostList 组件,以便将文章分组在一起,然后是一个 UserBar 组件,以便处理登录/注销和注册。最后,我们定义了一个 App 组件,以便将所有内容组合在一起,并定义应用程序的结构。

现在我们已经完成了 React 项目的构建,我们可以继续实现静态组件。

实现静态组件

在我们开始通过 Hook 向我们的博客应用程序添加状态之前,我们将应用程序的基本功能建模为静态 React 组件。这样做意味着我们必须处理应用程序的静态视图结构。

首先处理静态结构是有意义的,以避免以后必须将动态代码移动到不同的组件。此外,首先处理超文本标记语言(HTML)和 CSS 更容易帮助我们快速开始项目。此后,我们可以继续实现动态代码和处理状态。

逐步执行此操作,而不是一次实现所有内容,可以帮助我们快速开始新项目,而无需一次考虑太多,并让我们避免以后必须重组项目!

设置项目

我们已经学会了如何建立一个新的 React 项目。正如我们所了解的,我们可以使用 create-react-app 工具来轻松初始化一个新项目。我们现在要这样做:

  1. 首先,我们使用 create-react-app 来初始化我们的项目:
npx create-react-app chapter3_1
  1. 然后,我们为功能创建文件夹:
  • 创建文件夹src/user/
  • 创建文件夹src/post/

现在我们的项目结构已经建立起来,我们可以开始实现组件了。

实现用户

我们将从静态组件方面最简单的功能开始:实现与用户相关的功能。正如我们从模型中看到的,我们将在这里需要四个组件:

  • Login 组件,我们将在用户尚未登录时显示该组件
  • Register 组件,当用户尚未登录时,我们也将显示该组件
  • Logout 组件,将在用户登录后显示
  • UserBar 组件,它将有条件地显示其他组件

我们将从定义前三个组件开始,它们都是独立的组件。最后,我们将定义 UserBar 组件,因为它取决于要定义的其他组件。

Login 组件

首先,我们定义 Login 组件,其中显示两个字段:用户名字段和密码字段。此外,我们还显示一个登录按钮:

  1. 我们首先为我们的组件创建一个新文件:src/user/Login.js
  2. 在新创建的 src/user/Login.js 文件中,我们导入 React
import React from 'react'
  1. 然后,我们定义函数组件。目前,Login 组件将不接受任何 props:
export default function Login () {
  1. 最后,我们通过 JSX 返回两个字段和登录按钮。我们还定义了一个 form 容器元素来包装它们。为了避免在提交表单时刷新页面,我们必须定义一个 onSubmit 处理程序并在事件对象上调用 e.preventDefault()
  return (
    <form onSubmit={e => e.preventDefault()}>
      <label htmlFor="login-username">Username:</label>
      <input type="text" name="login-username" id="login-username"
        />
      <label htmlFor="login-password">Password:</label>
      <input type="password" name="login-password" id="login-password" />
      <input type="submit" value="Login" />
    </form>
  )
}

在这里,我们使用匿名函数来定义 onSubmit 处理程序。匿名函数定义如下,如果它们没有任何参数:() => { ... },而不是function (){ ... }。使用参数,我们可以写 (arg1, arg2) => { ... },而不是 function (arg1, arg2) { ... }。如果我们只有一个参数,我们可以省略 () 括号。此外,如果我们的函数中只有一个语句,我们可以省略 {} 括号,如下所示:e => e.preventDefault()

使用语义 HTML 元素(如 <form><label>)让使用辅助功能辅助功能软件(如屏幕阅读器)的用户更易于导航应用程序。此外,使用语义 HTML 时,键盘快捷键(例如通过按回车键提交表单)会自动工作。

我们已实现登录管理器组件,现在可以进行测试了。

测试我们的组件

现在我们已经定义了第一个组件,让我们渲染它并查看它的外观:

  1. 首先,我们编辑 src/App.js ,并删除其所有内容。
  2. 然后,我们首先导入 ReactLogin 组件:
import React from 'react'
import Login from './user/Login'
最好将导入分组到属于一起的代码块中。在这种情况下,我们将外部导入(如 React)与本地导入(如我们的 Login 组件)分开,方法是在两者之间添加一个空行。这样做可以使我们的代码具有可读性,尤其是当我们稍后添加更多导入语句时。
  1. 最后,我们定义 App 组件,并返回 Login 组件:
export default function App () { 
  return <Login />
}
如果我们只返回单个组件,我们可以省略 return 语句中的括号。与其写 return (<Login />),我们可以简单地写 return <Login />
  1. 打开 http://localhost:3000,您应该会看到正在渲染的 Login 组件。如果您已经在浏览器中打开了该页面,则当您更改代码时,它应该会自动刷新:

logging.jpg
我们博客应用程序的第一个组件:通过用户名和密码登录

正如我们所看到的,静态 Login 组件在 React 中渲染良好。我们现在来看看 Logout 组件。

Logout 组件

接下来,我们定义Logout 组件,该组件将显示当前登录的用户,以及一个用于注销的按钮:

  1. 创建一个新文件:src/user/Logout.js
  2. 导入 React,如下所示:
import React from 'react'
  1. 这一次,我们的函数将使用一个 user prop来显示当前登录的用户:
export default function Logout ({ user }) {
在这里,我们使用解构来从 props 对象中提取用户密钥。React 将单个对象中的所有组件 props 作为第一个参数传递给函数。对第一个参数使用解构类似于在类组件中使用解构。const { user } = this.props
  1. 最后,我们返回一个显示当前登录 user 的文本和注销按钮:
return (
    <form onSubmit={e => e.preventDefault()}> Logged in as: <b>{user}</b>
      <input type="submit" value="Logout" />
    </form>
  )
}
  1. 我们现在可以在 src/App.js 中将 Login 组件替换为 Logout 组件,以便查看我们新定义的组件(不要忘记将 user prop 传递给它):
import React from 'react'
import Logout  from './user/Logout' 


export default function App() {
  return <Logout user = "Daniel Bugl"  />
}

现在,我们定义好了 Logout 组件,再来看看 Register 组件。

Register 组件

静态 Register 组件将与 Login 组件非常相似,但有一个附加字段来重复密码。如果它们非常相似,您可能会想到将它们合并到一个组件中,并添加一个 prop 来切换重复密码字段。但是,最好坚持单一责任原则,并让每个组件只处理一个功能。稍后,我们将使用动态代码扩展静态组件,然后注册和登录将具有截然不同的代码。因此,我们稍后需要再次拆分它们。

尽管如此,让我们开始处理 Register 组件的代码:

  1. 我们首先创建一个新的 src/user/Register.js 文件,并从 Login 组件复制代码,因为静态组件非常相似。确保将组件的名称更改为 Register
import React from 'react'

export default function Register() {
  return (
    <form onSubmit={e => e.preventDefault()}>
      <label htmlFor="register-username">Username:</label>
      <input type="text" name="register-username" id="register-username"
        />
      <label htmlFor="register-password">Password:</label>
      <input 
        type="password"
        name="register-password"
        id="register-password"
      />
  1. 接下来,我们在密码字段代码正下方添加重复密码字段:
<label htmlFor="register-password-repeat">Repeat password:</label>
  <input
    type="password"
    name="register-password-repeat"
    id="register-password-repeat"
  />
  1. 最后,我们还将提交按钮的值更改为注册:
  <input type="submit" value="Register" />
    </form>
  )
}
  1. 同样,我们可以编辑 src/App.js 以显示我们的组件,其方式类似于我们对 Login 组件所做的那样:
import React from 'react'
import Register from './user/Register'
export default function App() {
  return <Register />
}

如我们所见,我们的 Register 组件看起来与 Login 组件非常相似。

UserBar 组件

现在是时候将我们的用户相关组件组合成一个 UserBar 组件了。在这里,我们将有条件地显示登录和 Register 组件或Logout 组件,具体取决于用户是否已登录。

让我们开始实现 UserBar 组件:

  1. 首先,我们新建 src/user/UserBar.js 文件,并导入 React 以及我们定义的三个组件:
import React from 'react'
import Login from './Login'
import Logout from './Logout'
import Register from './Register'
  1. 接下来,我们定义函数组件和 user 值。现在,我们只将其保存在静态变量中:
export default function UserBar () {
  const user = ''
  1. 然后,我们检查用户是否已登录。如果用户已登录,则显示Logout 组件,并将 user 值传递给它:
if (user) {
  return <Logout user={user} />
  1. 否则,我们显示 LoginRegister 组件。在这里,我们可以使用 React.Fragment 代替<div>容器元素。这使我们的 UI 树保持干净,因为组件将简单地并排渲染,而不是包装在另一个元素中:
} else {
  return (
    <React.Fragment>
      <Login />
      <Register />
    </React.Fragment>
    )
  }
}
  1. 同样,我们编辑 src/App.js ,现在我们显示我们的 UserBar 组件:
import React from 'react'
import UserBar from './user/UserBar'

export default function App() {
  return <UserBar />
}
  1. 正如我们所看到的,它工作了!现在同时显示 LoginRegister 组件:

userbar-componet.jpg
我们的用户栏组件,显示登录和注册组件

  1. 接下来,我们可以编辑 src/user/UserBar.js 文件,并将 user 值设置为一个字符串:
const user = 'Daniel Bugl'
  1. 完成此操作后,我们的应用程序现在显示 Logout 组件:

logout.jpg
我们的应用程序在定义用户值后显示注销组件

在本章的后面,我们将向应用程序添加 Hook,以便我们可以登录并动态更改状态,而无需编辑代码!

示例代码

用户相关组件的示例代码可以在 Chapter03/chapter3_1 文件夹中找到。

只需运行 npm install 即可安装所有依赖项,运行 npm start 启动应用程序,然后在您的浏览器中访问 http://localhost:3000(如果它没有自动打开)。

实现文章

在实现所有用户相关组件后,我们继续在我们的博客应用程序中实现文章。我们将定义以下组件:

  • 用于显示单篇文章的 Post 组件
  • 用于创建新文章的 CreatePost 组件
  • 用于显示多篇文章的 PostList 组件

现在让我们开始实现与文章相关的组件。

Post 组件

我们已经考虑过在创建模型时文章具有哪些元素。文章应具有标题、内容和作者(撰写文章的用户)。

现在让我们实现 Post 组件:

  1. 首先,我们创建一个新文件: src/post/Post.js
  2. 然后,我们导入 React,并定义函数组件接受三个 props:titlecontentauthor
import React from 'react'

export default function Post ({ title, content, author }) {
  1. 接下来,我们以类似于模型的方式渲染所有 props:
  return (
    <div>
      <h3>{title}</h3>
      <div>{content}</div>
      <i>Written by <b>{author}</b></i>
    </div>
  )
}
  1. 与往常一样,我们可以通过编辑 src/App.js 文件来测试我们的组件:
import React from 'react'
import Post from './post/Post'

export default function App() {
  return <Post title="React Hook"
     content="The greatest thing since sliced bread!"
     author="Daniel Bugl" />
}

现在,静态 Post 组件已经实现,我们可以继续创建 CreatePost 组件。

CreatePost 组件

接下来,我们实现一个表单以允许创建新文章。在这里,我们将 user 值作为 props 传递给组件,因为作者应始终是当前登录的用户。然后,我们显示作者,并为标题提供一个 input 字段,为博客文章的内容提供一个 <textarea> 元素。

现在让我们来实现 CreatePost 组件:

  1. 创建一个新文件:src/post/CreatePost.js
  2. 定义以下组件:
import React from 'react'

export default function CreatePost({ user }) {
  return (
    <form onSubmit={e => e.preventDefault()}>
      <div>Author: <b>{user}</b></div>
      <div>
        <label htmlFor="create-title">Title:</label>
        <input type="text" name="create-title" id="create-title"
          />
      </div>
      <textarea />
      <input type="submit" value="Create" />
    </form>
  )
}
  1. 与往常一样,我们可以通过编辑 src/App.js 文件来测试我们的组件:
import React from 'react'
import CreatePost from './post/CreatePost'
export default function App() {
  return <CreatePost />
}

正如我们所看到的,CreatePost 组件渲染良好。现在我们来看看 PostList 组件。

PostList 组件

在我们实现其他与文章相关的组件后,现在可以实现博客应用程序中最重要的部分:博客文章的提要。目前,提要只是显示博客文章列表。

现在让我们开始实现 PostList 组件:

  1. 我们首先导入 ReactPost 组件:
import React from 'react'
import Post from './Post'
  1. 然后,我们定义我们的 PostList 函数组件,接受一个 posts 数组作为 prop。如果未定义 posts,则默认情况下将其设置为空数组:
export default function PostList ({ posts = [] }) {
  1. 接下来,我们使用 map 函数和扩展运算符来显示所有文章:
  return (
    <div>
      {posts.map((p, i) => <Post {...p} key={'post-' + i} />)}
    </div>
  )
}
如果我们渲染一个元素列表,我们必须给每个元素一个唯一的 key prop。当数据发生变化时,React 使用这个 key prop 高效地计算两个列表的差值。

在这里,我们使用 map 函数,它将函数应用于数组的所有元素。这类似于使用 for 循环并存储所有结果,但它更但它更简洁,声明式,更易于阅读。或者我们可以执行以下操作,而不是使用 map 函数:

let renderedPosts = []
let i = 0
for (let p of posts) {
  renderedPosts.push(<Post {...p} key={'post-' + i} />)
  i++
}
return (
  <div>
    {renderedPosts}
  </div>
)

然后,我们为每篇文章返回 <Post> 组件,并将 Post 对象 p 中的所有键作为 props 传递给该组件。我们使用扩展语法来做到这一点,这与手动将对象中的所有键列为 props 的效果相同,如下所示:<Post title={p.title} content={p.content} author={p.author} />

  1. 在模型中,每篇博客文章之后都有一条水平线。我们可以在没有额外 <div> 容器元素的情况下通过使用 React.Fragment 实现这一点:
{posts.map((p, i) => (
  <React.Fragment key={'post-' + i} >
    <Post {...p} />
    <hr />
  </React.Fragment>
))}
key prop 始终必须添加到 map 函数中渲染的的最上面的父元素。在这种情况下,我们必须将 key prop 从 Post 组件移到 React.Fragment 组件。
  1. 再次,我们通过编辑 src/App.js 文件来测试我们的组件:
import React from 'react'
import PostList from './post/PostList'

const posts = [
  { title: 'React Hook', content: 'The greatest thing since sliced bread!', author: 'Daniel Bugl' },
  { title: 'Using React Fragments', content: 'Keeping the DOM tree clean!', author: 'Daniel Bugl' }
]
export default function App() {
  return <PostList posts={posts} />
}

现在,我们可以看到应用程序列出了在 posts 数组中定义的所有文章:

post-list.jpg
使用文章列表组件显示多篇文章

如我们所见,通过 PostList 组件列出多篇文章可以正常工作。我们现在来看看怎样将应用程序组合在一起。

组合应用程序

实现所有组件后,为了重现模型,我们现在只需要将所有内容放在 App 组件中即可。然后,我们将成功复制模型!

让我们开始修改 App 组件,并将我们的应用程序组合在一起:

  1. 编辑 src/App.js ,并删除所有当前代码。
  2. 首先,我们导入 ReactPostListCreatePostUserBar 组件:
import React from 'react'
import PostList from './post/PostList'
import CreatePost from './post/CreatePost'
import UserBar from './user/UserBar'
  1. 然后,我们为应用程序定义一些模拟数据:
const user = 'Daniel Bugl'
const posts = [
  { title: 'React Hook', content: 'The greatest thing since sliced bread!', author: 'Daniel Bugl' },
  { title: 'Using React Fragments', content: 'Keeping the DOM tree clean!', author: 'Daniel Bugl' }
]
  1. 接下来,我们定义 App 组件,并返回一个 <div> 容器元素,我们给它设置了一些填充:
export default function App() {
  return (
    <div style={{ padding: 8 }}>
  1. 现在,我们插入 UserBarCreatePost 组件,将 user prop 传递给 CreatePost 组件:
<UserBar />
<br />
<CreatePost user={user} />
<br />
<hr />
请注意,您应该始终更喜欢通过 CSS 设置间距,而不是使用 <br/> HTML 标记。但目前我们专注于 UI,而不是其样式,因此我们尽可能简单使用 HTML。
  1. 最后,我们显示 PostList 组件,列出所有 posts
  <PostList posts={posts} />
    </div>
  )
}
  1. 保存文件后,http://localhost:3000 应该会自动刷新,我们现在可以看到完整的 UI:

full-ui.png
根据模型完全实现我们的静态博客应用程序

如我们所见,我们之前定义的所有静态组件都在一个 App 组件中一起渲染。我们的应用程序现在看起来就像模型一样。接下来,我们可以继续使所有组件动态化。

示例代码

我们的博客应用程序的静态实现的示例代码可以在 Chapter03/chapter3_2 文件夹中找到。

只需运行 npm install 即可安装所有依赖项,运行 npm start 启动应用程序,然后在您的浏览器中访问 http://localhost:3000(如果它没有自动打开)


(节选)

1

评论 (0)

取消