React Hooks
于 2018年10月的React Conf 中引入,作为在 React
函数组件中使用状态和生命周期的一种方法。虽然函数组件之前被称为 无状态组件(FSC) ,但是 React Hooks
的出现,使得这些函数组件可以使用状态。因此,现在许多人将它们视为功能组件。
在这篇文章中,我会解释这些 Hooks
背后的动机,React
会发生什么改变,为什么我们不应该恐慌,以及如何在函数式组件中使用常见的 React Hooks
,比如 state 和 生命周期。
为什么使用 React Hooks
React Hooks
是由 React
团队发明的,用于在函数组件中引入状态管理和生命周期方法。如果我们希望一个 React
函数组件可以拥有 状态管理和生命周期方法,我不需要再去将一个 React
函数组件重构成一个 React
类组件。React Hooks
让我们可以仅使用函数组件就可以完成一个 React 应用
。
不必要的组件重构
以前,只有 React
类组件可以使用 本地状态管理 和 生命周期方法 。后者对于在 React
类组件中引入副作用(如监听DOM事件,异步加载数据)至关重要。
import React from 'react';
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button
onClick={() =>
this.setState({ count: this.state.count + 1 })
}
>
Click me
</button>
</div>
);
}
}
export default Counter;
只有在您不需要状态或生命周期方法时,才会考虑使用 React
无状态组件(FSC)。而且因为 React
函数组件更轻便(更优雅),人们已经使用了大量的函数组件。每次,当这些 函数组件 需要状态或生命周期方法时,都需要将 React
函数组件升级成 React
类组件(反之亦然)。
import React, { useState } from 'react';
// how to use the state hook in a React function component
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
有了 Hooks
,就没有必要进行这种重构。状态或生命周期方法在 React
函数组件中变得可用。这也让 无状态组件
到 功能组件
的重塑变得切实可行。
副作用逻辑
在 React
类组件中,副作用主要在生命周期方法中引入 (例如 componentDidMount
,componentDidUpdate
,componentWillUnmount
) 。副作用可能是在 React
中获取数据,或 与 Browser API
进行交互 。通常这些副作用会伴随着设置和清理阶段。例如,如果您忘记了去删除监听器,就会遇到一些 React
性能问题 。
// side-effects in a React class component
class MyComponent extends Component {
// setup phase
componentDidMount() {
// add listener for feature 1
// add listener for feature 2
}
// clean up phase
componentWillUnmount() {
// remove listener for feature 1
// remove listener for feature 2
}
...
}
// side-effects in React function component with React Hooks
function MyComponent() {
useEffect(() => {
// add listener for feature 1 (setup)
// return function to remove listener for feature 1 (clean up)
});
useEffect(() => {
// add listener for feature 2 (setup)
// return function to remove listener for feature 2 (clean up)
});
...
}
现在,如果您在 React
类组件的生命周期方法中引入多个副作用,所有副作用将按生命周期方法分组,而不会按副作用的功能来分组。这就是 React Hooks
带来的改变,将每个副作用通过一个钩子函数进行封装,而每个钩子函数都会处理各自的副作用,并提供这个副作用的设置和清理。稍后您将在本篇教程中看到如何在 React Hook
中添加和删除监听器来实现这一点。
React
抽象地狱
在 React
中可以通过 高阶组件 和 Render Props 来实现抽象和可重用性。还有 React Context
及其Provider
和消费者组件 所提供的另一个层次的抽象。React
中的所有这些高级模式都使用了所谓的包装组件。对于正在创建更大的 React
应用程序的开发人员来说,以下组件的实现应该并不陌生。
import { compose } from 'recompose';
import { withRouter } from 'react-router-dom';
function App({ history, state, dispatch }) {
return (
<ThemeContext.Consumer>
{theme =>
<Content theme={theme}>
...
</Content>
}
</ThemeContext.Consumer>
);
}
export default compose(
withRouter,
withReducer(reducer, initialState)
)(App);
Sophie Alpert 将之称为 React
中的 “包装地狱” 。您不仅可以在组件实现时看到它,还可以在浏览器中检查组件时看到它。由于使用 Render Props
组件(包括 React's Context
提供的消费者组件)和高阶组件,很容易产生了几十个包装组件。由于所有抽象逻辑都被其他 React
组件所隐藏,我们的应用变成了一棵没有可读性的组件树🌲。而那些可见的组件也很难在浏览器的 DOM
中进行跟踪。那么,如果这些抽象的逻辑在函数组件中被一些封装好的副作用所代替,这些额外的组件将不再需要。
function App() {
const theme = useTheme();
const history = useRouter();
const [state, dispatch] = useReducer(reducer, initialState);
return (
<Content theme={theme}>
...
</Content>
);
}
export default App;
这就是 React Hooks
的魅力所在。所有副作用都可以直接在组件中使用,业务组件为了使用这些副作用,也不需要再引入其他组件所为容器。容器组件消失,逻辑只存在于作为函数的 React Hooks
中。
Andrew Clark 已经在他的鹅妹子嘤高阶组件库:recompose 中发表了一篇关于赞成 React Hooks
的声明。
混乱的 JavaScript
类
JavaScript
很好地混合了两个世界:面向对象编程(OOP) 和 函数式编程(FP)。React
将许多的开发者带到了这两个世界。一方面,React
(和 Redux
)向人们介绍了 函数编程(FP) 的功能组合,一些函数式编程的通用编程概念(例如高阶函数,JavaScript
内置方法,如 map
,reduce
,filter
),以及一些函数式编程的术语,如 不变性
和 副作用
。React
本身没有真正介绍这些东西,因为它们是语言或编程范式的特性,但它们在 React
中被大量使用,这使得每个 React
开发人员都会自动成为一个更好的 JavaScript
开发人员 。
另一方面,在 React
中,可以使用 JavaScript
类作为定义 React
组件的一种方法。类只是声明,而组件的实际用法是它的实例。它会创建一个类实例,而类实例的 this
对象可以用于调用类的方法(例如 setState
,forceUpdate
,以及其他自定义类方法)。但是,对于不是来自 OOP 背景的 React
初学者来说,学习曲线会更陡峭。这就是为什么类绑定,this
对象和继承可能令人困惑的原因。对于初学者来说,这一直是 React
最令人困惑的事情,我的 React书 中也只有几章在讲这方面的知识。
// I THOUGHT WE ARE USING A CLASS. WHY IS IT EXTENDING FROM SOMETHING?
class Counter extends Component {
// WAIT ... THIS WORKS???
state = { value: 0 };
// I THOUGH IT'S THIS WAY, BUT WHY DO I NEED PROPS HERE?
// constructor(props) {
// SUPER???
// super(props);
//
// this.state = {
// value: 0,
// };
// }
// WHY DO I HAVE TO USE AN ARROW FUNCTION???
onIncrement = () => {
this.setState(state => ({
value: state.value + 1
}));
};
// SHOULDN'T IT BE this.onDecrement = this.onDecrement.bind(this); in the constructor???
// WHAT'S this.onDecrement = this.onDecrement.bind(this); DOING ANYWAY?
onDecrement = () => {
this.setState(state => ({
value: state.value - 1
}));
};
render() {
return (
<div>
{this.state.value}
{/* WHY IS EVERYTHING AVAILABLE ON "THIS"??? */}
<button onClick={this.onIncrement}>+</button>
<button onClick={this.onDecrement}>-</button>
</div>
)
}
}
现在,有许多人在争论 React
不应该移除 JavaScript classes
, 尽管他们不理解这些类的概念。毕竟,这些概念都来自于语言自身。但是,引入 Hooks API
的假设之一就是让初学者在第一次编写 React
应用时可以不用使用类组件,从而提供更加平滑的学习曲线。
React Hooks
会发生什么变化?
每次引入新功能时,人们都会关注它。一些人对这一变化感到欣喜若狂,而另外一些人对这一变化感到担忧。我听说 React Hooks
最常见的问题是:
- 一切都变了!莫名的感到恐慌......
React
像Angular
一样变得臃肿!Hooks
没有用,classes
很好- 这是魔法!
让我在这里解决这些问题:
一切都在变化?
React Hooks
将改变我们将来编写 React
应用程序的方式。但是,目前没有任何变化。您仍然可以使用本地状态和生命周期方法编写类组件,并部署高级模式,例如高阶组件或 Render Props
组件。没有人会把这些知识从你身边带走。了解我如何将所有开源项目从旧版本升级到 React 16.6
。这些项目都没有问题。他们正在使用 HOC
,Render Props
,甚至是较为古老的 context API
(如果我错了,请纠正我)。这些年来我所学到的一切仍然有效。React
团队确保 React
保持向后兼容。它与 React 16.7
保持相同。
React
像 Angular
一样变得臃肿
React
作为一个第三方库,其 API
总是会给人一种短小精干的感觉 。现在是这样,将来也是如此。但是,考虑到几年前基于组件所构建的应用,在升级的时候不被其他更加先进的库所取代, React
引入了有利于旧 API
的变更。如果 React
是9012年发布的,也许他的功能只会保留函数组件和 Hooks
。但 React
几年前就已经发布,它在发布新的特性的同时,还需要兼容之前的版本。可能会在几年弃用类组件和其生命周期方法,转而使用 React
函数组件和 Hooks
,但目前,React
团队仍将 React
类组件保留在了他们的工具库中。毕竟, React
团队希望利用新的特性 Hooks
来陪伴 React
赢得一场马拉松,而不是一场短跑。显然,React Hooks
为 React
添加了另一个 API
,但它有利于在未来简化 React
的新 API
。我喜欢这种转变,而不是拥有 "React 2",让一切都不同。
Hooks
没有用,classes
很好 ?
想象一下,你将从零开始学习 React
,你会被介绍给 React with Hooks
。也许 create-react-app
不会以 React
类组件开始,而是使用 React
函数组件。您需要了解组件的所有内容都是 React Hooks
。它们可以管理状态和副作用,因此您只需要知道 state hook
和 effect hook
。这是 React
类组件之前为包含的一切。React
初学者学习 React
会更简单,而不需要 JavaScript
类(继承,this
,bindings
,super
,...)带来的所有其他开销。想象一下 React Hooks
作为一种编写 React
组件的新方法 - 这是一种全新的思维方式。我自己是一个持怀疑态度的人,但是一旦我用 React Hooks
写了几个更简单的场景,我确信这是最简单的 React
写作方式,同样也学习 React
最简单的方式。作为一个做了很多 React workshops
的人,我认为它消除了那些令 React
初学者所沮丧的 classes
。
这是魔法?
众所周知,React
是用 JavaScript
来实现的。当有人问我:“我为什么要学习 React
?”时,最好的解释就是:编写 React
应用程序会让你成为一个更好的 JavaScript
开发人员 。无论未来是否会使用新的库,每个人都可以在使用 React
的过程中,磨练他们的 JavaScript
技能和一些通用的编程技巧。这是 Redux
经常做的事情,同样也是 React
做的事情:流行,但是没有魔力,它只是普通的 JavaScript
。现在 React Hooks
出现了,在以前经常使用的纯函数组件中引入了一些有状态的东西,同时还引入了一些不容易让人接受的规则,这使得很多人都不明白底层发生了什么。但是这样考虑一下:在React
中的一个函数组件不仅仅是一个函数。您仍然必须将 React
作为库导入到源代码文件中。它对你的函数做了一些事情,使得函数在 React
的世界中变成了函数组件。此函数组件的实现始终是隐式的。如何在将 React Hooks
引入之前使用函数实现的函数组件呢?人们也接受了它,即使它有点像是一个魔法。现在,唯一改变的东西(也许它之前已经是这样)是这些函数组件带有一个额外的隐藏对象,可以跟踪的 Hooks
。引用 [Dan Abramov
](https://twitter.com/dan_abramov) 在他的关于 Hooks
文章的话:“也许你想知道 React
在哪里为 Hooks
保持状态。答案是它保存在 React
为类保持状态的完全相同的位置。无论你如何定义你的组件,React
都会在内部维护一个更新队列”。
最后,试一下这种思考方式
基于组件的解决方案(如 Angular
,Vue
和 React
)正在推动每个版本的 Web
开发的界限。它们建立在二十多年前发明的技术之上。他们的出现是为了让2018年的开发者更加轻松,而不是1998。他们疯狂地优化自身以满足现在和现在的需求。我们正在使用组件而不是 HTML模板
构建 Web
应用程序。虽然还没有实现,但我想象一个未来,我们坐在一起,为浏览器发明一个基于组件的标准。Angular
,Vue
和 React
只是这一运动的先锋。
在下文中,我想通过示例深入介绍一些受欢迎的 React Hooks
,以帮助您加速了解这一变化。所有示例都可以在此GitHub存储库中找到。
React useState Hook
上文中,您已经在一个典型的计数器示例的代码片段中看到过 useState Hook
。它用于管理函数组件中的本地状态。让我们在一个更详细的例子中使用 Hook
,我们将管理一系列项目。在我的另一篇文章中,您可以了解有关在React中将数组作为状态进行管理的更多信息,但这次我们使用 React Hook
进行操作。让我们开始吧:
import React, { useState } from 'react';
const INITIAL_LIST = [
{
id: '0',
title: 'React with RxJS for State Management Tutorial',
url:
'https://www.robinwieruch.de/react-rxjs-state-management-tutorial/',
},
{
id: '1',
title: 'A complete React with Apollo and GraphQL Tutorial',
url: 'https://www.robinwieruch.de/react-graphql-apollo-tutorial',
},
];
function App() {
const [list, setList] = useState(INITIAL_LIST);
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
export default App;
useState Hook
接受一个初始状态作为参数,并通过使用数组解构返回两个可以命名的变量,您可以为它们取需要的名字。第一个变量是实际状态,而第二个变量是一个可以设置并返回新状态的函数。
这个示例的下一步是从列表中删除子元素。为了实现它,我们为列表中的每个子元素都设置了一个可单击按钮。单击处理程序会使用一个内联函数实现,因为稍后会在内联函数中使用 list
以及 setList
。 因此,您不需要将这些变量传递给处理程序,因为它们已经可以从组件的外部作用域中获得。
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem() {
// remove item from "list"
// set the new list in state with "setList"
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={onRemoveItem}>
Remove
</button>
</li>
))}
</ul>
);
}
因为一些原因,我们需要从列表中删除的子元素。使用高阶函数,我们可以将子元素的标识符传递给处理函数。否则,我们将无法从列表中删除的这些子元素。
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem(id) {
// remove item from "list"
// set the new list in state with "setList"
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}
最后,使用数组的内置方法过滤列表,删除包含标识符的子元素。它返回一个新列表,用于设置列表的新状态。
function App() {
const [list, setList] = useState(INITIAL_LIST);
function onRemoveItem(id) {
const newList = list.filter(item => item.id !== id);
setList(newList);
}
return (
<ul>
{list.map(item => (
<li key={item.id}>
<a href={item.url}>{item.title}</a>
<button type="button" onClick={() => onRemoveItem(item.id)}>
Remove
</button>
</li>
))}
</ul>
);
}
此时应该已经完成了。您可以根据传递给处理程序的标识符从列表中删除子元素。然后,处理函数过滤列表并使用 setList
函数设置列表的新状态。
useState Hook
为您提供了在函数组件中管理状态所需的一切:初始状态,最新状态和状态更新功能。其他一切都是 JavaScript
。此外,您不需要像以前一样在类组件中使用浅合并来保持 state
的更新。相反,您使用 useState
封装一个域(例如列表),但如果您需要另一个状态(例如计数器),则只需使用另一个 useState
封装此域。您可以在 React
的文档中阅读有关 useState Hook
的更多信息。
React useEffect Hook
让我们转到下一个名为 useEffect
的 Hook
。如前所述,功能组件应该能够使用 Hook
管理状态和副作用。上面我们已经使用 useState Hook
展示了管理状态。现在将 useEffect Hook
用于副作用,这些副作用通常用于与 Browser
/ DOM API
或外部 API
(如数据获取)的交互。让我们看一下如何通过实现一个简单的秒表,将 useEffect Hook
和 Browser API
相结合。您可以在此 GitHub
仓库中查看使用 React
类组件需要如何完成。
import React, { useState } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
return (
<div>
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
现在还没有秒表。但现在至少有条件渲染显示 “开始” 或 “停止” 按钮。并且由 useState hook
来管理布尔标志的状态。
接下来让我们加入副作用:用 useEffect
来注册一个 interval
定时器。定时器函数每秒会向您的浏览器控制台发出一条记录。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
setInterval(() => console.log('tick'), 1000);
});
return (
<div>
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
为了在组件卸载的时候移除定时器(以及每次渲染更新之后),您可以在 useEffect
中返回一个函数,以便清理任何内容。对于上面的例子,当组件不再存在时,不应该留下任何内存泄漏。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
});
...
}
export default App;
现在,您需要在安装组件时设置副作用,并在卸载组件时清除副作用。如果您需要记录函数的调用次数,您会看到每次组件状态发生变化时它都会设置一个新的定时器(例如,单击“开始”/“停止”按钮)。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
console.log('effect runs');
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
});
...
}
export default App;
为了仅在组件的 mount
和 unmount
时响应 ,可以将空数组作为第二个参数传递给它。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
}, []);
...
}
export default App;
但是,由于在每次渲染之后都会清除定时器,我们也需要在我们的业务周期中设置定时器。我们也可以告诉 effect
仅在 isOn
变量发生变化时运行。仅当数组中的一个变量发生更改时,effect
才会在更新周期中运行。如果将数组保持为空, effect
将仅在 mount
和 unmount
时运行,因为没有要检查的变量是否再次运行副作用。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
const interval = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
现在,无论 isOn
布尔值是 true
还是 false
,定时器都在运行。接下来,我们希望定时器仅在秒表启动的时候运行。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(() => console.log('tick'), 1000);
}
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
现在在功能组件中引入另一个状态来跟踪秒表的计时器。它用于更新计时器,但仅在秒表被激活时使用。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
return (
<div>
{timer}
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
export default App;
代码中仍然存在一个错误。当定时器运行时,它会将 timer
每秒加 1
。但是,它始终依赖于计时器的初始状态在累加。只有当 inOn
布尔标志改变时,状态才会正常显示。为了在计时器在运行时,始终接收最新的状态,您可以使用函数代替代替状态,是的每次更新的时候都可以拿到最新的状态。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
...
}
export default App;
另一种方法是,在计时器改变时,运行 effect
。然后 effect
将收到最新的计时器状态。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn, timer]);
...
}
export default App;
这是使用浏览器 API
实现的秒表效果,如果您想继续,您也可以通过提供 “重置” 按钮来扩展示例。
import React, { useState, useEffect } from 'react';
function App() {
const [isOn, setIsOn] = useState(false);
const [timer, setTimer] = useState(0);
useEffect(() => {
let interval;
if (isOn) {
interval = setInterval(
() => setTimer(timer => timer + 1),
1000,
);
}
return () => clearInterval(interval);
}, [isOn]);
const onReset = () => {
setIsOn(false);
setTimer(0);
};
return (
<div>
{timer}
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
<button type="button" disabled={timer === 0} onClick={onReset}>
Reset
</button>
</div>
);
}
export default App;
That’s it. 使用 useEffect hook
来管理 React
函数组件的副作用,比如 Browser / DOM API
或其他第三方 API
的交互(例如数据获取)。您可以在 React
官方文档 useEffect hook
部分 获取更多信息。
自定义 React Hooks
最后同样重要的是,在您了解了两个最常用的在函数组件中引入状态和副作用的 Hooks
之后,我还想告诉您最后一件事:自定义 Hooks
。没错,您可以实现自己的自定义 React Hooks
,在您的应用程序中或提供给其他人中使用。让我们看看接下来的示例:一个能够检测您的设备是在线还是离线的应用程序是如何工作的。
import React, { useState } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(false);
if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}
return <div>You are online!</div>;
}
export default App;
接下来,为副作用引入 useEffect hook
。在这种情况下,effect
会添加和删除检查设备是联机还是脱机的监听器。两个监听器在 mount
时只设置一次,在 unmount
时清理一次(空数组作为第二个参数)。每当调用其中一个监听器时,它就会设置 isOffline
布尔值的状态。
import React, { useState, useEffect } from 'react';
function App() {
const [isOffline, setIsOffline] = useState(false);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);
if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}
return <div>You are online!</div>;
}
export default App;
现在,让我们将这些功能封装在一个 Effect
中。这是一个很棒的功能,我们希望其他地方也可以重用。这就是为什么我们可以提取功能,作为一个自定义 Hook
, 它遵循与其他 Hook
相同的命名约定。
import React, { useState, useEffect } from 'react';
function useOffline() {
const [isOffline, setIsOffline] = useState(false);
function onOffline() {
setIsOffline(true);
}
function onOnline() {
setIsOffline(false);
}
useEffect(() => {
window.addEventListener('offline', onOffline);
window.addEventListener('online', onOnline);
return () => {
window.removeEventListener('offline', onOffline);
window.removeEventListener('online', onOnline);
};
}, []);
return isOffline;
}
function App() {
const isOffline = useOffline();
if (isOffline) {
return <div>Sorry, you are offline ...</div>;
}
return <div>You are online!</div>;
}
export default App;
将自定义 Hook
作为函数提取出来并不是唯一的事情。您还必须将 isOffline
状态从自定义 Hook
中返回,以便在应用程序中向用户显示是否离线的消息。否则,它应该呈现正常的应用程序。这就是自定义 Hook
,它可以检测您是在线还是离线。您可以在 React
的文档中阅读有关自定义 Hook
的更多信息。
可重用的 React Hooks
最棒的地方,是它有可能发展自定义 React Hooks
的生态系统,我们可以从 npm
为任何 React
应用程序安装 Hooks
。而且不仅仅是 React
应用程序。Vue
的作者 Evan You (尤雨溪) 也被 Hooks
迷住了!。也许我们会看到两个生态系统之间的桥梁,可以在 Vue
和 React
之间共享 Hooks
。
如果您想深入了解 state hook
和 effect hook
,可以查看以下 React Hooks
教程:
查看 React
文档中关于钩子的官方FAQ和规则,以了解有关其细粒度行为的更多信息。此外,您还可以查看所有官方提供的React Hooks。
对于仍然关注 React Hooks
的每个人:给自己一个机会。使用状态和副作用实现几个 React
函数组件。我们必须自己去理解它们是如何工作的,感受这种编程体验的升级。我必须说使用它们感觉非常棒。