b

自定义 hooks

这一章节的练习与前几章节有点不同。 前一章节的练习和这一章节的练习都是关于这一章节提出的理论.。

本章节还包含一系列练习,其中我们从第4章节和第5章节修改 Bloglist 应用,以演练和应用我们所学到的技能。

Hooks

React 提供了10种不同的内置Hook,其中最受欢迎的是我们已经广泛使用的useStateuseEffect Hook。

第5章中,我们使用了useImperativeHandle-hook,它允许组件为其他组件提供其功能。

在过去的一年里,许多 React 库已经开始提供基于 hook 的 api。正如第6章所讲的。

我们使用 react-redux 库中的useSelectoruseDispatchHook来共享我们对组件的 redux-store 和 dispatch 函数。 Redux 的基于Hook的 api 比旧的、仍然可用的connect-api 更易于使用。

我们在上一章节中介绍的React-router的 api 也部分基于hook。 它的Hook可以用来访问 url 参数和历史对象,这允许以编程方式操作浏览器的 url。

正如在第1章中提到的,Hook不是正常的函数,在使用这些函数时,我们必须遵守某些规则或限制。 让我们回顾一下使用Hook的规则,一字不差地从官方的 React 文档中复制下来:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function.

不要在循环、条件或嵌套函数中调用 Hooks。取而代之的是,始终在 React 函数的顶层使用 Hooks。

Don’t call Hooks from regular JavaScript functions. Instead, you can:

不要从常规的 JavaScript 函数中调用 Hooks,取而代之的是,你可以:

  • Call Hooks from React function components.
  • 从 React 函数组件调用Hook。
  • Call Hooks from custom Hooks
  • 从自定义Hook调用Hook

有一个现有的ESlint规则可以用来验证应用是否正确地使用Hook。

Create-react-app 已经配置好了规则eslint-plugin-react-hooks ,如果Hook被非法使用就会产生警告:

fullstack content

Custom hooks

【自定义Hook】

React 提供了创建我们自己的自定义Hook的选项。 根据 React,自定义Hook的主要目的是促进组件中使用的逻辑的重用。

构建自己的 hook 可以让您将组件逻辑提取到可重用的函数中

自定义Hook是常规的 JavaScript 函数,可以使用任何其他Hook,只要它们遵循hook 的规则。 此外,自定义Hook的名称必须以单词 use 开头。

我们在第1章中实现了一个计数器应用,它的值可以递增、递减或重置。 应用代码如下:

import React, { useState } from 'react'
const App = (props) => {
  const [counter, setCounter] = useState(0)

  return (
    <div>
      <div>{counter}</div>
      <button onClick={() => setCounter(counter + 1)}>
        plus
      </button>
      <button onClick={() => setCounter(counter - 1)}>
        minus
      </button>      
      <button onClick={() => setCounter(0)}>
        zero
      </button>
    </div>
  )
}

让我们将计数器逻辑提取到它自己的自定义Hook中,Hook的代码如下:

const useCounter = () => {
  const [value, setValue] = useState(0)

  const increase = () => {
    setValue(value + 1)
  }

  const decrease = () => {
    setValue(value - 1)
  }

  const zero = () => {
    setValue(0)
  }

  return {
    value, 
    increase,
    decrease,
    zero
  }
}

我们的自定义Hook在内部使用 useState Hook来创建自己的状态。 Hook返回一个对象,其属性包括计数器的值以及操作值的函数。

React组件可以使用如下所示的Hook:

const App = (props) => {
  const counter = useCounter()

  return (
    <div>
      <div>{counter.value}</div>
      <button onClick={counter.increase}>
        plus
      </button>
      <button onClick={counter.decrease}>
        minus
      </button>      
      <button onClick={counter.zero}>
        zero
      </button>
    </div>
  )
}

通过这样做,我们可以将 App 组件的状态及其操作完全提取到 useCounter Hook中。 管理计数器状态和逻辑现在是自定义Hook的责任。

同样的Hook可以在记录左右按钮点击次数的应用中重用:

const App = () => {
  const left = useCounter()
  const right = useCounter()

  return (
    <div>
      {left.value}
      <button onClick={left.increase}>
        left
      </button>
      <button onClick={right.increase}>
        right
      </button>
      {right.value}
    </div>
  )
}

应用创建了两个完全独立的计数器。 第一个分配给左边的变量,另一个分配给右边的变量。

在 React 中处理表单有点棘手。 下面的应用向用户提供一个表单,要求用户输入他们的姓名、生日和身高:

const App = () => {
  const [name, setName] = useState('')
  const [born, setBorn] = useState('')
  const [height, setHeight] = useState('')

  return (
    <div>
      <form>
        name: 
        <input
          type='text'
          value={name}
          onChange={(event) => setName(event.target.value)} 
        /> 
        <br/> 
        birthdate:
        <input
          type='date'
          value={born}
          onChange={(event) => setBorn(event.target.value)}
        />
        <br /> 
        height:
        <input
          type='number'
          value={height}
          onChange={(event) => setHeight(event.target.value)}
        />
      </form>
      <div>
        {name} {born} {height} 
      </div>
    </div>
  )
}

形体的每个字段都有自己的状态。 为了使表单的状态与用户提供的数据保持同步,我们必须为每个input 元素注册一个适当的onChange 处理程序。

让我们定义我们自己的定制 useField hook,它简化了表单的状态管理:

const useField = (type) => {
  const [value, setValue] = useState('')

  const onChange = (event) => {
    setValue(event.target.value)
  }

  return {
    type,
    value,
    onChange
  }
}

Hook函数接收input字段的类型作为参数。 函数返回input 所需的所有属性: 它的类型、值和 onChange 处理程序。

Hook可以用如下方式使用:

const App = () => {
  const name = useField('text')
  // ...

  return (
    <div>
      <form>
        <input
          type={name.type}
          value={name.value}
          onChange={name.onChange} 
        /> 
        // ...
      </form>
    </div>
  )
}

Spread attributes

【展开属性】

我们可以进一步简化事情。 因为 name 对象具有input 元素期望作为props接收的所有属性,所以我们可以使用展开语法如下面的方式将props传递给元素:

<input {...name} /> 

正如 React 文档中的示例所述,如下两种方法为组件传递props可以得到完全相同的结果:

<Greeting firstName='Arto' lastName='Hellas' />

const person = {
  firstName: 'Arto',
  lastName: 'Hellas'
}

<Greeting {...person} />

应用将简化为如下格式:

const App = () => {
  const name = useField('text')
  const born = useField('date')
  const height = useField('number')

  return (
    <div>
      <form>
        name: 
        <input  {...name} /> 
        <br/> 
        birthdate:
        <input {...born} />
        <br /> 
        height:
        <input {...height} />
      </form>
      <div>
        {name.value} {born.value} {height.value}
      </div>
    </div>
  )
}

当与同步表单状态有关的恼人的细节被封装在自定义Hook中时,表单的处理就大大简化了。

自定义Hook显然不仅是一种可重用的工具,它们还为将代码划分为更小的模块化部分提供了一种更好的方式。

More about hooks

【关于Hook更多知识】

互联网上开始充斥着越来越多关于Hook的有用资料。 如下是值得一查的资料来源: