React 是一个 JavaScript 库,可用于构建高效且可扩展的 Web 应用程序。React 由 Facebook 开发,用于许多大型 Web 应用程序,如 Facebook,Instagram,Netflix 和 WhatsApp Web。
在本书中,我们将学习如何使用 React 构建复杂高效的用户界面,同时保持代码的简单性和可扩展性。使用 React Hook 的新范式,我们可以大大简化 Web 应用程序中的状态管理和副作用的处理,确保以后增长和扩展应用程序的潜力。我们还将学习 React context 和 React Suspense ,以及如何将它们与 Hook 一起使用。之后,我们将学习如何将 Redux 和 MobX 与 React Hook 集成。最后,我们将学习如何从现有的 React 类组件、Redux 和 MobX 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。
观看以下视频,了解代码的实际应用:
请注意,强烈建议您自己编写代码。不要简单地运行以前提供的代码示例。为了正确学习和理解它,自己编写代码很重要。但是,如果遇到任何问题,始终可以参考代码示例。
现在,让我们从本章开始。
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)获取数据的类组件:
- 首先,我们通过扩展
React.Component
类来定义我们的类组件:
class Example extends React.Component {
- 然后,我们定义
componentDidMount
生命周期方法,在该方法中我们从 API 中拉取数据:
componentDidMount () {
fetch(`http://my.api/${this.props.name}`)
.then(...)
}
- 但是,我们还需要定义
componentDidUpdate
生命周期方法,以防name
prop 发生变化。此外,我们需要在此处添加手动检查,以确保我们仅在name
prop 更改时才重新获取数据,而不是在其他 prop 更改时重新获取数据:
componentDidUpdate (prevProps) {
if (this.props.name !== prevProps.name) {
fetch(`http://my.api/${this.props.name}`)
.then(...)
}
}
- 为了使我们的代码不那么重复,我们可以定义一个名为
fetchData
的单独方法,以便获取我们的数据,如下所示:
fetchData () {
fetch(`http://my.api/${this.props.name}`)
.then(...)
}
- 接下来,我们可以在
componentDidMount
和componentDidUpdate
中调用该方法:
componentDidMount () {
this.fetchData()
}
componentDidUpdate (prevProps) {
if (this.props.name !== prevProps.name) {
this.fetchData()
}
}
但是,即便如此,我们仍然需要在两个地方调用 fetchData
。每当我们更新传递给方法的参数时,我们都需要在两个地方更新它们,这使得这种模式很容易出现错误和将来出错。
包装器地狱
在 Hook 之前,如果我们想封装状态管理逻辑,就必须使用高阶组件和 render prop。例如,我们创建一个 React 组件,该组件使用上下文来处理用户身份验证,如下所示:
- 我们首先导入
authenticateUser
函数,以便将我们的组件包装在上下文中,然后导入AuthenticationContext
组件以访问上下文:
import authenticateUser, { AuthenticationContext } from './auth'
- 然后,我们定义我们的
App
组件,在其中我们使用AuthenticationContext.Consumer
组件和user
render prop:
const App = () => (
<AuthenticationContext.Consumer>
{
user =>
- 现在,我们根据用户是否登录显示不同的文本:
user ? `${user} logged in` : 'not logged in'
在这里,我们使用了两个 JavaScript 概念:
- 三元运算符,它是
if
条件的内联版本。它看起来像这样:ifThisisTrue ? returnThis : otherwiseReturnThis
。 - 模板字符串,可用于将变量插入到字符串。它是用反引号(\`)定义的,而不是普通的单引号(')。变量可以通过
${variableName}
语法插入。也可以在${}
括号中使用任何 JavaScript 表达式,例如${someValue + 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,这样我们就可以使用 JSX 和 ES6 语法
- 它甚至包括 ES6 以外的语言附加功能,例如对象扩展运算符,我们将在后面使用。
- 此外,我们甚至可以使用 TypeScript 和 Flow语法
此外,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
来访问我们的项目 :
我们的第一个 React 应用程序!
正如我们所看到的,使用 create-react-app
,设置一个新的 React 项目非常容易!
部署项目
要为生产部署构建项目,我们只需运行构建脚本:
- 运行以下命令以生成用于生产部署的项目:
npm run-script build
使用yarn
,我们可以简单地运行yarn build
。实际上,我们可以通过这种方式运行任何与内部yarn
命令名称不冲突的包脚本:yarn <script name>
,而不是npm run-script <script-name>
。
- 然后,我们可以使用 Web 服务器或使用
serve
工具为静态构建文件夹提供服务。首先,我们必须安装它:
npm install -g serve
- 然后,我们可以运行
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
接下来,我们将应用程序定义为一个类组件。
定义类组件
我们首先将应用程序编写为传统的类组件,如下所示:
- 首先,我们从
src/App.js
文件中删除所有代码。 - 接下来,在
src/App.js
中,我们导入React
:
import React from 'react'
- 然后我们开始定义我们自己的类组件——
MyName
:
class MyName extends React.Component {
- 接下来,我们必须定义一个
constructor
方法,在其中设置初始状态对象,该对象将是一个空字符串。在这里,我们还需要确保调用super(props)
,以便让React.Component
构造函数知道props
对象:
constructor (props) {
super(props)
this.state = { name: '' }
}
- 现在,我们使用
this.setState
定义一个方法来设置name
变量。由于我们将使用此方法来处理来自文本字段的输入,因此我们需要使用evt.target.value
从输入字段中获取值: - 然后,我们定义
render
方法,我们将在其中显示一个输入字段和名称:
render () {
- 为了从
this.state
对象中获取name
变量,我们将使用解构:
const { name } = this.state
上面的语句等效于执行以下操作:
const name = this.state.name
- 然后,我们显示当前输入的
name
状态变量:
return (
<div>
<h1>My name is: {name}</h1>
- 我们显示一个输入字段,将处理方法传递给它:
<input type="text" value={name} onChange=
{this.handleChange} />
</div>
)
}
}
- 最后,我们导出我们的类组件:
export default MyName
如果我们现在运行此代码,我们将在输入文本时收到以下错误,因为将处理方法传递给 onChange
会更改 this
上下文:
未捕获的类型错误:无法读取未定义的属性 'setState'
- 因此,现在我们需要调整构造函数方法并将处理方法的
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 定义函数组件。
定义函数组件
现在,我们将相同的组件定义为函数组件:
- 首先,我们从
src/App.js
文件中删除所有代码。 - 接下来,在
src/App.js
中,我们导入React
和useState
Hook:
import React, { useState } from 'react'
- 我们从函数定义开始。在我们的例子中,不传递任何参数,因为我们的组件没有任何 props:
function MyName () {
下一步是从组件状态中获取 name
变量。但是,不能在函数组件中使用 this.state
。我们已经知道 Hook 只是 JavaScript 函数,但这到底意味着什么呢?这意味着我们可以简单地使用函数组件中的 Hook,就像使用任何其他 JavaScript 函数一样!
要通过 Hook 使用状态,我们调用 useState()
,并将初始状态作为参数。此函数返回一个包含两个元素的数组:
- 当前状态
- 用于设置状态的 setter 函数
- 我们可以使用解构将这两个元素存储在单独的变量中,如下所示:
const [ name, setName ] = useState('')
上面的代码等价于下面的代码:
const nameHook = useState('')
const name = nameHook[0]
const setName = nameHook[1]
- 现在,我们定义输入处理函数,在其中我们使用
setName
setter 函数:
function handleChange (evt) {
setName(evt.target.value)
}
由于我们现在不处理类,因此无需再重新绑定 this
!
- 最后,我们来渲染从函数返回的用户界面。然后,我们导出函数组件:
return (
<div>
<h1>My name is: {name}</h1>
<input type="text" value={name} onChange={handleChange} />
</div>
)
}
export default MyName
就是这样——我们第一次成功使用 Hook!如您所见,useState
Hook 是 this.state
和 this.setState
的直接替代品。
让我们通过执行 npm start
来运行我们的应用程序,并在浏览器中打开 http://localhost:3000
:
我们第一个使用 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 代替,因此我们不需要处理 this
或 constructor
方法。完整的函数组件代码如下所示:
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:props、state、context、refs 和生命周期。 此外,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 的工作方式类似于在 componentDidMount
和 componentDidUpdate
上添加一个函数。此外,Effect Hook 允许从中返回清理函数,其工作原理类似于向 compoentWillUnmount
添加一个函数。
useEffect
Hook 用于处理有效的代码,例如计时器、订阅、请求等。我们可以按如下方式使用它:
import { useEffect } from 'react'
useEffect(didUpdate)
useEffect
Hook 取代了 componentDidMount
、componentDidUpdate
和 componentWillUnmount
方法。
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
库还提供了用于 setInterval
和 setTimeout
的 Hook。这些工作类似于直接调用 setTimeout
或 setInterval
,但作为 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 创建一个小型博客应用程序!
问题
为了回顾我们在本章中学到的内容,请尝试回答以下问题:
- React 的三个基本原则是什么?
- React 中的两种组件是什么?
- React 中的类组件存在哪些问题?
- 在 React 中使用高阶组件有什么问题?
- 我们可以使用哪种工具来设置 React 项目,我们需要运行什么命令才能使用它?
- 如果我们在使用类组件时遇到以下错误,我们需要做什么:TypeError:undefined 不是一个对象(评估 'this.setState')?
- 我们如何使用 Hook 访问和设置 React 状态?
- 与类组件相比,将函数组件与 Hook 一起使用有什么优势?
- 在更新 React 时,我们是否需要使用 Hook 将所有类组件替换为函数组件?
- React 提供的三个基本 Hook 是什么?
延伸阅读
如果您对我们在本章中学到的概念的更多信息感兴趣,请查看以下阅读材料:
- create-react-app GitHub 地址:https://github.com/facebook/create-react-app#create-react-app
- React Hooks 征求意见:https://github.com/reactjs/rfcs/blob/master/text/0068-react-hooks.md
- 使用 React 处理输入:https://reactjs.org/docs/forms.html
- React 类组件中的状态和生命周期与:https://reactjs.org/docs/state-and-lifecycle.html
- 解构:http://exploringjs.com/es6/ch_destructuring.html
- 模板字符串:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Ref erence/Template_literals
- 三元运算符:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Ref
评论 (0)