备注:为了保证的可读性,本文采用意译而非直译。
在这个 React钩子 教程中,你将学习如何使用 React钩子,它们是什么,以及我们为什么这样做!
主要内容
- 你将学到什么
- 要求
- 项目设置
- 一开始有 setState
- 更新 React 中的状态,没有 setState
- 开始时有 componentDidMount(和 render props)
- 使用 useEffect 获取数据
- 可以使用 React 钩子渲染吗?
- 第一个自定义 React 钩子
- 可以使用 async / await 和 useEffect 吗?
- 结语
- 附录
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。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。