第 8 章 使用社区 Hook

第 8 章 使用社区 Hook

Flying
2021-03-28 / 0 评论 / 186 阅读 / 正在检测是否收录...

在上一章中,我们使用 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
观看以下视频,了解代码的实际应用:

http://bit.ly/2Mm9yoC

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

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

探索输入处理 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 参数,将 valueonChange props 绑定到 input 字段。这在处理 HTML Input 字段时很有用。
  • bind:将 valueonChange props 绑定到 input 字段,仅使用 e 作为 onChange 函数的值。这对于将值直接传递给 onChange 函数的 React 组件很有用。

bindbindToInput 对象可以与展开运算符一起使用,如下所示:

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} 属性来分配 valueonChange 函数。对于 切换按钮,我们需要使用 {…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:

  1. src/user/Login.js 文件的开头导入 useInput Hook:
import { useInput } from 'react-hookedup'
  1. 然后,我们删除以下 username State Hook:
const [ username, setUsername ] = useState('')

它被替换为 Input Hook,如下所示:

const { value: username, bindToInput: bindUsername } = useInput('')`
由于我们使用了两个 Input Hook,为了避免名称冲突,我们在对象解构中使用重命名语法({ from: to })将值键重命名为用户名,将 bindToInput 键重命名为 bindUsername。
  1. 我们还删除了以下 password State Hook:
const [ password, setPassword ] = useState('')

它被替换为 Input Hook,如下所示:

const { value: password, bindToInput: bindPassword } = useInput('')
  1. 现在,我们可以删除以下处理函数:
function handleUsername(evt) {
  setUsername(evt.target.value)
}

function handlePassword(evt) {
  setPassword(evt.target.value)
}
  1. 最后,我们不是手动传递 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:

  1. 在 src/user/Register.js 文件的开头导入 useInput Hook:
import { useInput } from 'react-hookedup'
  1. 然后,我们删除以下 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('')
  1. 同样,我们可以删除所有处理函数:
function handleUsername(evt) {
  setUsername(evt.target.value)
}

function handlePassword(evt) {
  setPassword(evt.target.value)
}

function handlePasswordRepeat(evt) {
  setPasswordRepeat(evt.target.value)
}
  1. 最后,我们将所有 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:

  1. src/user/CreatePost.js 文件的开头导入 useInput Hook:
import { useInput } from 'react-hookedup'
  1. 然后,我们删除以下 State Hook:
const [ title, setTitle ] = useState('')
const [ content, setContent ] = useState('')

我们用相应的 Input Hook 替换它们:

const { value: title, bindToInput: bindTitle } = useInput('')
const { value: content, bindToInput: bindContent } = useInput('')
  1. 同样,我们可以删除以下输入处理函数:
function handleTitle(evt) {
  setTitle(evt.target.value)
}

function handleContent(evt) {
  setContent(evt.target.value)
}
  1. 最后,我们将所有 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 合二为一。我们可以将 useOnMountuseOnUnmount 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:将当前状态与给定状态对象合并的函数

例如,让我们考虑以下组件:

  1. 首先,我们导入 useState Hook:
import React, { useState } from 'react'
  1. 然后,我们定义应用程序组件和一个 State Hook,其中包含一个包含 loaded 值和 counter 值的对象:
export default function Mergestate () {
const [ state, setState ] = useState({ loaded: true, counter: 0 })
  1. 接下来,我们定义一个 handleClick 函数,在其中设置新的 state,将当前的 counter 值增加 1
function handleClick () {
  setState({ counter: state.counter + 1 })
}
  1. 最后,我们渲染当前的 counter 值和一个“+l”按钮,以便将 counter 值增加 1。如果 state.loadedfalseundefined,则该按钮将被禁用:
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(如果它没有自动打开)。


(节选)

1

评论 (0)

取消