第 1 章 介绍 React 和 React Hook

第 1 章 介绍 React 和 React Hook

Flying
2020-12-02 / 0 评论 / 172 阅读 / 正在检测是否收录...

React 是一个 JavaScript 库,可用于构建高效且可扩展的 Web 应用程序。React 由 Facebook 开发,用于许多大型 Web 应用程序,如 Facebook,Instagram,Netflix 和 WhatsApp Web。

在本书中,我们将学习如何使用 React 构建复杂高效的用户界面,同时保持代码的简单性和可扩展性。使用 React Hook 的新范式,我们可以大大简化 Web 应用程序中的状态管理和副作用的处理,确保以后增长和扩展应用程序的潜力。我们还将学习 React contextReact Suspense ,以及如何将它们与 Hook 一起使用。之后,我们将学习如何将 ReduxMobX 与 React Hook 集成。最后,我们将学习如何从现有的 React 类组件、ReduxMobX Web 应用程序迁移到 React Hook。

在本书的第一章中,我们将学习 React 和 React Hook 的基本原理。我们首先学习 React 和 React Hook 是什么,以及为什么我们应该使用它们。然后,我们继续学习 Hook 的功能。最后,我们介绍了 React 提供的 Hook 种类,以及我们将在本书中学习的几个 Hook。通过学习 React 和 React Hook 的基础知识,我们将能够更好地理解本书中将介绍的概念。

本章将介绍以下主题:

  • 了解 Reac 的基本原理
  • 使用 React Hook 的动机
  • 开始使用 React Hook
  • 概述各种 Hook

技术要求

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

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

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

http://bit.ly/2Mm9yoC

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

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

React 原理

在我们开始学习 React Hook 之前,我们将了解 React 的三个基本原则。这些原则使我们能够轻松编写可扩展的 Web 应用程序。了解基本原则很重要,因为它们将帮助我们理解 Hook 如何以及为什么适合 React 生态系统。

React 基于三个基本原则:

  • 声明式:我们不是告诉 React 如何做事,而是告诉它我们希望它做什么。因此,我们可以轻松设计我们的应用程序,当数据发生变化时,React 将有效地更新和渲染正确的组件。例如,以下代码(在数组中复制字符串)是必需的,这与声明性相反:
const input = ['a', 'b', 'c']
let result = []
for (let i = 0; i < input.length; i++) {
  result.push(input[i] + input[i])
}
console.log(result) // 打印: [ 'aa', 'bb', 'cc' ]

正如我们所看到的,在命令式代码中,我们需要一步一步地告诉计算机该做什么。但是,使用声明式代码,我们可以简单地告诉计算机我们想要什么,如下所示:

const input = ['a', 'b', 'c']
let result = input.map(str => str + str)
console.log(result) // 打印: [ 'aa', 'bb', 'cc' ]

在前面的声明性代码中,我们告诉计算机我们要将 input 数组的每个元素从 str 映射到 str + str。如我们所见,声明式代码更加简洁。

  • 基于组件 :React 封装了管理自己的状态和视图的组件,然后允许我们编写它们以创建复杂的用户界面。
  • 一次学习,随处编写:React 不会对您的技术堆栈做出假设,并试图确保您可以在不重写现有代码的情况下开发应用程序。

我们刚刚提到 React 是基于组件的。在 React 中,有两种类型的组件:

  • 函数组件: JavaScript 数,将 props 作为参数,并返回用户界面(通常通过 JSX
  • 类组件:提供渲染方法的 JavaScript 类,该方法返回用户界面(通常通过 JSX

虽然函数组件更容易定义和理解,但需要类组件来处理状态、上下文和更多 React 的高级特性。但是,使用 React Hook,我们可以处理 React 的高级功能,而无需使用类组件!

使用 React Hook 的动机

React 的三个基本原则使编写代码、封装组件和跨多个平台共享代码变得容易。React 没有重新发明轮子,而是总是试图尽可能多地利用现有的 JavaScript 功能。因此,我们将学习软件设计模式,这些模式将适用于更多情况,而不仅仅是设计用户界面。

React 始终致力于使开发人员体验尽可能流畅,同时确保其保持足够的性能,而开发人员不必过多担心如何优化性能。然而,在使用 React 的这些年里,已经发现了一些问题。

让我们在以下各节中详细看一下这些问题。

令人困惑的类

过去,我们必须使用具有特殊函数的类组件,称为生命周期方法,如 componentDidUpdate,以及特殊的状态处理方法,如 this.setState,以处理状态变化。React 类,尤其是 this 上下文,它是一个 JavaScript 对象,无论对人还是机器来说都很难阅读和理解。

this 是 JavaScript 中的一个特殊关键字,它始终引用它所属的对象。

  • 在方法中,this 指类对象(类的实例)。
  • 在事件处理函数中,this 指接收事件的元素。
  • 在函数中或独立使用时,this 指全局对象。例如,在浏览器中,全局对象是 window 对象。
  • 在严格模式下,this 函数中是 undefined 的。
  • 此外,call()apply()等方法可以更改它引用的对象,因此它可以引用任何对象。

对于人来说,类是难理解的,因为 this 总是引用不同的东西,所以有时(例如,在事件处理函数中)我们需要手动将其重新绑定到类对象。对于机器来说,类也是难理解的,因为机器不知道将调用类中的哪些方法,以及如何修改 this ,使得优化性能和删除未使用的代码变得困难。

此外,类有时要求我们同时在多个地方编写代码。例如,如果我们想在组件渲染或数据更新时获取数据,我们需要使用两种方法执行此操作:一次在 componentDidMount 中,另一次在 componentDidUpdate 中。

举个例子,让我们定义一个从应用程序编程接口(API)获取数据的类组件:

  1. 首先,我们通过扩展 React.Component 类来定义我们的类组件:
class Example extends React.Component {
  1. 然后,我们定义 componentDidMount 生命周期方法,在该方法中我们从 API 中拉取数据:
componentDidMount () {
  fetch(`http://my.api/${this.props.name}`)
    .then(...)
}
  1. 但是,我们还需要定义 componentDidUpdate 生命周期方法,以防 name prop 发生变化。此外,我们需要在此处添加手动检查,以确保我们仅在 name prop 更改时才重新获取数据,而不是在其他 prop 更改时重新获取数据:
componentDidUpdate (prevProps) {
  if (this.props.name !== prevProps.name) {
    fetch(`http://my.api/${this.props.name}`)
      .then(...)
  }
}
  1. 为了使我们的代码不那么重复,我们可以定义一个名为 fetchData 的单独方法,以便获取我们的数据,如下所示:
fetchData () {
  fetch(`http://my.api/${this.props.name}`)
    .then(...)
}
  1. 接下来,我们可以在 componentDidMountcomponentDidUpdate 中调用该方法:
componentDidMount () {
  this.fetchData()
}
componentDidUpdate (prevProps) {
  if (this.props.name !== prevProps.name) {
    this.fetchData()
  }
}

但是,即便如此,我们仍然需要在两个地方调用 fetchData。每当我们更新传递给方法的参数时,我们都需要在两个地方更新它们,这使得这种模式很容易出现错误和将来出错。

包装器地狱

在 Hook 之前,如果我们想封装状态管理逻辑,就必须使用高阶组件和 render prop。例如,我们创建一个 React 组件,该组件使用上下文来处理用户身份验证,如下所示:

  1. 我们首先导入 authenticateUser 函数,以便将我们的组件包装在上下文中,然后导入 AuthenticationContext 组件以访问上下文:
import authenticateUser, { AuthenticationContext } from './auth'
  1. 然后,我们定义我们的 App 组件,在其中我们使用 AuthenticationContext.Consumer 组件和 user render prop:
const App = () => (
  <AuthenticationContext.Consumer>
    {
      user =>
  1. 现在,我们根据用户是否登录显示不同的文本:
user ? `${user} logged in` : 'not logged in'

在这里,我们使用了两个 JavaScript 概念:

  • 三元运算符,它是 if 条件的内联版本。它看起来像这样:ifThisisTrue ? returnThis : otherwiseReturnThis
  • 模板字符串,可用于将变量插入到字符串。它是用反引号(\`)定义的,而不是普通的单引号(')。变量可以通过 ${variableName} 语法插入。也可以在 ${} 括号中使用任何 JavaScript 表达式,例如 ${someValue + 1}
  1. 最后,在使用 authenticateUser 上下文包装组件后导出组件:
    }
  </AuthenticationContext.Consumer>
)
export default authenticateUser(App)

在前面的示例中,我们使用高阶组件 authenticateUser 将身份验证逻辑添加到现有组件。然后,我们使用 AuthenticationContext.Consumer 通过其 render prop 将 user 对象注入到我们的组件中。

可以想象,使用许多上下文将导致一棵具有许多子树的大树,也称为 包装器地狱 。例如,当我们想要使用三个上下文时,包装器地狱如下所示:

<AuthenticationContext.Consumer>
  {user => (
  <LanguageContext.Consumer>
    {language => (
    <StatusContext.Consumer>
      {status => (
      ...
    )}
    </StatusContext.Consumer>
  )}
  </LanguageContext.Consumer>
)}
</AuthenticationContext.Consumer>

这代码读写起来不是很容易,如果我们以后需要更改某些东西,它也容易出错。此外,包装器地狱使调试变得困难,因为我们需要查看一个大型组件树,其中许多组件只是充当包装器。

Hook 来拯救

React Hook 基于与 React 相同的基本原则。他们尝试通过使用现有的 JavaScript 功能来封装状态管理。因此,我们不再需要学习和理解专门的 React 功能;我们可以简单地利用我们现有的 JavaScript 知识来使用 Hook。

使用 Hook,我们可以解决前面提到的所有问题。我们不再需要使用类组件,因为 Hook 只是可以在函数组件中调用的函数。我们也不再需要使用高阶组件和 render prop 来获取上下文,因为我们只需使用 Context Hook 来获取我们需要的数据。此外,Hook 允许我们重用在组件之间的有状态逻辑,而无需创建高阶组件。

例如,之前提到的使用生命周期方法的问题可以使用 Hook 解决,如下所示:

function Example({ name }) {
  useEffect(() => {
    fetch(`http://my.api/${this.props.name}`)
      .then(...)
  }, [name])
  // ...
}

此处实现的 Effect Hook 将在组件挂载时以及 name prop 更改时自动触发。

此外,之前提到的包装器地狱也可以使用 Hook 解决,如下所示:

const user = useContext(AuthenticationContext)
const language = useContext(LanguageContext)
const status = useContext(StatusContext)

现在我们知道 Hook 可以解决哪些问题,让我们开始在实践中使用 Hook吧!

开始使用 React Hook

正如我们所看到的,React Hook 解决了许多问题,尤其是大型 Web 应用程序中的。React 16.8 中添加了 Hook,它们允许我们使用状态和各种其他 React 功能,而无需编写类。在本节中,我们将首先使用 create-react-app 初始化一个项目,然后我们将定义一个类组件,最后我们将使用 Hook 编写相同的组件作为函数组件。在本节结束时,我们将讨论 Hook 的优势,以及如何迁移到基于 Hook 的解决方案。

使用 create-react-app 程序初始化项目

要初始化 React 项目,我们可以使用 create-react-app 工具,该工具为 React 开发设置环境,包括以下内容:

  • Babel,这样我们就可以使用 JSXES6 语法
  • 它甚至包括 ES6 以外的语言附加功能,例如对象扩展运算符,我们将在后面使用。
  • 此外,我们甚至可以使用 TypeScriptFlow语法

此外,create-react-app 设置以下内容:

  • 自动前缀级联样式(CSS),因此我们不需要特定于浏览器的前缀,例如 -webkit
  • 一 具有代码覆盖率报告的快速交互式单元测试运行程序
  • 一个实时开发服务器,它警告我们常见的错误
  • 一个构建脚本,它捆绑了 JavaScript、CSS 和用于生产环境的图片,包括 hash 和 sourcemap
  • 一个离线优先服务辅助角色和一个 Web 应用程序清单,以满足 渐进式 Web 应用程序(PWA)的所有条件
  • 之前列出的所有工具的无忧更新

正如我们所看到的,create-react-app 工具让我们的 React 开发变得更加容易。它是我们用来了解 React 以及在生产中部署 React 应用程序的完美工具。

创建新项目

为了设置一个新项目,我们运行以下命令,该命令将创建一个名为 <app-name> 的新目录:

npx create-react-app <app-name>
如果你更喜欢使用 yarn 包管理器,你可以运行 yarn create react-app <appname>

我们现在将使用 create-react-app 创建一个新项目。运行以下命令,为第一章的第一个示例创建一个新的 React 项目:

npx create-react-app chapter1_1

现在我们已经初始化了我们的项目,让我们继续启动项目。

启动一个项目

为了在开发模式下启动项目,我们必须运行 npm star 命令。运行以下命令:

npm start

现在,我们可以通过在浏览器中打开 http://localhost:3000 来访问我们的项目 :

first-react-app.jpg
我们的第一个 React 应用程序!

正如我们所看到的,使用 create-react-app,设置一个新的 React 项目非常容易!

部署项目

要为生产部署构建项目,我们只需运行构建脚本:

  1. 运行以下命令以生成用于生产部署的项目:
npm run-script build
使用 yarn,我们可以简单地运行 yarn build。实际上,我们可以通过这种方式运行任何与内部 yarn 命令名称不冲突的包脚本: yarn <script name> ,而不是 npm run-script <script-name>
  1. 然后,我们可以使用 Web 服务器或使用 serve 工具为静态构建文件夹提供服务。首先,我们必须安装它:
npm install -g serve
  1. 然后,我们可以运行 serve 命令,如下所示:
serve -s build
serve 命令的 -s 标志重写所有未找到 index.html 的请求 ,从而允许客户端路由。

现在,我们可以通过在的浏览器中打开 http://localhost:5000 来访问相同的应用程序。请注意,serve 工具不会自动在您的浏览器中打开页面。

在了解了 create-react-app 之后,我们现在将使用 React 编写我们的第一个组件。

从类组件开始

首先,我们从一个传统的 React 类组件开始,它允许我们输入一个名称,然后将其显示在我们的应用程序中。

设置项目

如前所述,我们将使用 create-react-app 来初始化我们的项目。如果尚未执行此操作,请立即运行以下命令:

npx create-react-app chapter1_1

接下来,我们将应用程序定义为一个类组件。

定义类组件

我们首先将应用程序编写为传统的类组件,如下所示:

  1. 首先,我们从 src/App.js 文件中删除所有代码。
  2. 接下来,在 src/App.js 中,我们导入 React
import React from 'react'
  1. 然后我们开始定义我们自己的类组件——MyName
class MyName extends React.Component {
  1. 接下来,我们必须定义一个 constructor 方法,在其中设置初始状态对象,该对象将是一个空字符串。在这里,我们还需要确保调用 super(props),以便让 React.Component 构造函数知道 props 对象:
constructor (props) { 
  super(props)
  this.state = { name: '' }
}
  1. 现在,我们使用 this.setState 定义一个方法来设置 name 变量。由于我们将使用此方法来处理来自文本字段的输入,因此我们需要使用 evt.target.value 从输入字段中获取值:
  2. 然后,我们定义 render 方法,我们将在其中显示一个输入字段和名称:
render () {
  1. 为了从 this.state 对象中获取 name 变量,我们将使用解构:
const { name } = this.state

上面的语句等效于执行以下操作:

const name = this.state.name
  1. 然后,我们显示当前输入的 name 状态变量:
return (
  <div>
    <h1>My name is: {name}</h1>
  1. 我们显示一个输入字段,将处理方法传递给它:
<input type="text" value={name} onChange=
  {this.handleChange} />
      </div>
    )
  }
}
  1. 最后,我们导出我们的类组件:
export default MyName

如果我们现在运行此代码,我们将在输入文本时收到以下错误,因为将处理方法传递给 onChange 会更改 this 上下文:

未捕获的类型错误:无法读取未定义的属性 'setState'
  1. 因此,现在我们需要调整构造函数方法并将处理方法的 this 上下文重新绑定到类中:
constructor (props) { 
  super(props)
  this.state = { name: '' }
  this.handleChange = this.handleChange.bind(this)
}
有可能使用箭头函数作为类方法,以避免重新绑定此上下文。但是,要使用此功能,我们需要安装 Babel 编译器插件 @babel plugin-proposal-class-properties,因为它还不是一个发布的 JavaScript 功能。

最后,我们的组件工作了!如您所见,需要大量代码才能使状态处理与类组件正常工作。我们还必须重新绑定 this 上下文,否则我们的处理方法将不起作用。这不是很直观,并且在开发时很容易错过,从而导致讨厌的的开发人员体验。

示例代码

示例代码可以在 Chapter01/chapter1_1 文件夹中找到。

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

改用 Hook

在使用传统的类组件编写我们的应用程序后,我们将使用 Hook 编写相同的应用程序。和以前一样,我们的应用程序将让我们输入一个名称,然后我们在应用程序中显示该名称。

请注意,只能在 React 函数组件中使用 Hook。你不能在 React 类组件中使用 Hook!

我们现在从设置项目开始。

设置项目

同样,我们使用 create-react-app 来设置项目:

npx create-react-app chapter1_2

现在让我们开始使用 Hook 定义函数组件。

定义函数组件

现在,我们将相同的组件定义为函数组件:

  1. 首先,我们从 src/App.js 文件中删除所有代码。
  2. 接下来,在 src/App.js 中,我们导入 ReactuseState Hook:
import React, { useState } from 'react'
  1. 我们从函数定义开始。在我们的例子中,不传递任何参数,因为我们的组件没有任何 props:
function MyName () {

下一步是从组件状态中获取 name 变量。但是,不能在函数组件中使用 this.state。我们已经知道 Hook 只是 JavaScript 函数,但这到底意味着什么呢?这意味着我们可以简单地使用函数组件中的 Hook,就像使用任何其他 JavaScript 函数一样!

要通过 Hook 使用状态,我们调用 useState(),并将初始状态作为参数。此函数返回一个包含两个元素的数组:

  • 当前状态
  • 用于设置状态的 setter 函数
  1. 我们可以使用解构将这两个元素存储在单独的变量中,如下所示:
const [ name, setName ] = useState('')

上面的代码等价于下面的代码:

const nameHook = useState('')
const name = nameHook[0] 
const setName = nameHook[1]
  1. 现在,我们定义输入处理函数,在其中我们使用 setName setter 函数:
function handleChange (evt) { 
  setName(evt.target.value)
}
由于我们现在不处理类,因此无需再重新绑定 this
  1. 最后,我们来渲染从函数返回的用户界面。然后,我们导出函数组件:
  return (
    <div>
      <h1>My name is: {name}</h1>
      <input type="text" value={name} onChange={handleChange} />
    </div>
  )
}
export default MyName

就是这样——我们第一次成功使用 Hook!如您所见,useState Hook 是 this.statethis.setState 的直接替代品。

让我们通过执行 npm start 来运行我们的应用程序,并在浏览器中打开 http://localhost:3000

first-app-with-hook.jpg
我们第一个使用 Hook 的 React 应用程序!

使用类组件和函数组件实现相同的应用程序后,让我们比较一下解决方案。

示例代码

示例代码可以在 Chapter01/chapter1_2 文件夹中找到。

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

比较解决方案

让我们比较一下我们的两个解决方案,以了解类组件和使用 Hook 的函数组件之间的差异。

类组件

类组件使用 constructor 方法来定义状态,并且需要重新绑定 this 才能将处理方法传递给 input 字段。完整的类组件代码如下所示:

import React from 'react'
class MyName extends React.Component {
  constructor(props) {
    super(props)
    this.state = { name: '' }
    this.handleChange = this.handleChange.bind(this)
  }

  handleChange(evt) {
    this.setState({ name: evt.target.value })
  }

  render() {
    const { name } = this.state
    return (
      <div>
        <h1>My name is: {name}</h1>
        <input type="text" value={name} onChange={this.handleChange} />
      </div>
    )
  }
}

export default MyName

如我们所见,类组件需要大量样板代码来初始化 state 对象和处理函数。

现在,让我们看一下函数组件。

带 Hook 的函数组件

函数组件使用 useState Hook 代替,因此我们不需要处理 thisconstructor 方法。完整的函数组件代码如下所示:

mport React, { useState } from 'react'
function MyName() {
  const [name, setName] = useState('')
  function handleChange(evt) {
    setName(evt.target.value)
  }

  return (
    <div>
      <h1>My name is: {name}</h1>
      <input type="text" value={name} onChange={handleChange} />
    </div>
  )
}

export default MyName

正如我们所看到的,Hook 使我们的代码更加简洁,更容易推理。我们不再需要担心内部如何运作;通过访问 useState 函数,我们可以简单地使用 state!

Hook 的优点

让我们回顾一下 React 的第一个原则:

声明式:我们不是告诉 React 如何做事,而是告诉它我们希望它做什么。因此,我们可以轻松设计我们的应用程序,当数据发生变化时,React 将有效地更新和渲染正确的组件。

正如我们在本章中学到的,Hook 允许我们编写代码来告诉 React 我们想要什么。然而,对于类组件,我们需要告诉 React 如何做事。因此,Hook 比类组件更具声明性,使它们更适合 React 生态系统。

Hook 是声明性的也意味着 React 可以对我们的代码进行各种优化,因为分析函数和函数调用比分析类及其复杂的 this 行为更容易。此外,Hook 使得在组件之间抽象和共享公共有状态逻辑变得更加容易。通过使用 Hook,我们可以避免使用 render prop 和高阶组件。

我们可以看到,Hook 不仅使我们的代码更加简洁,对开发人员来说更容易理解,而且还使代码更容易针对 React 进行优化。

迁移到 Hook

现在,您可能想知道:这是否意味着类组件已被弃用,我们现在需要将所有东西迁移到 Hook?当然不是——Hook 完全是可选的。您可以在某些组件中尝试 Hook,而无需重写任何其他代码。React 团队目前也不打算删除类组件。

现在不急于将所有内容迁移到 Hook。建议您在某些最有用的组件中逐步采用 Hook。例如,如果有许多组件处理类似的逻辑,则可以将该逻辑提取到 Hook 中。您还可以将函数组件与 Hook 与类组件并排使用。

此外,Hook 是 100% 向后兼容的,并为你已经知道的所有 React 概念提供了一个直接的 API:propsstatecontextrefs生命周期。 此外,Hook 提供了新的方法来组合这些概念,并以更好的方式封装它们的逻辑,不会导致包装器地狱或类似问题。我们将在本书后面对此进行更多介绍。

Hook 思维模式

Hook 的主要目标是将有状态逻辑从渲染逻辑中解耦。它们允许我们在单独的函数中定义逻辑,并在多个组件中重用它们。使用 Hook,我们不需要更改组件层次结构即可实现有状态逻辑。不再需要定义一个单独的组件来为多个组件提供状态逻辑,我们可以简单地使用 Hook!

然而,Hook 需要与经典 React 开发完全不同的思维方式。我们不应该再考虑组件的生命周期。相反,我们应该考虑数据流。例如,我们可以告诉 Hook 在其他 Hook 的某些 props 或值发生变化时触发。我们将在第 4 章使用 Reducer 和 Effect Hook 中进一步了解这个概念。我们也不应该再根据生命周期来拆分组件。相反,我们可以使用 Hook 来处理常见的功能,例如获取数据或设置订阅。

Hook 的规则

Hook 非常灵活。但是,使用 Hook 存在某些限制,我们应该始终牢记这些限制:

  • Hook 只能在函数组件中使用,不能在类组件中使用
  • Hook 定义的顺序很重要,需要保持不变;因此,我们不能将 Hook 放入条件、循环或嵌套函数

我们将在本书中更详细地讨论这些限制,以及如何解决它们。

各种 Hook 概述

正如我们在上一节中学到的,Hook 为所有 React 概念提供了一个直接的 API。此外,我们可以定义自己的 Hook 来封装逻辑,而不必编写高阶组件,这会导致包装器地狱。在本节中,我们将概述各种 Hook,这些内容将贯穿全书。

React 提供的 Hook

React 已经为不同的功能提供了各种 Hook。有三个基本的 Hook,还有一些额外的 Hook。

基本 Hook

Basic Hook 提供了有状态 React 应用程序中最常用的功能。它们如下:

  • useState
  • useEffect
  • useContext

让我们在接下来的章节中逐一看看。

useState

我们已经使用了这个 Hook。它返回一个有状态值(state)和一个 setter 函数(setState)以更新值。

useState Hook 用于处理 React 中的状态。我们可以按如下方式使用它:

import { useState } from 'react'

const [ state, setState ] = useState(initialState)

useState Hook 代替了 this.state 和 this.setState()。

useEffect

这个 Hook 的工作方式类似于在 componentDidMountcomponentDidUpdate 上添加一个函数。此外,Effect Hook 允许从中返回清理函数,其工作原理类似于向 compoentWillUnmount 添加一个函数。

useEffect Hook 用于处理有效的代码,例如计时器、订阅、请求等。我们可以按如下方式使用它:

import { useEffect } from 'react'

useEffect(didUpdate)

useEffect Hook 取代了 componentDidMountcomponentDidUpdatecomponentWillUnmount 方法。

useContext

此 Hook 接受 context 对象并返回当前 context 值。

useContext Hook 用于处理 React 中的上下文。我们可以按如下方式使用它:

import { useContext } from 'react'

const value = useContext(MyContext)

useContext Hook 取代了 context consumer。

额外的 Hook

额外的 Hook 要么是基本 Hook 的更通用的变体,要么是某些边缘情况所必需的。我们将要查看的其他 Hook 如下:

  • useRef
  • useReducer
  • useMemo
  • useCallback
  • useLayoutEffect
  • useDebugValue

让我们在以下各节中更深入地了解这些额外的 Hook。

useRef

此 Hook 返回一个可变的 ref 对象,其中 .current 属性初始化为传递的参数 (initialValue)。我们可以按如下方式使用它:

import { useRef } from 'react'

const refContainer = useRef(initialValue)

useRef Hook 用于处理对 React 中元素和组件的引用。我们可以通过将 ref prop 传递给元素或组件来设置引用,如下所示:

<ComponentName ref={refContainer} />
useReducer

这个 Hook 是 useState 的替代品,其工作原理类似于 Redux 库。我们可以按如下方式使用它:

import { useReducer } from 'react'

const [ state, dispatch ] = useReducer(reducer, initialArg, init)

useReducer Hook 用于处理复杂的状态逻辑。

useMemo

记忆是一种优化技术,其中函数调用的结果被缓存,然后在再次出现相同的输入时返回。useMemo Hook 允许我们计算一个值并记住它。我们可以按如下方式使用它:

import { useMemo } from 'react'

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

当我们想要避免重新执行昂贵的操作时,useMemo Hook 对于优化很有用。

useCallback

这个 Hook 允许我们传递一个内联回调 ,以及一个依赖项数组,并将返回回调函数的记忆版本。我们可以按如下方式使用它:

import { useCallback } from 'react'

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b)
  },
  [a, b]
)

useCallback Hook 在将回调传递给优化的子组件时很有用。它的工作方式类似于 useMemo Hook,但用于回调函数。

useLayoutEffect

此 Hook 与 useEffect 相同,但它仅在所有文档对象模型(DOM)突变后触发。我们可以按如下方式使用它:

import { useLayoutEffect } from 'react'

useLayoutEffect(didUpdate)

useLayoutEffect Hook 可用于从 DOM 读取信息。

尽可能使用 useEffect Hook,因为 useLayoutEffect 会阻止视觉更新并减慢应用程序的速度。

最后,我们将看看在撰写本文时 React 提供的最后一个 Hook。

useDebugValue

此 Hook 可用于在创建自定义 Hook 时在 React DevTools 中显示标签。我们可以按如下方式使用它:

import { useDebugValue } from 'react'

useDebugValue(value)

确保在自定义 Hook 中使用此 Hook 来显示 Hook 的当前状态,因为它可以更轻松地调试它们。

社区 Hook

除了 React 提供的所有 Hook 之外,社区已经发布了大量的库。这些库还提供 Hook。我们将要研究的 Hook 如下:

  • useInput
  • useResource
  • useDimensions
  • Navigation Hook
  • Life cycle Hook
  • Timer Hook

让我们在以下各节中概述这些 Hook 是什么。

useInput

此 Hook 用于轻松实现输入处理,并将 input 字段的状态与变量同步。它可以按如下方式使用:

import { useInput } from 'react-hookedup' 

function App () {
  const { value, onChange } = useInput('')
  return <input value={value} onChange={onChange} />
}

正如我们所看到的,Hook 大大简化了 React 中输入字段的处理。

useResource

此 Hook 可以在我们的应用程序中通过请求实现异步数据加载。我们可以按如下方式使用它:

import { useRequest } from 'react-request-hook'
const [profile, getProfile] = useResource(id => ({
  url: `/user/ ${ id }`, 
  method: 'GET'
})

如我们所见,使用特殊的 Hook 来处理获取数据非常简单。

Navigation Hook

这些 Hook 是 Navi 库的一部分,用于通过 React 中的 Hook 实现路由。Navi 库提供了更多与路由相关的 Hook。我们将在本书的后面深入学习通过 Hook 进行路由。我们可以按如下方式使用它们:

import { useCurrentRoute, useNavigation } from 'react-navi'

const { views, url, data, status } = useCurrentRoute()
const { navigate } = useNavigation()

正如我们所看到的,Hook 使路由更容易处理。

Life cycle Hook

react-hookedup 库提供了各种 Hook,包括 React 的所有生命周期监听器。

请注意,在使用 Hook 进行开发时,不建议考虑组件生命周期。这些 Hook 只是提供了一种将现有组件重构为 Hook 的快速方法。但是,在开发新组件时,建议您考虑数据流和依赖关系,而不是生命周期。

在这里,我们列出了其中的两个,但该库实际上提供了更多的 Hook,我们将在后面学习。我们可以使用 react-hookedup 提供的 Hook,如下所示:

import { useOnMount, useOnUnmount } from 'react-hookedup'
useOnMount(() => { ... })
useOnUnmount(() => { ... })

如我们所见,Hook 可以直接替换类组件中的生命周期方法。

Timer Hook

react-hookedup 库还提供了用于 setIntervalsetTimeout 的 Hook。这些工作类似于直接调用 setTimeoutsetInterval,但作为 React Hook,它将在重新渲染之间持续存在。如果我们在没有 Hook 的情况下直接在函数组件中定义计时器,那么每次组件重新渲染时,我们都会重置计时器。

我们可以以毫秒为单位传递时间作为第二个参数。我们可以按如下方式使用它们:

import { useInterval, useTimeout } from 'react-hookedup'

useInterval(() => { ... }, 1000)
useTimeout(() => { ... }, 1000)

正如我们所看到的,Hook 大大简化了我们在 React 中处理间隔和超时的方式。

其他社区 Hook

可以想象,社区提供了更多的 Hook。我们将在第 8 章使用社区 Hook 中深入学习前面提到的社区 Hook,以及其他各种社区 Hook。

总结

在本书的第一章中,我们首先学习了 React 的基本原理以及它提供了哪些类型的组件。然后,我们继续学习类组件的常见问题,使用 React 的现有特性,以及它们如何破坏基本原则。接下来,我们使用类组件和函数组件与 Hook 实现一个简单的应用程序,以便能够比较两种解决方案之间的差异。正如我们所发现的,带有 Hook 的函数组件更适合 React 的基本原理,因为它们没有与类组件相同的问题,并且使我们的代码更加出色。

简明易懂!最后,我们第一次瞥见了我们将在本书中学习的各种 Hook。在本章之后,React 和 React Hook 的基础知识就很清楚了。现在,我们可以继续讨论 Hook 的更高级概念。

在下一章中,我们将通过从头开始重新实现它来深入了解 State Hook 的工作原理。通过这样做,我们将掌握 Hook 在内部的工作方式,以及它们的局限性。之后,我们将使用 State Hook 创建一个小型博客应用程序!

问题

为了回顾我们在本章中学到的内容,请尝试回答以下问题:

  1. React 的三个基本原则是什么?
  2. React 中的两种组件是什么?
  3. React 中的类组件存在哪些问题?
  4. 在 React 中使用高阶组件有什么问题?
  5. 我们可以使用哪种工具来设置 React 项目,我们需要运行什么命令才能使用它?
  6. 如果我们在使用类组件时遇到以下错误,我们需要做什么:TypeError:undefined 不是一个对象(评估 'this.setState')?
  7. 我们如何使用 Hook 访问和设置 React 状态?
  8. 与类组件相比,将函数组件与 Hook 一起使用有什么优势?
  9. 在更新 React 时,我们是否需要使用 Hook 将所有类组件替换为函数组件?
  10. React 提供的三个基本 Hook 是什么?

延伸阅读

如果您对我们在本章中学到的概念的更多信息感兴趣,请查看以下阅读材料:

1

评论 (0)

取消