备注:为了保证的可读性,本文采用意译而非直译。

在这个 React钩子 教程中,你将学习如何使用 React钩子,它们是什么,以及我们为什么这样做!

图片描述

主要内容

  1. 你将学到什么
  2. 要求
  3. 项目设置
  4. 一开始有 setState
  5. 更新 React 中的状态,没有 setState
  6. 开始时有 componentDidMount(和 render props)
  7. 使用 useEffect 获取数据
  8. 可以使用 React 钩子渲染吗?
  9. 第一个自定义 React 钩子
  10. 可以使用 async / await 和 useEffect 吗?
  11. 结语
  12. 附录

1、你将学到什么

  • 如何使用 React 钩子
  • 如何在 React 类组件中实现相同的逻辑

. 2、要求

学习以下内容,你应该基本了解

  • ES6(箭头函数、解构、类)
  • React

3、项目设置

这里默认你已经配置好 React 开发环境,我们试着安装

npx create-react-app exploring-hooks

4、一开始有 setState

假设你已经在你的项目中使用了 React,让我们快速回顾一下:

React 是一个用于构建用户界面的库,其优点之一是库本身会向开发人员强加严格的数据流。你还记得 jQuery 吗?使用 jQuery,不太可能清楚地构建项目,更不用说定义数据应如何在 UI 中展示。这很难跟踪哪些功能正在改变哪个 UI 视图。

这同样适用于普通的 JavaScript:即使有自我解释和实践,也可以提出一个结构良好的项目(考虑模块模式),好的跟踪状态和功能之间的相互作用(请参阅 Redux )。

React 在某种程度上缓解了什么问题呢:通过强制执行清晰的结构(容器 和 功能组件) 和 严格的数据流(组件对状态 和 道具更改 做出反应),现在比以前更容易创建合理的 UI 视图逻辑。

因此,React 理论上是,一个 UI 可以 “ 响应 ” 以响应状态变化。到目前为止,表达这种流程的基本形式是 ES6 课程。考虑以下示例:从React.Component 扩展的 ES6 类,具有内部状态:

import React, { Component } from "react"
export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" }
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" }
    });
  }
  render() {
    const { buttonText } = this.state
    return <button onClick = {this.handleClick}>{buttonText}</button>
  }
}

从上面的代码中可以看出,当单击按钮时,组件的内部状态会被 setState 改变。按钮依次响应并更改获取更新的文本。

由于 类字段,删除构造函数可以表示更简洁的组件版本:

import React, { Component } from "react"
export default class Button extends Component {
  state = { buttonText: "Click me, please" }
  handleClick = () => {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" }
    })
  };
  render() {
    const { buttonText } = this.state
    return <button onClick={this.handleClick}>{buttonText}</button>
  }
}

5、更新 React 中的状态,没有 setState

那么我们现在有什么选择来管理 React 中的内部状态,是因为不再需要 setState 和 类 了吗?
第一个也是最重要的 React 钩子:useState。useState 是 react 暴露的函数。将在文件顶部引入它:

import React, { useState } from "react"

通过在代码中导入 useState,在 React 组件中保存某种状态的意图。更重要的是,React 组件不再是 ES6 类。它可以是一个纯粹而简单的 JavaScript 函数。
导入 useState 后,将选择一个包含两个变量的数组,这些变量不在 useState 中,代码应该放在 React 组件中:

const [buttonText, setButtonText] = useState("Click me, please")

如果对这种语法感到困惑,其实这是 ES6 解构。上面的名字可以是你想要的任何东西,对 React 来说无关紧要。

所以前面的例子,一个带有钩子的按钮组件会变成:

import React, { useState } from "react"
export default function Button() {
  const [buttonText, setButtonText] = useState("Click me, please")
  return (
    <button onClick={() => setButtonText("Thanks, been clicked!")}>
      {buttonText}
    </button>
  )
}

要在 onClick 处理程序中调用 setButtonText 状态更新程序,可以使用箭头函数。但如果你更喜欢使用常见的功能,你可以:

import React, { useState } from "react"
export default function Button() {
  const [buttonText, setButtonText] = useState("Click me, please")
  function handleClick() {
    return setButtonText("Thanks, been clicked!")
  }
  return <button onClick={handleClick}>{buttonText}</button>
}

除了有特殊要求外,我更喜欢常规功能而不是箭头函数的方式。可读性提高了很多。此外,当我编写代码时,我总是认为下一个开发人员将保留代码。这里的代码应该是可读更强的。

6、开始时有 componentDidMount(和 render props)

在 React 中获取数据!你还记得 componentDidMount 吗?你可以在 componentDidMount 中点击提取(url)。以下是如何从 API 获取数据以及如何展示为一个列表:

import React, { Component } from "react"
export default class DataLoader extends Component {
  state = { data: [] }
  componentDidMount() {
    fetch("http://localhost:3001/links/")
      .then(response => response.json())
      .then(data =>
        this.setState(() => {
          return { data }
        })
      )
  }
  render() {
    return (
      <div>
        <ul>
          {this.state.data.map(el => (
            <li key={el.id}>{el.title}</li>
          ))}
        </ul>
      </div>
    )
  }
}

你甚至可以在 componentDidMount 中使用 async / await,但有一些注意事项。但是在项目中的大多数的异步逻辑都存在 React 组件之外。上面的代码还有一些缺点。
渲染列表他是固定的,但使用渲染道具,我们可以轻松地将子项作为函数传递。重构的组件如下所示:

import React, { Component } from "react"
export default class DataLoader extends Component {
  state = { data: [] }
  componentDidMount() {
    fetch("http://localhost:3001/links/")
      .then(response => response.json())
      .then(data =>
        this.setState(() => {
          return { data }
        })
      )
  }
  render() {
    return this.props.render(this.state.data)
  }
}

并且你将通过从外部提供渲染道具来使用当前该组件:

<DataLoader
  render={data => {
    return (
      <div>
        <ul>
          {data.map(el => (
            <li key={el.id}>{el.title}</li>
          ))}
        </ul>
      </div>
    )
  }}
/>

7、使用 useEffect 获取数据

我认为使用 React 钩子获取数据不应该与 useState 有什么不同。工作中看文档给了我一个提示:useEffect 可能是正确的一个工具。
当看到:“ useEffect 与 React 类中的 componentDidMount,componentDidUpdate 和 componentWillUnmount 具有相同的用途时,统一为单个 API ”。
而且我可以使用 setData(从 useState 中提取的任意函数)代替调用 this.setState:

import React, { useState, useEffect } from "react"
export default function DataLoader() {
  const [data, setData] = useState([])
  useEffect(() => {
    fetch("http://localhost:3001/links/")
      .then(response => response.json())
      .then(data => setData(data));
  });
  return (
    <div>
      <ul>
        {data.map(el => (
          <li key={el.id}>{el.title}</li>
        ))}
      </ul>
    </div>
  )
}

在这一点上,我想“ 可能出现什么问题? ”,这是在控制台中看到的:

图片描述

这显然是我的错,因为我已经知道发生了什么:
“ useEffect 与 componentDidMount,componentDidUpdate 和 componentWillUnmount 具有相同的用途
componentDidUpdate!componentDidUpdate 是一个生命周期方法,每当组件获得新的道具或状态发生变化时运行。
这就是诀窍。如果你像我一样调用 useEffect,你会看到无限循环。要解决这个“ bug ”,你需要传递一个空数组作为 useEffect 的第二个参数:

//
  useEffect(() => {
    fetch("http://localhost:3001/links/")
      .then(response => response.json())
      .then(data => setData(data))
  }, []); // << super important array
//

8、可以使用 React 钩子渲染吗?

当然!但是这样做没有意义。我们的 DataLoader 组件将会变为:

import React, { useState, useEffect } from "react"
export default function DataLoader(props) {
  const [data, setData] = useState([])
  useEffect(() => {
    fetch("http://localhost:3001/links/")
      .then(response => response.json())
      .then(data => setData(data))
  }, []); // << super important array
  return props.render(data)
}

并且你将通过从外部提供渲染道具来消耗组件,就像我们在前面的示例中所做的那样。
但同样,这种重构没有意义,因为 React 钩子的诞生是有原因的:在组件之间共享逻辑,我们将在下一节中看到一个例子。

9、第一个自定义 React 钩子

我们可以将我们的逻辑封装在 React 钩子中,然后在我们需要引入该钩子时,而不是 HO C和 渲染道具。在我们的示例中,我们可以创建用于获取数据的自定义挂钩。
根据 React 文档,自定义钩子是一个 JavaScript 函数,其名称以“ use ”开头。比说起来容易。让我们创建一个 useFetch :

// useFetch.js
import { useState, useEffect } from "react"
export default function useFetch(url) {
  const [data, setData] = useState([])
  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => setData(data))
  }, []);
  return data
}

这就是你如何使用自定义钩子:

import React from "react"
import useFetch from "./useFetch"
export default function DataLoader(props) {
  const data = useFetch("http://localhost:3001/links/")
  return (
    <div>
      <ul>
        {data.map(el => (
          <li key={el.id}>{el.title}</li>
        ))}
      </ul>
    </div>
  )
}

这就是使钩子如此吸引人的原因:最后我们有一个 有趣的、标准化的、干净 的方式来封装和共享逻辑。

10、我可以使用 useEffect 的 async / await 吗?

当你在使用 useEffect 时想在钩子里尝试 async / await。让我们看看我们的自定义:

// useFetch.js
import { useState, useEffect } from "react"
export default function useFetch(url) {
  const [data, setData] = useState([])
  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => setData(data))
  }, [])
  return data
}

对于 重构异步 / 等待 最自然的事情你可能会:

// useFetch.js
import { useState, useEffect } from "react"
export default function useFetch(url) {
  const [data, setData] = useState([])
  useEffect(async () => {
    const response = await fetch(url)
    const data = await response.json()
    setData(data)
  }, [])
  return data
}

然后打开控制台,React 正在提示着:
图片描述

“警告:不能返回任何内容。

事实证明不能从 useEffect 返回一个 Promise。JavaScript 异步函数总是返回一个 promise,而 useEffect 应该只返回另一个函数,该函数用于清除效果。也就是说,如果你要在 useEffect 中启动 setInterval,你将返回一个函数(我们有一个闭包)来清除间隔。

因此,为了使 React,我们可以像这样重写我们的异步逻辑:

// useFetch.js
import { useState, useEffect } from "react"
export default function useFetch(url) {
  const [data, setData] = useState([])
  async function getData() {
    const response = await fetch(url)
    const data = await response.json()
    setData(data)
  }
  useEffect(() => {
    getData()
  }, [])
  return data
}

这里,你的自定义钩子将再次运行。

11、结语

React hooks 是库的一个很好补充。他们于 2018年10月 作为 RFC 发布,很快就赶上了 React 16.8。将 React 钩子想象为生活在 React 组件之外的封装状态。

React 钩子使渲染道具和 HOC 几乎过时,并为共享逻辑提供了更好的人体工程学。使用 React 钩子,你可以在 React 组件之间重用常见的逻辑片段。

React 附带一堆预定义的钩子。最重要的是 useState 和 useEffect。useState 可以在 React 组件中使用本地状态,而无需使用 ES6 类。

useEffec t替换了提供统一 API:componentDidMount,componentDidUpdate 和 componentWillUnmount。还有很多其他的钩子,我建议阅读官方文档以了解更多信息。

很容易预见React的发展方向:功能组件遍布各处!但即便如此,我们还是有三种方法可以在 React 中表达组件:

  • 功能组件
  • 类组件
  • 带有挂钩的功能组件

12、附录

在文章的开头我说:“ 使用 jQuery,几乎不可能清楚地构建项目,更不用说定义数据应如何在 UI 中展示”。

但是你可能不需要 React 来构建用户界面。有时我使用 vanilla JavaScript 构建项目。当我不确定该项目将采用什么形状时,我用来创建一个没有任何 JavaScript 库的简单原型。

在这些项目中,我依靠模块的方式来编写代码。

能够正确组织和编写你的代码,即使使用 vanilla JavaScript 也是每个 JavaScript 开发人员的宝贵资产。为了更多地了解 JavaScript 中的模块方式,我建议你由 Todd Motto 掌握模块方式和 Addy Osmani 的 JavaScript 设计模式。

另一方面,跟踪 UI 中的状态变化确实很难。对于这种工作,我最喜欢的是 Redux,甚至可以使用 vanilla JavaScript。

备注:原文链接地址 https://www.valentinog.com/bl...


派大星的海洋裤
24 声望1 粉丝

技术职业本身是一个需要靠不断的学习来维持竞争力的职业,如果只是抱着学会一门手艺吃到老的心态做这行的话,可能会比你想象得更早被淘汰。