在上一章中,我们使用 Navi 库实现了路由。我们从实现页面开始,然后定义路由和静态链接。最后,我们实现了动态链接并使用 Hook 访问路由信息。
在本章中,我们将了解 React 社区提供的各种 Hook。这些 Hook 可用于简化输入处理,并实现 React 生命周期,以简化从 React 类组件的迁移。此外,还有一些 Hook 可以实现各种行为,例如计时器、检查客户端是否在线、悬停和聚焦事件以及数据操作。最后,我们将学习响应式设计和使用 Hook 实现撤消/重做功能。
本章将介绍以下主题:
- 使用 Input Hook 简化输入处理
- 使用 Hook 实现 React 生命周期
- 学习各种有用的 Hook(
usePrevious
,计时器,在线,焦点,悬停和数据操作 Hook) - 使用 Hook 实现响应式设计
- 实现撤消/重做功能并使用 Hook 防抖
- 学习在哪里可以找到其他 Hook
技术要求
应该已经安装了相当新版本的 Node.js(vll.l2.0 或更高版本)。Node.js 的 npm
包管理器也需要安装。
本章的代码可以在 GitHub 存储库中找到:https://github.com/PacktPublishing/Learn-React-Hooks/tree/master/Chapter08。
观看以下视频,了解代码的实际应用:
请注意,强烈建议您自己编写代码。不要简单地运行已提供的代码示例。重要的是你自己编写代码,以便能够正确学习和理解。但是,如果遇到任何问题,始终可以参考代码示例。
现在,让我们从本章开始。
探索输入处理 Hook
处理 Hook 时,一个非常常见的用例是使用 State 和 Effect Hook 存储 input
字段的当前值。在本书中,我们已经多次这样做了。
useInput
Hook 通过提供一个处理 input
字段的 value
变量的单个 Hook,大大简化了这个用例。它的工作原理如下:
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
函数也是从 Hook 返回的:
const { clear } = useInput('')
调用 clear
函数会将 value
设置为空值,并清除 input
字段中的所有文本。
此外,Hook 提供了两种绑定 Input
字段的方法:
bindToInput
:使用e.target.value
作为onChange
函数的value
参数,将value
和onChange
props 绑定到input
字段。这在处理 HTMLInput
字段时很有用。bind
:将value
和onChange
props 绑定到input
字段,仅使用e
作为onChange
函数的值。这对于将值直接传递给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
函数。对于 切换按钮
,我们需要使用 {…bind}
的 props,因为我们在这里不处理输入事件,并且值直接传递给更改处理程序(而不是通过 e.target.value
)。
现在我们已经了解了 Input Hook,我们可以继续在我们的博客应用程序中实现它。
在博客应用中实现 Input Hook
现在我们已经了解了 Input Hook,以及它如何简化对 input
字段状态的处理,我们将在我们的博客应用程序中实现 Input Hook。
首先,我们必须在我们的博客应用项目中安装 react-hookedup
库:
npm install --save react-hookedup
我们现在将在以下组件中实现 Input Hook:
Login
组件Register
组件CreatPost
组件
让我们开始实现 Input Hook。
Login 组件
我们在 Login
组件中有两个 Input
字段:用户名和密码字段。我们现在将用 Input Hook 替换 State Hook。
现在让我们开始在 Login
组件中实现 Input Hook:
- 在
src/user/Login.js
文件的开头导入useInput
Hook:
import { useInput } from 'react-hookedup'
- 然后,我们删除以下
username
State Hook:
const [ username, setUsername ] = useState('')
它被替换为 Input Hook,如下所示:
const { value: username, bindToInput: bindUsername } = useInput('')`
由于我们使用了两个 Input Hook,为了避免名称冲突,我们在对象解构中使用重命名语法({ from: to })
将值键重命名为用户名,将 bindToInput 键重命名为 bindUsername。
- 我们还删除了以下
password
State Hook:
const [ password, setPassword ] = useState('')
它被替换为 Input Hook,如下所示:
const { value: password, bindToInput: bindPassword } = useInput('')
- 现在,我们可以删除以下处理函数:
function handleUsername(evt) {
setUsername(evt.target.value)
}
function handlePassword(evt) {
setPassword(evt.target.value)
}
- 最后,我们不是手动传递
onChange
处理程序,而是使用 Input Hook 中的绑定对象:
<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 Hook,而不是通用的 State Hook。我们也不必再为每个 Input
字段定义相同类型的处理函数。正如我们所看到的,使用社区 Hook 可以大大简化常见用例的实现,例如输入处理。我们现在将对 Register
组件重复相同的过程。
Register 组件
Register
组件的工作方式与 Login
组件类似。但是,它有三个 Input
字段:用户名、密码和重复密码。
现在让我们在 Register
组件中实现 Input Hook:
- 在 src/user/Register.js 文件的开头导入 useInput Hook:
import { useInput } from 'react-hookedup'
- 然后,我们删除以下 State Hook:
const [ username, setUsername ] = useState('')
const [ password, setPassword ] = useState('')
const [ passwordRepeat, setPasswordRepeat ] = useState('')
它们被替换为相应的 Input Hook:
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="registerpassword-repeat
/>
注册功能仍将以相同的方式工作,但现在使用 Input Hook。接下来是 CreatePost
组件,我们也将在其中实现 Input Hook。
CreatePost 组件
CreatePost
组件使用两个 Input
字段:一个用于 title
,一个用于 content
。我们将用 Input Hook 替换它们。
现在让我们在 CreatePost
组件中实现 Input Hook:
- 在
src/user/CreatePost.js
文件的开头导入useInput
Hook:
import { useInput } from 'react-hookedup'
- 然后,我们删除以下 State Hook:
const [ title, setTitle ] = useState('')
const [ content, setContent ] = useState('')
我们用相应的 Input Hook 替换它们:
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} />
创建文章功能也将以与 Input Hook 相同的方式工作。
示例代码
示例代码可以在 Chapter08/chapter8_1
文件夹中找到。
只需运行 npm install
即可安装所有依赖项,运行 npm start
启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
用 Hook 实现 React 的生命周期
正如我们在前面的章节中学到的,我们可以使用 useEffect
Hook 来模拟 React 的大部分生命周期方法。但是,如果你更喜欢直接处理 React 生命周期,而不是使用 Effect Hook,有一个名为 react-hookedup
的库,它提供了各种 Hook,包括各种 React 生命周期的 Hook。此外,该库提供了一个 Merge State Hook,其工作原理类似于 React 类组件中的 this.setState()
。
useOnMount Hook
useOnMount
Hook 与 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”到控制台。当组件(比如由于 prop 更改)重新渲染时,将不会再次调用它。
或者,我们可以只需使用带有空数组的 useEffect
Hook 作为第二个参数,它将具有相同的效果:
import React, { useEffect } from 'react'
export default function OnMountWithEffect() {
useEffect(() => console.log('mounted with effect'), [])
return <div>look at the console :)</div>
}
正如我们所看到的,使用带有空数组的 Effect Hook 作为第二个参数会导致与 useOnMount
Hook 或 componentDidMount
生命周期方法相同的行为。
useOnUnmount Hook
useOnUnmount
Hook 与 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>
}
前面的代码将在组件卸载时(在 React 组件从 DOM 中删除之前)输出 “unmounting” 到控制台。
如果你还记得 第 4 章使用 Reducer 和 Effect Hook,我们可以从 useEffect
Hook 返回一个清理函数,该函数将在组件卸载时调用。这意味着我们也可以使用 useEffect
实现 useOnMount
Hook,如下所示:
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 Hook 返回的清理函数,将空数组作为第二个参数,与 useOnUnmount
Hook 或 componentWillUnmount
生命周期方法具有相同的效果。
useLifecycleHook Hook
useLifecycleHook
Hook 将前两个 Hook 合二为一。我们可以将 useOnMount
和 useOnUnmount
Hook 组合如下:
import React from 'react'
import { useLifecycleHook } from 'react-hookedup'
export default function UseLifecycleHook() {
useLifecycleHook({
onMount: () => console.log('lifecycle mounted'),
onUnmount: () => console.log('lifecycle unmounting')
})
return <div>look at the console and click the button</div>
}
或者,我们可以分别使用两个 Hook:
import React from 'react'
import { useOnMount, useOnUnmount } from 'react-hookedup'
export default function UseLifecycleHookseparate() {
useOnMount(() => console.log('separate lifecycle mounted'))
useOnUnmount(() => console.log('separate lifecycle unmounting'))
return <div>look at the console and click the button</div>
}
但是,如果你有这种模式,我建议简单地使用 useEffect
Hook,如下所示:
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
,我们可以将整个效果放入单个函数中,然后简单地返回一个函数进行清理。当我们在接下来的章节中学习如何自定义 Hook 时,这种模式特别有用。
Effect 让我们以不同的方式思考 React 组件。我们根本不需要考虑组件的生命周期。相反,我们考虑的是 effect、依赖关系和 effect 的清理。
useMergeState Hook
useMergestate
Hook 的工作方式类似于 useState
Hook。但是,它不会替换当前状态,而是将当前状态与新状态合并,就像 this.setState()
在 React 类组件中工作一样。
Merge State Hook 返回以下对象:
state
:当前状态setState
:将当前状态与给定状态对象合并的函数
例如,让我们考虑以下组件:
- 首先,我们导入
useState
Hook:
import React, { useState } from 'react'
- 然后,我们定义应用程序组件和一个 State Hook,其中包含一个包含
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
值和一个“+l”按钮,以便将counter
值增加1
。如果state.loaded
为false
或undefined
,则该按钮将被禁用:
return (
<div>
Count: {state.counter}
<button onClick={handleClick} disabled=
{!state.loaded}>+1</button>
</div>
)
}
如我们所见,我们有一个简单的计数器应用程序,显示当前计数和一个 “+l” 按钮。仅当 loaded
值设置为 true
时,才会启用该按钮。
如果我们现在单击“+l” 按钮,counter
将从 0
增加到 1
,但该按钮将被禁用,因为我们用新的 state
对象覆盖了当前的 state
对象。
要解决此问题,我们必须调整 handleClick
函数,如下所示:
function handleClick () {
setState({ ...state, counter: state.counter + 1 })
}
或者,我们可以使用 useMergestate
Hook 来完全避免这个问题,并获得与我们在类组件中使用 this.setState()
相同的行为:
import React from 'react'
import { useMergeState } from 'react-hookedup'
export default function UseMergeState() {
const { state, setState } = useMergeState({ loaded: true, counter: 0 })
正如我们所看到的,通过使用 useMergestate
Hook,我们可以重现与类组件中的 this.setState()
相同的行为。因此,我们不再需要使用扩展运算符语法。然而,通常情况下,最好简单地使用多个 State Hook 或一个 Reducer Hook。
示例代码
示例代码可以在 Chapter08/chapter8_2
文件夹中找到。
只需运行 npm install
即可安装所有依赖项,运行 npm start
启动应用程序,然后在您的浏览器中访问 http://localhost:3000
(如果它没有自动打开)。
(节选)
评论 (0)