备注:为了保证的可读性,本文采用意译而非直译。react
在这个 React钩子 教程中,你将学习如何使用 React钩子,它们是什么,以及咱们为何这样作!json
学习如下内容,你应该基本了解设计模式
这里默认你已经配置好 React 开发环境,咱们试着安装数组
npx create-react-app exploring-hooks
假设你已经在你的项目中使用了 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 钩子: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> }
除了有特殊要求外,我更喜欢常规功能而不是箭头函数的方式。可读性提升了不少。此外,当我编写代码时,我老是认为下一个开发人员将保留代码。这里的代码应该是可读更强的。
在 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> ) }} />
我认为使用 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 //
固然!可是这样作没有意义。咱们的 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 钩子中,而后在咱们须要引入该钩子时,而不是 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。让咱们看看咱们的自定义:
// 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。