八、使用社区挂钩
在上一章中,我们使用 Navi 库实现了路由。我们从实现页面开始,然后定义路由和静态链接。最后,我们实现了动态链接,并使用挂钩访问路由信息。
在本章中,我们将学习 React 社区提供的各种挂钩。这些挂钩可用于简化输入处理,并实现 React 生命周期,以简化从 React 类组件的迁移。此外,还有实现各种行为的挂钩,如计时器、检查客户端是否在线、悬停和聚焦事件以及数据操作。最后,我们将学习响应式设计和使用挂钩实现撤销/重做功能。
本章将介绍以下主题:
- 使用输入挂钩简化输入处理
- 用挂钩实现 React 生命周期
- 学习各种有用的挂钩(
usePrevious
、定时器、在线、聚焦、悬停和数据操作挂钩) - 用挂钩实现响应性设计
- 使用挂钩实现撤消/重做功能和去抖动
- 学习在哪里找到其他挂钩
技术要求
应该已经安装了 Node.js 的最新版本(v11.12.0 或更高版本)。Node.js 的npm
包管理器也需要安装。
本章的代码可以在 GitHub 存储库中找到:https://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter08 。
请查看以下视频以查看代码的运行情况:
Please note that it is highly recommended that you write the code on your own. Do not simply run the code examples that have been provided. It is important that you write the code yourself in order to be able to learn and understand properly. However, if you run into any issues, you can always refer to the code example.
现在,让我们从这一章开始。
探索输入处理挂钩
在处理挂钩时,一个非常常见的用例是使用状态和效果挂钩存储input
字段的当前值。在本书中,我们已经多次这样做了。
useInput
挂钩通过提供处理input
字段的value
变量的单个挂钩,大大简化了这个用例。其工作原理如下:
import React from 'react'
import { useInput } from 'react-hookedup'
export default function App () {
const { value, onChange } = useInput('')
return <input value={value} onChange={onChange} />
}
此代码将onChange
处理程序函数和value
绑定到input
字段。这意味着每当我们在input
字段中输入文本时,value
将自动更新。
此外,还有一个清除input
字段的功能。这个clear
函数也是从挂钩返回的:
const { clear } = useInput('')
调用clear
函数将value
设置为空值,并清除input
字段中的所有文本。
此外,挂钩提供了两种绑定input
字段的方法:
bindToInput
:使用e.target.value
作为onChange
函数的value
参数,将value
和onChange
道具绑定到input
字段。这在处理 HTMLinput
字段时非常有用。bind
:仅使用e
作为onChange
函数的值,将value
和onChange
道具绑定到input
字段。这对于直接将值传递给onChange
函数的 React 组件非常有用。
bind
和bindToInput
对象可与扩展运算符一起使用,如下所示:
import React from 'react'
import { useInput } from 'react-hookedup'
const ToggleButton = ({ value, onChange }) => { ... } // custom component that renders a toggle button
export default function App () {
const { bind, bindToInput } = useInput('')
return (
<div>
<input {...bindToInput} />
<ToggleButton {...bind} />
</div>
)
}
我们可以看到,对于input
字段,我们可以使用{...bindToInput}
道具来分配value
和onChange
函数。对于ToggleButton
,我们需要使用{...bind}
道具,因为我们这里不处理输入事件,并且值直接传递给变更处理程序(不是通过e.target.value
)。
现在我们已经了解了输入挂钩,我们可以继续在我们的博客应用中实现它。
在我们的博客应用中实现输入挂钩
现在我们已经了解了输入挂钩,以及它如何简化处理input
字段状态,我们将在我们的博客应用中实现输入挂钩。
首先,我们必须在我们的博客应用项目中安装react-hookedup
库:
> npm install --save react-hookedup
我们现在将在以下组件中实现输入挂钩:
Login
组件Register
组件CreatePost
组件
让我们开始实现输入挂钩。
登录组件
我们在Login
组件中有两个input
字段:用户名和密码字段。我们现在将用输入挂钩替换状态挂钩。
现在让我们开始在Login
组件中实现输入挂钩:
- 导入
src/user/Login.js
文件开头的useInput
挂钩:
import { useInput } from 'react-hookedup'
- 然后,我们移除以下
username
状态挂钩:
const [ username, setUsername ] = useState('')
将其替换为输入挂钩,如下所示:
const { value: username, bindToInput: bindUsername } = useInput('')
Since we are using two Input Hooks, in order to avoid name collisions, we are using the rename syntax ({ from: to }
) in object destructuring to rename the value
key to username
, and bindToInput
key to bindUsername
.
- 我们还移除了以下
password
状态挂钩:
const [ password, setPassword ] = useState('')
将其替换为输入挂钩,如下所示:
const { value: password, bindToInput: bindPassword } = useInput('')
- 现在,我们可以删除以下处理程序函数:
function handleUsername (evt) {
setUsername(evt.target.value)
}
function handlePassword (evt) {
setPassword(evt.target.value)
}
- 最后,我们使用输入挂钩中的绑定对象,而不是手动传递
onChange
处理程序:
<input type="text" value={username} {...bindUsername} name="login-username" id="login-username" />
<input type="password" value={password} {...bindPassword} name="login-password" id="login-password" />
登录功能仍将以与以前完全相同的方式工作,但我们现在使用更简洁的输入挂钩,而不是通用状态挂钩。我们也不必为每个input
字段定义相同类型的处理函数。正如我们所看到的,使用社区挂钩可以大大简化常见用例的实现,例如输入处理。我们现在将对Register
组件重复相同的过程。
寄存器组件
Register
组件的工作原理与Login
组件类似。但是,它有三个input
字段:用户名、密码和重复密码。
现在让我们在Register
组件中实现输入挂钩:
- 导入
src/user/Register.js
文件开头的useInput
挂钩:
import { useInput } from 'react-hookedup'
- 然后,我们移除以下状态挂钩:
const [ username, setUsername ] = useState('')
const [ password, setPassword ] = useState('')
const [ passwordRepeat, setPasswordRepeat ] = useState('')
它们将替换为相应的输入挂钩:
const { value: username, bindToInput: bindUsername } = useInput('')
const { value: password, bindToInput: bindPassword } = useInput('')
const { value: passwordRepeat, bindToInput: bindPasswordRepeat } = useInput('')
- 同样,我们可以删除所有处理程序函数:
function handleUsername (evt) {
setUsername(evt.target.value)
}
function handlePassword (evt) {
setPassword(evt.target.value)
}
function handlePasswordRepeat (evt) {
setPasswordRepeat(evt.target.value)
}
- 最后,我们用相应的绑定对象替换所有的
onChange
处理程序:
<input type="text" value={username} {...bindUsername} name="register-username" id="register-username" />
<input type="password" value={password} {...bindPassword} name="register-password" id="register-password" />
<input type="password" value={passwordRepeat} {...bindPasswordRepeat} name="register-password-repeat" id="register-password-repeat/>
寄存器功能也将以同样的方式工作,但现在使用输入挂钩。接下来是CreatePost
组件,我们也将在其中实现输入挂钩。
CreatePost 组件
CreatePost
组件使用两个input
字段:一个用于title
,另一个用于content
。我们将用输入挂钩替换它们。
现在让我们在CreatePost
组件中实现输入挂钩:
- 导入
src/user/CreatePost.js
文件开头的useInput
挂钩:
import { useInput } from 'react-hookedup'
- 然后,我们移除以下状态挂钩:
const [ title, setTitle ] = useState('')
const [ content, setContent ] = useState('')
我们用相应的输入挂钩替换它们:
const { value: title, bindToInput: bindTitle } = useInput('')
const { value: content, bindToInput: bindContent } = useInput('')
- 同样,我们可以删除以下输入处理程序函数:
function handleTitle (evt) {
setTitle(evt.target.value)
}
function handleContent (evt) {
setContent(evt.target.value)
}
- 最后,我们用相应的绑定对象替换所有的
onChange
处理程序:
<input type="text" value={title} {...bindTitle} name="create-title" id="create-title" />
</div>
<textarea value={content} {...bindContent} />
createpost 功能也将以与输入挂钩相同的方式工作。
示例代码
示例代码可在Chapter08/chapter8_1
文件夹中找到。
只需运行npm install
安装所有依赖项,并运行npm start
启动应用,然后访问http://localhost:3000 在您的浏览器中(如果未自动打开)。
挂钩和 React 生命周期
正如我们在前几章中所了解的,我们可以使用useEffect
挂钩对 React 的大多数生命周期方法进行建模。但是,如果您喜欢直接处理 React 生命周期,而不是使用 Effect 挂钩,那么有一个名为react-hookedup
的库,它提供了各种挂钩,包括各种 React 生命周期的挂钩。此外,该库还提供了一个合并状态挂钩,其工作原理与 React 类组件中的this.setState()
类似。
useOnMount 挂钩
useOnMount
挂钩与componentDidMount
生命周期具有类似的效果。其用途如下:
import React from 'react'
import { useOnMount } from 'react-hookedup'
export default function UseOnMount () {
useOnMount(() => console.log('mounted'))
return <div>look at the console :)</div>
}
前面的代码将在安装组件时(第一次呈现 React 组件时)输出 mounted 到控制台。当组件由于(例如)道具更改而重新渲染时,将不会再次调用它。
或者,我们可以使用带有空数组的useEffect
挂钩作为第二个参数,其效果相同:
import React, { useEffect } from 'react'
export default function OnMountWithEffect () {
useEffect(() => console.log('mounted with effect'), [])
return <div>look at the console :)</div>
}
如我们所见,使用带有空数组的效果挂钩作为第二个参数会导致与useOnMount
挂钩或componentDidMount
生命周期方法相同的行为。
useOnUnmount 挂钩
useOnUnmount
挂钩与componentWillUnmount
生命周期具有类似的效果。其用途如下:
import React from 'react'
import { useOnUnmount } from 'react-hookedup'
export default function UseOnUnmount () {
useOnUnmount(() => console.log('unmounting'))
return <div>click the "unmount" button above and look at the console</div>
}
前面的代码将在卸载组件时(在从 DOM 中移除 React 组件之前)将卸载输出到控制台。
如果您还记得第 4 章中提到的使用简化器和效果挂钩,我们可以从useEffect
挂钩返回一个清理功能,组件卸载时会调用该功能。这意味着我们也可以使用useEffect
实现useOnMount
挂钩,如下所示:
import React, { useEffect } from 'react'
export default function OnUnmountWithEffect () {
useEffect(() => {
return () => console.log('unmounting with effect')
}, [])
return <div>click the "unmount" button above and look at the console</div>
}
正如我们所看到的,使用从一个 Effect 挂钩返回的 cleanup 函数,使用一个空数组作为第二个参数,与useOnUnmount
挂钩或componentWillUnmount
生命周期方法具有相同的效果。
useLifecycleHooks 挂钩
useLifecycleHooks
挂钩将前两个挂钩合并为一个。我们可以将useOnMount
和useOnUnmount
挂钩组合如下:
import React from 'react'
import { useLifecycleHooks } from 'react-hookedup'
export default function UseLifecycleHooks () {
useLifecycleHooks({
onMount: () => console.log('lifecycle mounted'),
onUnmount: () => console.log('lifecycle unmounting')
})
return <div>look at the console and click the button</div>
}
或者,我们可以分别使用两个挂钩:
import React from 'react'
import { useOnMount, useOnUnmount } from 'react-hookedup'
export default function UseLifecycleHooksSeparate () {
useOnMount(() => console.log('separate lifecycle mounted'))
useOnUnmount(() => console.log('separate lifecycle unmounting'))
return <div>look at the console and click the button</div>
}
但是,如果您有这种模式,我建议您只使用useEffect
挂钩,如下所示:
import React, { useEffect } from 'react'
export default function LifecycleHooksWithEffect () {
useEffect(() => {
console.log('lifecycle mounted with effect')
return () => console.log('lifecycle unmounting with effect')
}, [])
return <div>look at the console and click the button</div>
}
使用useEffect
,我们可以将整个效果放在一个函数中,然后简单地返回一个函数进行清理。当我们在接下来的章节中学习如何制作自己的挂钩时,这种模式尤其有用。
效应使我们对 React 组件有不同的想法。我们根本不必考虑组件的生命周期。相反,我们考虑的是效果、依赖关系和效果的清理。
useMergeState 挂钩
useMergeState
吊钩的工作原理与useState
吊钩类似。但是,它不会替换当前状态,而是将当前状态与新状态合并,就像this.setState()
在 React 类组件中工作一样。
合并状态挂钩返回以下对象:
state
:当前状态setState
:将当前状态与给定状态对象合并的函数
例如,让我们考虑以下组件:
- 首先,我们导入
useState
挂钩:
import React, { useState } from 'react'
- 然后,我们使用包含
loaded
值和counter
值的对象定义我们的应用组件和状态挂钩:
export default function MergeState () {
const [ state, setState ] = useState({ loaded: true, counter: 0 })
- 接下来我们定义一个
handleClick
函数,在这里我们设置新的state
,将当前counter
值增加1
:
function handleClick () {
setState({ counter: state.counter + 1 })
}
- 最后,我们呈现当前的
counter
值和一个+1 按钮,以便将counter
值增加1
。当state.loaded
为false
或undefined
时,该按钮将被禁用:
return (
<div>
Count: {state.counter}
<button onClick={handleClick} disabled={!state.loaded}>+1</button>
</div>
)
}
正如我们所见,我们有一个简单的计数器应用,显示当前计数和+1 按钮。只有当loaded
值设置为true
时,+1 按钮才会启用。
如果我们现在点击+1 按钮,counter
将从0
增加到1
,但该按钮将被禁用,因为我们已经用一个新的state
对象覆盖了当前的state
对象。
要解决此问题,我们必须调整handleClick
功能,如下所示:
function handleClick () {
setState({ ...state, counter: state.counter + 1 })
}
或者,我们可以使用useMergeState
挂钩来完全避免这个问题,并获得与this.setState()
类组件相同的行为:
import React from 'react'
import { useMergeState } from 'react-hookedup'
export default function UseMergeState () {
const { state, setState } = useMergeState({ loaded: true, counter: 0 })
正如我们所看到的,通过使用useMergeState
挂钩,我们可以重现与this.setState()
类组件相同的行为。因此,我们不再需要使用扩展语法。然而,通常情况下,最好只使用多个状态挂钩或减速机挂钩。
示例代码
示例代码可在Chapter08/chapter8_2
文件夹中找到。
只需运行npm install
安装所有依赖项,并启动npm start
应用,然后在浏览器中访问http://localhost:3000
(如果它没有自动打开)。
各种有用的挂钩
除了生命周期挂钩外,react-hookedup
还为计时器提供挂钩,检查网络状态,以及其他各种有用的挂钩,用于处理数组和输入字段等。我们现在将介绍react-hookedup
提供的其他挂钩。
这些挂钩如下:
usePrevious
挂钩,获取挂钩或道具的先前值- 计时器挂钩,用于实现间隔和超时
useOnline
挂钩,用于检查客户端是否有活动的 internet 连接- 用于处理布尔、数组和计数器的各种数据操作挂钩
- 用于处理焦点和悬停事件的挂钩
上钩
usePrevious
挂钩是一个简单的挂钩,可以让我们获得道具或挂钩值的先前值。它将始终存储并返回任何给定变量的先前值,其工作方式如下:
- 首先,我们进口
useState
和usePrevious
挂钩:
import React, { useState } from 'react'
import { usePrevious } from 'react-hookedup'
- 然后,我们定义我们的
App
组件,以及一个存储当前count
状态的挂钩:
export default function UsePrevious () {
const [ count, setCount ] = useState(0)
- 现在,我们定义
usePrevious
挂钩,将count
值从状态挂钩传递给它:
const prevCount = usePrevious(count)
The usePrevious
Hook works with any variable, including component props and values from other Hooks.
- 接下来,我们定义一个 handler 函数,它将
count
增加1
:
function handleClick () {
setCount(count + 1)
}
- 最后,我们呈现之前的值
count
、当前值count
以及增加count
的按钮:
return (
<div>
Count was {prevCount} and is {count} now.
<button onClick={handleClick}>+1</button>
</div>
)
}
先前定义的组件将首先显示 Count was,现在为 0,因为上一个挂钩的默认值为null
。单击按钮一次时,将显示以下内容:计数为 0,现在为 1。。
计时器挂钩
react-hookedup
库还提供了处理计时器的挂钩。如果我们只是在我们的组件中使用setTimeout
或setInterval
创建一个计时器,它将在每次重新呈现组件时再次实例化。这不仅会导致错误和不可预测性,而且如果没有正确释放旧计时器,还会导致内存泄漏。使用计时器挂钩,我们可以完全避免这些问题,并且可以轻松地使用间隔和超时。
库提供了以下计时器挂钩:
useInterval
挂钩,用于定义 React 组件中的setInterval
计时器(多次触发的计时器)useTimeout
挂钩,用于定义setTimeout
计时器(在一定时间后仅触发一次的计时器)
间隔钩
useInterval
挂钩可以像setInterval
一样使用。现在,我们将实现一个小型计数器,用于计算安装组件后的秒数:
- 首先,导入
useState
和useInterval
挂钩:
import React, { useState } from 'react'
import { useInterval } from 'react-hookedup'
- 然后,我们定义组件和状态挂钩:
export default function UseInterval () {
const [ count, setCount ] = useState(0)
- 接下来,我们定义
useInterval
挂钩,每1000
ms 增加count
一次1
,等于1
秒:
useInterval(() => setCount(count + 1), 1000)
- 最后,我们显示当前的
count
值:
return <div>{count} seconds passed</div>
}
或者,我们可以将效果挂钩与setInterval
结合使用,而不是useInterval
挂钩,如下所示:
import React, { useState, useEffect } from 'react'
export default function IntervalWithEffect () {
const [ count, setCount ] = useState(0)
useEffect(() => {
const interval = setInterval(() => setCount(count + 1), 1000)
return () => clearInterval(interval)
})
return <div>{count} seconds passed</div>
}
正如我们所看到的,useInterval
挂钩使我们的代码更加简洁易读。
使用超时挂钩
useTimeout
挂钩可以像setTimeout
一样使用。我们现在要实现一个在10
秒后触发的组件:
- 首先,导入
useState
和useTimeout
挂钩:
import React, { useState } from 'react'
import { useTimeout } from 'react-hookedup'
- 然后,我们定义组件和状态挂钩:
export default function UseTimeout () {
const [ ready, setReady ] = useState(false)
- 接下来,我们定义
useTimeout
挂钩,在10000
毫秒(10
秒)之后,将ready
设置为true
:
useTimeout(() => setReady(true), 10000)
- 最后,我们显示是否准备就绪:
return <div>{ready ? 'ready' : 'waiting...'}</div>
}
或者,我们可以将效果挂钩与setTimeout
结合使用,而不是useTimeout
挂钩,如下所示:
import React, { useState, useEffect } from 'react'
export default function TimeoutWithEffect () {
const [ ready, setReady ] = useState(false)
useEffect(() => {
const timeout = setTimeout(() => setReady(true), 10000)
return () => clearTimeout(timeout)
})
return <div>{ready ? 'ready' : 'waiting...'}</div>
}
正如我们所看到的,useTimeout
挂钩使我们的代码更加简洁易读。
在线状态挂钩
在某些 web 应用中,实现脱机模式是有意义的;例如,如果我们希望能够在本地编辑和保存帖子的草稿,并在再次联机时将其同步到服务器。为了能够实现这个用例,我们可以使用useOnlineStatus
挂钩。
在线状态挂钩返回一个带有online
值的对象,如果客户端在线,该对象包含true
;否则,它包含false
。其工作原理如下:
import React from 'react'
import { useOnlineStatus } from 'react-hookedup'
export default function App () {
const { online } = useOnlineStatus()
return <div>You are {online ? 'online' : 'offline'}!</div>
}
上一个组件将显示您处于联机状态!,当 internet 连接可用或您处于脱机状态时!,否则
然后,我们可以使用前一个挂钩和一个效果挂钩,以便在我们再次联机时将数据同步到服务器:
import React, { useEffect } from 'react'
import { useOnlineStatus, usePrevious } from 'react-hookedup'
export default function App () {
const { online } = useOnlineStatus()
const prevOnline = usePrevious(online)
useEffect(() => {
if (prevOnline === false && online === true) {
alert('syncing data')
}
}, [prevOnline, online])
return <div>You are {online ? 'online' : 'offline'}!</div>
}
现在,我们有一个效果挂钩,每当online
的值改变时就会触发。然后检查之前的online
值是否为false
,当前值是否为true
。如果是这样的话,那就意味着我们离线了,现在又在线了,所以我们需要将更新的数据同步到服务器上。
因此,我们的应用将显示一个警报,在我们离线后再次在线时显示同步数据。
数据操作挂钩
react-hookedup
库提供了各种用于处理数据的实用程序挂钩。这些挂钩简化了对公共数据结构的处理,并提供了对状态挂钩的抽象。
提供了以下数据操作挂钩:
useBoolean
挂钩:处理布尔值的切换useArray
挂钩:处理数组useCounter
挂钩:对付柜台
布尔挂钩
useBoolean
挂钩用于处理布尔值的切换(true
/false
),并提供将值设置为true
/false
的函数和切换值的toggle
函数。
挂钩返回具有以下内容的对象:
value
:布尔值的当前值toggle
:切换当前值的功能(如果当前为false
,则设置true
;如果当前为true
,则设置false
)setTrue
:将当前值设置为true
setFalse
:将当前值设置为false
布尔挂钩的工作原理如下:
- 首先,我们从
react-hookedup
导入useBoolean
挂钩:
import React from 'react'
import { useBoolean } from 'react-hookedup'
- 然后,我们定义我们的组件和布尔挂钩,它返回一个带有
toggle
函数和value
的对象。我们通过false
作为默认值:
export default function UseBoolean () {
const { toggle, value } = useBoolean(false)
- 最后,我们渲染一个按钮,可以打开/关闭:
return (
<div>
<button onClick={toggle}>{value ? 'on' : 'off'}</button>
</div>
)
}
按钮最初将在文本关闭的情况下呈现。单击按钮时,它将在屏幕上显示文本。再次单击时,它将再次关闭。
使用数组挂钩
useArray
挂钩用于轻松处理数组,无需使用 rest/spread 语法。
数组挂钩返回具有以下内容的对象:
value
:当前数组setValue
:设置一个新数组作为值add
:将给定元素添加到数组中clear
:从数组中删除所有元素removeIndex
:根据元素的索引从数组中移除元素removeById
:通过id
从数组中移除元素(假设数组中的元素是具有id
键的对象)
其工作原理如下:
- 首先,我们从
react-hookedup
导入useArray
挂钩:
import React from 'react'
import { useArray } from 'react-hookedup'
- 然后定义组件和数组挂钩,默认值为
['one', 'two', 'three']
:
export default function UseArray () {
const { value, add, clear, removeIndex } = useArray(['one', 'two', 'three'])
- 现在,我们将当前数组显示为 JSON:
return (
<div>
<p>current array: {JSON.stringify(value)}</p>
- 然后,我们向
add
元素显示一个按钮:
<button onClick={() => add('test')}>add element</button>
- 接下来,我们将显示一个按钮以按索引删除第一个元素:
<button onClick={() => removeIndex(0)}>remove first element</button>
- 最后,我们在
clear
所有元素中添加一个按钮:
<button onClick={() => clear()}>clear elements</button>
</div>
)
}
正如我们所看到的,使用useArray
挂钩使处理数组变得更加简单。
反钩
useCounter
挂钩可用于定义各种计数器。我们可以定义下限/上限,指定计数器是否应循环,并指定增加/减少计数器的步长。此外,计数器挂钩提供增加/减少计数器的功能。
它接受以下配置选项:
upperLimit
:定义我们计数器的上限(最大值)lowerLimit
:定义我们计数器的下限(最小值)loop
:指定计数器是否应循环(例如,当达到最大值时,我们返回最小值)step
:设置增加和减少功能的默认步数
它返回以下对象:
value
:我们计数器的当前值。setValue
:设置我们计数器的当前值。increase
:将该值增加给定的步长量。如果未指定金额,则使用默认步骤金额。decrease
:将该值减少给定的步长量。如果未指定金额,则使用默认步骤金额。
反钩可按如下方式使用:
- 首先,我们从
react-hookedup
导入useCounter
挂钩:
import React from 'react'
import { useCounter } from 'react-hookedup'
- 然后,我们定义组件和挂钩,将
0
指定为默认值。我们还指定了upperLimit
、lowerLimit
和loop
:
export default function UseCounter () {
const { value, increase, decrease } = useCounter(0, { upperLimit: 3, lowerLimit: 0, loop: true })
- 最后,我们将当前值和两个按钮渲染为
increase
/decrease
值:
return (
<div>
<b>{value}</b>
<button onClick={increase}>+</button>
<button onClick={decrease}>-</button>
</div>
)
}
正如我们所看到的,计数器挂钩使计数器的实现更加简单。
焦点和悬停挂钩
有时,我们想检查用户是否悬停在某个元素上或关注某个input
字段。为此,我们可以使用react-hookedup
库提供的焦点和悬停挂钩。
库为这些功能提供了两个挂钩:
useFocus
挂钩:处理焦点事件(例如,所选input
字段)useHover
挂钩:处理悬停事件(例如,将鼠标指针悬停在某个区域上时)
聚焦挂钩
为了知道某个元素当前是否被聚焦,我们可以使用useFocus
挂钩,如下所示:
- 首先,我们导入
useFocus
挂钩:
import React from 'react'
import { useFocus } from 'react-hookedup'
- 然后,我们定义组件和焦点挂钩,它返回
focused
值和bind
函数,将挂钩绑定到元素:
export default function UseFocus () {
const { focused, bind } = useFocus()
- 最后,我们渲染一个
input
字段,并将焦点挂钩绑定到该字段:
return (
<div>
<input {...bind} value={focused ? 'focused' : 'not focused'} />
</div>
)
}
正如我们所看到的,焦点挂钩使得处理焦点事件变得更加容易。不再需要定义我们自己的处理函数了。
悬停钩
为了知道用户当前是否在元素上悬停,我们可以使用useHover
挂钩,如下所示:
- 首先,我们导入
useHover
挂钩:
import React from 'react'
import { useHover } from 'react-hookedup'
- 然后,我们定义组件和悬停挂钩,它返回
hovered
值和bind
函数,将挂钩绑定到元素:
export default function UseHover () {
const { hovered, bind } = useHover()
- 最后,我们渲染一个元素,并将悬停挂钩绑定到该元素:
return (
<div {...bind}>Hover me {hovered && 'THANKS!!!'}</div>
)
}
正如我们所看到的,悬停挂钩使处理悬停事件变得更加容易。不再需要定义我们自己的处理函数了。
示例代码
示例代码可在Chapter08/chapter8_3
文件夹中找到。
只需运行npm install
安装所有依赖项,并启动npm start
应用,然后在浏览器中访问http://localhost:3000
(如果它没有自动打开)。
带挂钩的响应性设计
在 web 应用中,具有响应性的设计通常很重要。响应性设计可使您的 web 应用在各种设备和窗口/屏幕大小上呈现良好。我们的博客应用可以在桌面、手机、平板电脑上观看,甚至可以在电视等超大屏幕上观看。
通常,简单地使用 CSS 媒体查询进行响应性设计是最有意义的。然而,有时这是不可能的,例如,当我们在画布或Web 图形库(WebGL中渲染元素时。有时,我们还希望使用窗口大小来决定是否加载组件,而不是简单地渲染组件,然后稍后通过 CSS 将其隐藏。
@rehooks/window-size
库提供useWindowSize
挂钩,挂钩返回以下值:
innerWidth
:等于window.innerWidth
值innerHeight
:等于window.innerHeight
值outerWidth
:等于window.outerWidth
值outerHeight
:等于window.outerHeight
值
要显示outerWidth
/outerHeight
与innerWidth
/innerHeight
之间的差异,请看下图:
Visualization of the window width/height properties
我们可以看到,innerHeight
和innerWidth
指定了浏览器窗口的最里面部分,而outerHeight
和outerWidth
指定了浏览器窗口的完整尺寸,包括 URL 栏、滚动条等。
我们现在将根据博客应用中的窗口大小隐藏组件。
响应隐藏组件
在我们的博客应用中,当屏幕尺寸非常小时,我们将完全隐藏UserBar
和ChangeTheme
组件,以便在手机上阅读帖子时,我们可以专注于内容。
让我们开始实现窗口大小挂钩:
- 首先,我们必须安装
@rehooks/window-size
库:
> npm install --save @rehooks/window-size
- 然后,我们在
src/pages/HeaderBar.js
文件的开头导入useWindowSize
挂钩:
import useWindowSize from '@rehooks/window-size'
- 接下来,我们在现有上下文挂钩之后定义以下窗口大小挂钩:
const { innerWidth } = useWindowSize()
- 如果窗口宽度小于
640
像素,我们假设该设备为手机:
const mobilePhone = innerWidth < 640
- 最后,我们仅在不使用手机时显示
ChangeTheme
和UserBar
组件:
{!mobilePhone && <ChangeTheme theme={theme} setTheme={setTheme} />}
{!mobilePhone && <br />}
{!mobilePhone && <React.Suspense fallback={"Loading..."}>
<UserBar />
</React.Suspense>}
{!mobilePhone && <br />}
如果我们现在将浏览器窗口调整为小于640
像素的宽度,我们可以看到ChangeTheme
和UserBar
组件将不再渲染:
Hiding the ChangeTheme and UserBar components on smaller screen sizes
使用窗口大小挂钩,我们可以避免在较小的屏幕大小上渲染元素。
示例代码
示例代码可在Chapter08/chapter8_4
文件夹中找到。
只需运行npm install
安装所有依赖项,并启动npm start
应用,然后在浏览器中访问http://localhost:3000
(如果它没有自动打开)。
用挂钩撤消/重做
在某些应用中,我们希望实现撤销/重做功能,这意味着我们可以在应用的状态下来回移动。例如,如果我们的博客应用中有一个文本编辑器,我们希望提供一个撤销/重做更改的功能。如果您了解了 Redux,您可能已经熟悉这种功能。由于 React 现在提供了一个简化器挂钩,因此我们可以仅使用 React 重新实现相同的功能。use-undo
库正好提供了这一功能。
useUndo
挂钩将默认state
对象作为参数,并返回一个包含以下内容的数组:[ state, functions ]
。
state
对象如下所示:
present
:当前状态past
:过去状态的数组(当我们撤销时,我们转到这里)future
:未来状态数组(撤销后可以重做此处)
functions
对象返回各种与撤销挂钩交互的函数:
set
:设置当前状态,并为present
赋值。reset
:重置当前状态,清除past
和future
数组(撤消/重做历史记录),并为present
赋值。undo
:撤消到上一个状态(通过past
数组的元素)。redo
:重做到下一个状态(通过future
数组的元素)。canUndo
:如果可以执行撤消操作(past
数组不为空),则等于true
。canRedo
:如果可以执行重做操作,则等于true
(future
数组不为空)。
我们现在将在我们的 post 编辑器中实现撤销/重做功能。
在我们的 post 编辑器中实现 Undo/Redo
在我们博客应用的简单帖子编辑器中,我们有一个textarea
,可以在那里写博客帖子的内容。我们现在将在那里实现useUndo
挂钩,这样我们就可以撤销/重做对文本所做的任何更改:
- 首先,我们必须通过
npm
安装use-undo
库:
> npm install --save use-undo
- 然后,我们从
src/post/CreatePost.js
中的库中导入useUndo
挂钩:
import useUndo from 'use-undo'
- 接下来,我们通过替换当前的
useInput
挂钩来定义撤销挂钩。删除以下代码行:
const { value: content, bindToInput: bindContent } = useInput('')
更换为useUndo
挂钩,如下所示。我们将默认状态设置为''
。我们还将状态保存到undoContent
,并获取setContent
、undo
和redo
函数,以及canUndo
和canRedo
值:
const [ undoContent, {
set: setContent,
undo,
redo,
canUndo,
canRedo
} ] = useUndo('')
- 现在,我们将
undoContent.present
状态分配给content
变量:
const content = undoContent.present
- 接下来,我们定义一个新的处理函数,以便使用
setContent
函数更新content
值:
function handleContent (e) {
setContent(e.target.value)
}
- 然后,我们必须用
handleContent
函数替换bindContent
对象,如下所示:
<textarea value={content} onChange={handleContent} />
- 最后,我们在
textarea
元素之后定义了撤销/重做更改的按钮:
<button type="button" onClick={undo} disabled={!canUndo}>Undo</button>
<button type="button" onClick={redo} disabled={!canRedo}>Redo</button>
It is important that <button>
elements in a <form>
element have a type
attribute defined. If the type
attribute is not defined, buttons are assumed to be type="submit"
, which means that they will trigger the onSubmit
handler function when clicked.
现在,在输入文本后,我们可以按“撤消”一次删除一个字符,然后按“重做”再次添加字符。接下来,我们将实现去 Bouncing,这意味着我们的更改只会在一段时间后添加到 undo history,而不是在输入的每个字符之后。
用挂钩去抖动
正如我们在上一节中所看到的,当我们按下“撤消”时,它一次撤消一个字符。有时,我们不想在撤销历史记录中存储所有更改。为了避免存储每一个更改,我们需要实现去 Bouncing,这意味着将我们的content
存储到 undo history 的函数只会在一定时间后调用。
use-debounce
库提供了useDebounce
挂钩,可用于以下简单值:
const [ text, setText ] = useState('')
const [ value ] = useDebounce(text, 1000)
现在,如果我们通过setText
更改文本,text
值会立即更新,value
变量只会在1000
ms(1
秒)后更新。
然而,对于我们的用例来说,这是不够的。我们需要取消公告回调,以便结合use-undo
实现取消公告。use-debounce
库还提供useDebouncedCallback
挂钩,挂钩的使用方式如下:
const [ text, setText ] = useState('')
const [ debouncedSet, cancelDebounce ] = useDebouncedCallback(
(value) => setText(value),
1000
)
现在,如果我们调用debouncedSet('text')
,则text
值将在1000
毫秒(1
秒)后更新。如果多次调用debouncedSet
,则每次都会重置超时,因此只有在debouncedSet
函数不再调用1000
ms 后,才会调用setText
函数。接下来,我们将继续在 post editor 中实现去 Bouncing。
在我们的博文编辑中取消公告更改
现在我们已经了解了去 Bouncing,我们将结合 post editor 中的 Undo 挂钩来实现它,如下所示:
- 首先,我们必须通过
npm
安装use-debounce
库:
> npm install --save use-debounce
- 在
src/post/CreatePost.js
中,首先确保导入useState
挂钩,如果尚未导入:
import React, { useState, useContext, useEffect } from 'react'
- 接下来,从
use-debounce
库导入useDebouncedCallback
挂钩:
import { useDebouncedCallback } from 'use-debounce'
- 现在,在 Undo 挂钩之前,定义一个新的状态挂钩,我们将使用该挂钩来更新
input
字段,该挂钩用于非去模糊值:
const [ content, setInput ] = useState('')
- 在撤销挂钩之后,我们移除了
content
值的赋值。删除以下代码:
const content = undoContent.present
- 现在,在撤消挂钩之后,定义取消公告的回调挂钩:
const [ setDebounce, cancelDebounce ] = useDebouncedCallback(
- 在取消公告回调挂钩中,我们定义了一个函数来设置撤销挂钩的内容:
(value) => {
setContent(value)
},
- 我们在
200
毫秒后触发setContent
功能:
200
)
- 接下来,我们必须定义一个效果挂钩,它将在撤销状态更改时触发。在此效果挂钩中,我们取消当前去抖动,并将
content
值设置为当前present
值:
useEffect(() => {
cancelDebounce()
setInput(undoContent.present)
}, [undoContent])
- 最后,我们调整
handleContent
功能以触发setInput
功能和setDebounce
功能:
function handleContent (e)
const { value } = e.target
setInput(value)
setDebounce(value)
}
因此,我们立即设置输入value
,但我们还没有将任何内容存储到撤销历史记录中。在解除抖动回调触发后(在200
ms 之后),我们将当前值存储到撤销历史记录中。每当“撤消”状态更新时,例如,当我们按下“撤消/重做”按钮时,我们将取消当前的取消抖动,以避免在撤消/重做后覆盖该值。然后,我们将content
值设置为撤销挂钩的新present
值。
如果我们现在在编辑器中键入一些文本,我们可以看到“撤消”按钮只会在一段时间后激活。然后看起来是这样的:
Undo button activated after typing some text
如果我们现在按下撤销按钮,我们可以看到我们不会一个字符一个字符地撤销,而是一次撤销更多的文本。例如,如果按“撤消”三次,将得到以下结果:
Going back in time using the Undo button
正如我们所看到的,撤消/重做和取消抖动现在工作得非常好!
示例代码
示例代码可在Chapter08/chapter8_5
文件夹中找到。
只需运行npm install
安装所有依赖项,并启动npm start
应用,然后在浏览器中访问http://localhost:3000
(如果它没有自动打开)。
寻找其他挂钩
社区还提供了许多其他挂钩。您可以在以下页面上找到各种挂钩的可搜索列表:https://nikgraf.github.io/react-hooks/.
为了让您了解还有哪些其他挂钩,社区挂钩提供了以下特性。我们现在列出社区提供的几个更有趣的挂钩。当然,还有更多的挂钩可以找到:
use-events
(https://github.com/sandiiarov/use-events :已经变成挂钩的各种 JavaScript 事件,如鼠标位置、触摸事件、点击外部等。react-apollo-hooks
(https://github.com/trojanowski/react-apollo-hooks :使用 Apollo 客户端(缓存GraphQL客户端)和 React 挂钩。react-use
(https://github.com/streamich/react-use :处理传感器(useBattery
、useIdle
、useGeolocation
等)、UI(useAudio
、useCss
、useFullscreen
等)、动画(useSpring
、useTween
、useRaf
等)和副作用(useAsync
、useDebounce
、useFavicon
等)的各种挂钩。react-use-clipboard
(https://github.com/danoc/react-use-clipboard :用于复制文本的剪贴板功能。
总结
在本章中,我们首先了解了react-hookedup
图书馆。我们在博客应用中使用这个库简化了挂钩的输入处理。然后,我们研究了如何使用挂钩实现各种 React 生命周期。接下来,我们介绍了各种有用的挂钩,如usePrevious
挂钩、间隔/超时挂钩、在线状态挂钩、数据操作挂钩以及焦点和悬停挂钩。之后,我们通过不在手机上渲染某些组件,讨论了使用挂钩的响应性设计。最后,我们学习了如何使用挂钩实现撤销/重做功能和解除绑定。
使用社区挂钩是一项非常重要的技能,因为 React 只提供了一些现成的挂钩。在实际应用中,您可能会使用社区提供的来自各种库和框架的许多挂钩。我们还了解了各种社区挂钩,它们将使我们在编写 React 应用时的生活变得更加轻松。
在下一章中,我们将深入了解挂钩的规则,在我们开始编写自己的挂钩之前,必须了解这些规则。
问题
为了重述我们在本章学到的知识,请尝试回答以下问题:
- 我们可以使用哪个挂钩来简化输入字段处理?
- 如何使用效果挂钩实现
componentDidMount
和componentWillUnmount
生命周期? -
我们如何使用挂钩来获取
this.setState()
的行为? -
为什么我们应该使用计时器挂钩而不是直接调用
setTimeout
和setInterval
? - 我们可以使用哪些挂钩来简化处理公共数据结构?
- 什么时候我们应该使用带有挂钩的响应式设计,而不是简单地使用 CSS 媒体查询?
- 我们可以使用哪个挂钩实现撤销/重做功能?
- 什么是去 Bouncing?我们为什么要这样做?
- 我们可以用哪种挂钩去抖动?
进一步阅读
如果您对我们在本章中所学概念的更多信息感兴趣,请阅读以下阅读材料:
react-hookedup
图书馆文献:https://github.com/zakariaharti/react-hookedupwindow-size
图书馆文献:https://github.com/rehooks/window-sizeuse-undo
图书馆文献:https://github.com/xxhomey19/use-undouse-debounce
图书馆文献:https://github.com/xnimorz/use-debounce.- React 挂钩收集:https://nikgraf.github.io/react-hooks/
- 学习 Redux由Packt出版的书籍,了解更多关于撤销/重做功能的深入信息:https://www.packtpub.com/web-development/learning-redux
版权属于:月萌API www.moonapi.com,转载请注明出处