一、React 和 React 挂钩简介

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

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

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

本章将介绍以下主题:

  • 学习 React 的基本原理
  • 激发对 React 挂钩的需求
  • 开始使用 React 挂钩
  • 概述各种挂钩

技术要求

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

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

请查看以下视频以查看代码的运行情况:

http://bit.ly/2Mm9yoC

Please note that it is highly recommended that you write the code on your own. Do not simply run the code examples that were previously provided. It is important to write the code yourself in order to learn and understand it properly. However, if you run into any issues, you can always refer to the code example.

现在,让我们从这一章开始。

React 原理

在开始学习 React 挂钩之前,我们将学习 React 的三个基本原则。这些原则使我们能够轻松编写可伸缩的 web 应用。了解基本原则很重要,因为它们将帮助我们理解挂钩如何以及为什么适合 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) // prints: [ 'aa', 'bb', 'cc' ]

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

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

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

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

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

  • 函数组件:将道具作为参数并返回用户界面的 JavaScript 函数(通常通过 JSX)
  • 类组件:提供render方法的 JavaScript 类,返回用户界面(通常通过 JSX)

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

使用 React 挂钩的动机

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

React 始终致力于使开发人员的体验尽可能流畅,同时确保它保持足够的性能,而开发人员不必过于担心如何优化性能。然而,在使用 React 的几年中,发现了几个问题

让我们在下面的章节中详细地了解这些问题。

混淆类

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

this是 JavaScript 中的一个特殊关键字,它总是指它所属的对象:

  • 在方法中,this指的是类对象(类的实例)。
  • 在事件处理程序中,this指接收事件的元素。
  • 在函数中或独立时,this指全局对象。例如,在浏览器中,全局对象是Window对象。
  • 在严格模式下,this在函数中为undefined
  • 另外,像call()apply()这样的方法可以改变this所指的对象,所以它可以指任何对象。

对于人类来说,类是很难的,因为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. 但是,我们还需要定义name道具发生变化时的componentDidUpdate生命周期方法。此外,我们需要在此处添加一个手动检查,以确保我们仅在name道具更改时重新获取数据,而不是在其他道具更改时重新获取数据:
    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。每当我们更新传递给该方法的参数时,我们都需要在两个地方更新它们,这使得该模式非常容易出现错误和将来的 bug。

包装机地狱

在挂钩之前,如果我们想封装状态管理逻辑,我们必须使用高阶组件和渲染道具。例如,我们创建了一个 React 组件,该组件使用上下文处理用户身份验证,如下所示:

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

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

  1. 最后,在使用authenticateUser上下文包装组件后,我们将其导出:
        }
    </AuthenticationContext.Consumer>
)

export default authenticateUser(App)

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

您可以想象,使用许多上下文将生成一个包含许多子树的大型树,也称为包装器地狱。例如,当我们想要使用三个上下文时,包装器地狱如下所示:

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

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

救命啊!

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

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

例如,可以使用挂钩解决生命周期方法的上述问题,如下所示:

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

在这里实现的效果挂钩将在组件安装时以及name道具更改时自动触发。

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

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

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

开始使用 React 挂钩

正如我们所看到的,React 挂钩解决了许多问题,尤其是大型 web 应用的问题。挂钩是在 React 16.8 中添加的,它们允许我们在不编写类的情况下使用状态和其他各种 React 特性。在本节中,我们将首先用create-react-app初始化一个项目,然后我们将定义一个类组件,最后我们将使用挂钩编写与函数组件相同的组件。在本节结束时,我们将讨论挂钩的优点,以及如何迁移到基于挂钩的解决方案。

使用 create react app 初始化项目

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

  • Babel,这样我们就可以使用 JSX 和 ES6 语法了
  • 它甚至包括 ES6 之外的额外语言,例如对象扩展操作符,我们稍后将使用它
  • 此外,我们甚至可以使用类型脚本和流语法

此外,create-react-app还设置了以下各项:

  • Autoprefixed层叠样式表CSS),因此我们不需要特定于浏览器的前缀,例如-webkit
  • 具有代码覆盖率报告功能的快速交互式单元测试运行程序
  • 一个实时开发服务器,它警告我们常见的错误
  • 一个构建脚本,它捆绑了 JavaScript、CSS 和用于生产的图像,包括哈希和源映射
  • 离线第一服务人员和 web 应用清单,以满足渐进式 web 应用PWA的所有标准)
  • 以前列出的所有工具的无障碍更新

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

创建新项目

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

> npx create-react-app <app-name>

If you prefer using the yarn package manager, you can run yarn create react-app <app-name> instead.

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

> npx create-react-app chapter1_1

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

启动项目

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

> npm start

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

Our first React app!

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

部署项目

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

  1. 运行以下命令为生产部署生成项目:
> npm run-script build

Using yarn, we can simply run yarn build. Actually, we can run any package script that does not conflict with the name of an internal yarn command in this way: yarn <script-name>, instead of npm run-script <script-name>.

  1. 然后,我们可以通过 web 服务器或使用serve工具为静态构建文件夹提供服务。首先,我们必须安装它:
> npm install -g serve
  1. 然后,我们可以运行serve命令,如下所示:
> serve -s build

The -s flag of the serve command rewrites all not-found requests to index.html, allowing for client-side routing.

现在,我们可以通过在浏览器中打开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方法,在这里我们设置初始state对象,它将是一个空字符串。在这里,我们还需要确保调用super(props),以便让React.Component构造函数知道props对象:
    constructor (props) {
        super(props)
        this.state = { name: '' }
    }
  1. 现在,我们定义了一个方法来设置name变量,使用this.setState。由于我们将使用此方法处理文本字段的输入,我们需要使用evt.target.value从输入字段获取值:
   handleChange (evt) {
       this.setState({ name: evt.target.value })
   }
  1. 然后,我们定义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字段,将处理程序方法传递给它:
                <input type="text" value={name} onChange={this.handleChange} />
            </div>
        )
    }
}
  1. 最后,我们导出类组件:
export default MyName

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

Uncaught TypeError: Cannot read property 'setState' of undefined

  1. 因此,现在我们需要调整constructor方法,并将处理程序方法的this上下文重新绑定到类:
    constructor (props) {
        super(props)
        this.state = { name: '' }
        this.handleChange = this.handleChange.bind(this)
    }

There is the possibility of using arrow functions as class methods, to avoid having to re-bind the this context. However, to use this feature we need to install the Babel compiler plugin, @babel/plugin-proposal-class-properties, as it is not a released JavaScript feature yet.

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

示例代码

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

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

用挂钩代替

在使用传统类组件编写应用之后,我们将使用挂钩编写相同的应用。和以前一样,我们的应用将允许我们输入一个名称,然后显示在应用中。

Please note that it is only possible to use Hooks in React function components. You cannot use Hooks in a React class component!

我们现在开始设置项目。

建立项目

同样,我们使用create-react-app来建立我们的项目:

> npx create-react-app chapter1_2

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

定义功能组件

现在,我们将同一组件定义为功能组件:

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

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

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

  1. 我们可以使用解构将这两个元素存储在单独的变量中,如下所示:
            const [ name, setName ] = useState('')

前面的代码相当于以下代码:

            const nameHook = useState('')
            const name = nameHook[0]
            const setName = nameHook[1]
  1. 现在,我们定义输入处理函数,其中我们使用setNamesetter 函数:
            function handleChange (evt) {
                setName(evt.target.value)
            }

As we are not dealing with classes now, there is no need to rebind this anymore!

  1. 最后,我们通过从函数返回来呈现用户界面。然后,我们导出函数组件:
    return (
        <div>
            <h1>My name is: {name}</h1>
            <input type="text" value={name} onChange={handleChange} />
        </div>
    )
}

export default MyName

就这样,我们第一次成功地使用了挂钩!正如您所看到的,useState挂钩是this.statethis.setState的替代品。

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

Our first React app with Hooks

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

示例代码

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

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

比较解决方案

让我们比较两种解决方案,以了解类组件和使用挂钩的函数组件之间的差异。

类组件

类组件使用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对象和处理程序函数。

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

带挂钩的功能组件

函数组件使用了useState挂钩,因此我们不需要处理thisconstructor方法。完整功能组件代码如下所示:

import 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

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

吊钩的优点

让我们提醒自己关于 React 的第一个原则:

Declarative: Instead of telling React how to do things, we tell it what we want it to do. As a result, we can easily design our applications, and React will efficiently update and render just the right components when the data changes.

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

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

我们可以看到,挂钩不仅使我们的代码更简洁,更容易为开发人员进行推理,而且还使代码更容易进行优化。

迁移到挂钩

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

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

此外,挂钩是 100%向后兼容的,并为您已经知道的所有 React 概念提供了直接 API:道具状态上下文参考生命周期。此外,挂钩提供了新的方法来组合这些概念,并以更好的方式封装它们的逻辑,而不会导致包装器地狱或类似的问题。我们将在本书后面部分了解更多关于这方面的信息。

挂钩心态

hook 的主要目标是将有状态逻辑与呈现逻辑解耦。它们允许我们在单独的函数中定义逻辑,并跨多个组件重用它们。使用挂钩,我们不需要为了实现有状态逻辑而更改组件层次结构。不再需要定义一个单独的组件来为多个组件提供状态逻辑,我们只需使用一个挂钩就可以了!

然而,挂钩需要一种与经典 React 开发完全不同的思维方式。我们不应该再考虑组件的生命周期。相反,我们应该考虑数据流。例如,我们可以告诉挂钩在某些道具或其他挂钩的值发生变化时触发。我们将在第 4 章中使用简化器和效果挂钩了解更多关于此概念的信息。我们也不应该再根据生命周期划分组件。相反,我们可以使用挂钩处理常见功能,例如获取数据或设置订阅。

挂钩规则

挂钩非常灵活。但是,使用挂钩有一定的局限性,我们应该始终牢记这一点:

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

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

各种挂钩概述

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

React 提供的挂钩

React 已经为不同的功能提供了各种挂钩。有三个基本的挂钩,还有一些附加的挂钩。

基本挂钩

基本挂钩提供了有状态应用中最常用的功能。详情如下:

  • useState
  • useEffect
  • useContext

下面我们来看看下面的每一个部分。

使用状态

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

useState挂钩用于处理 React 中的state。我们可以按如下方式使用它:

import { useState } from 'react'

const [ state, setState ] = useState(initialState)

useState挂钩取代了this.statethis.setState()

使用效果

这个挂钩的工作原理类似于在componentDidMountcomponentDidUpdate上添加一个函数。此外,Effect 挂钩允许从它返回一个 cleanup 函数,其工作原理类似于向componentWillUnmount添加一个函数。

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

import { useEffect } from 'react'

useEffect(didUpdate)

useEffect挂钩替代了componentDidMountcomponentDidUpdatecomponentWillUnmount方法。

使用上下文

这个挂钩接受一个上下文对象并返回当前的上下文值。

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

import { useContext } from 'react'

const value = useContext(MyContext)

useContext挂钩替换上下文消费者。

附加挂钩

附加挂钩是基本挂钩的更通用的变体,或者是某些边缘情况所需要的。我们将要研究的其他挂钩如下所示:

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

让我们在下面几节中更深入地了解这些附加的挂钩。

useRef

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

import { useRef } from 'react'

const refContainer = useRef(initialValue)

useRef挂钩用于处理 React 中对元素和组件的引用。我们可以通过将ref属性传递给元素或组件来设置引用,如下所示:<ComponentName ref={refContainer} />

用户教育者

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

import { useReducer } from 'react'

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

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

使用备忘录

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

import { useMemo } from 'react'

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

当我们想要避免重复执行昂贵的操作时,useMemo挂钩对于优化非常有用。

使用回调

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

import { useCallback } from 'react'

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

useCallback挂钩在将回调传递给优化的子组件时非常有用。它的工作原理类似于useMemo挂钩,但用于回调函数。

使用布局效果

此挂钩与useEffect相同,但它仅在文档对象模型DOM突变后激发。我们可以按如下方式使用它:

import { useLayoutEffect } from 'react'

useLayoutEffect(didUpdate)

useLayoutEffect挂钩可用于从 DOM 读取信息。

Use the useEffect Hook when possible, because useLayoutEffect will block visual updates and slow down your application.

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

使用调试值

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

import { useDebugValue } from 'react'

useDebugValue(value)

确保在自定义挂钩中使用此挂钩来显示挂钩的当前状态,因为这样可以更容易地调试挂钩。

社区挂钩

除了 React 提供的所有挂钩之外,社区已经发布了很多库。这些库还提供了挂钩。我们将研究的挂钩如下:

  • useInput
  • useResource
  • useDimensions
  • 导航钩
  • 生命周期挂钩
  • 计时器挂钩

让我们在下面的部分中看到这些挂钩的概述。

使用输入

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

import { useInput } from 'react-hookedup'

function App () {
    const { value, onChange } = useInput('')

    return <input value={value} onChange={onChange} />
}

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

用户资源

这个挂钩可用于通过应用中的请求实现异步数据加载。我们可以按如下方式使用它:

import { useRequest } from 'react-request-hook'

const [profile, getProfile] = useResource(id => ({
    url: `/user/${id}`,
    method: 'GET'
})

正如我们所看到的,使用一个特殊的挂钩来处理获取数据是非常简单的。

导航钩

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

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

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

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

生命周期挂钩

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

Please note that it is not recommended to think in terms of a component life cycle when developing with Hooks. These Hooks just provide a quick way to refactor existing components to Hooks. However, when developing new components, it is recommended that you think about data flow and dependencies, rather than life cycles.

在这里,我们列出了其中的两个,但是这个库实际上提供了更多的挂钩,我们稍后将了解这些挂钩。我们可以使用react-hookedup提供的挂钩如下:

import { useOnMount, useOnUnmount } from 'react-hookedup'

useOnMount(() => { ... })
useOnUnmount(() => { ... })

正如我们所看到的,挂钩可以直接替换类组件中的生命周期方法。

计时器挂钩

react-hookedup库还为setIntervalsetTimeout提供挂钩。它们的工作方式类似于直接调用setTimeoutsetInterval,但作为一个 React 挂钩,它将在重新渲染之间持续存在。如果我们在没有挂钩的函数组件中直接定义计时器,那么每次组件重新呈现时,我们都会重置计时器。

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

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

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

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

其他社区挂钩

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

总结

在本书的第一章中,我们首先学习 React 的基本原理以及它提供的组件类型。然后,我们继续学习类组件的常见问题,使用 React 的现有功能,以及它们如何打破基本原则。接下来,我们使用类组件和带挂钩的函数组件实现了一个简单的应用,以便能够比较两种解决方案之间的差异。我们发现,带有挂钩的函数组件更适合 React 的基本原则,因为它们不会遇到与类组件相同的问题,并且它们使我们的代码更加简洁易懂!最后,我们第一次看到了本书中我们将要学习的各种挂钩。在本章之后,React 和 React 挂钩的基础知识就很清楚了。现在我们可以继续学习更高级的挂钩概念。

在下一章中,我们将通过从头开始重新实现状态挂钩,深入了解状态挂钩是如何工作的。通过这样做,我们将了解挂钩在内部是如何工作的,以及它们的局限性是什么。之后,我们将使用状态挂钩创建一个小型博客应用!

问题

为了总结本章所学内容,请尝试回答以下问题:

  1. React 的三个基本原则是什么?
  2. React 中的两种组件是什么?
  3. React 中的类组件有什么问题?
  4. 在 React 中使用高阶组件有什么问题?

  5. 我们可以使用哪个工具来设置 React 项目,我们需要运行什么命令来使用它?

  6. 如果类组件出现以下错误,我们需要做什么:TypeError:undefined 不是对象(计算'this.setState')
  7. 我们如何使用挂钩访问和设置 React 状态?
  8. 与类组件相比,使用带挂钩的函数组件有哪些优点?
  9. 更新 React 时是否需要使用挂钩将所有类组件替换为函数组件?
  10. React 提供的三个基本挂钩是什么?

进一步阅读

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