React Hooks 入门(2019)

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

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

图片描述

主要内容

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

一、你将学到什么

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

. 二、要求

学习如下内容,你应该基本了解设计模式

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

三、项目设置

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

npx create-react-app exploring-hooks

四、一开始有 setState

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

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

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

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

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

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>
  }
}

五、更新 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>
}

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

六、开始时有 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>
    )
  }}
/>

七、使用 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
//

八、可使用 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 钩子的诞生是有缘由的:在组件之间共享逻辑,咱们将在下一节中看到一个例子。

九、第一个自定义 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>
  )
}

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

十、我可使用 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
}

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

十一、结语

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 中表达组件:

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

十二、附录

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

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

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

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

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

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

相关文章
相关标签/搜索