0%

初识 React Hook

在Facebook以及社区成员的大力推广下,如今React已经几乎成为前端开发的必备技能。但随着对版本的不断优化和迭代,React官方决定逐步废弃 Class Component 的API全面拥抱 Function Component 的API 即基于函数的钩子 Hook 。这一趋势也是在意料之中,React建立之初本就是面向函数式的,之后加入的类组件API颇有些“强扭瓜不甜”的味道。本次,我们将初步了解一下 React Hook 的用法。

React Hook

一、关于React的两套API

很久以前,React大多数情况下都是使用一套API 即类组件API,然而随着React引入了全新的基于函数的钩子 Hook,函数式组件的使用变得更加广泛。

因此,现在任何一个组件既可以用类来写,又可以用钩子即函数来写。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
// class component
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

// function component
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

正如前文所说,尽管两者都可以使用,但是现在官方推荐使用钩子(函数)。

使用函数式的写法即钩子有很多好处:

  1. 视觉上:钩子更简洁,代码量更少

    原因:钩子 Hooks可以更优雅地实现逻辑复用。开发人员可以把逻辑抽象成钩子 Hooks 以实现代码复用,进一步,更可以将其发布为Library,供React生态社区的其他人使用。

    再比如:钩子 Hooks (主要为useEffect) 充当了函数式组件中的“生命周期”的概念,这也大大减少了API

  2. 使用上:钩子更灵活,用起来更“轻”,类组件则相对更“重”

  3. 🌈屁:更符合React函数式的本质

二、二者差异

1. Class Component

类组件是数据和逻辑的封装。组件的状态和操作方法是封装在一起的。因此在类组件的写法中,相关的数据和操作方法一般需要写在同一个 class 中。

2. Function Component

函数组件是只聚焦于一件事,即返回一个值。如果有多个操作,每个操作应该单独写成一个函数。并且,数据的状态应该与操作方法分离。因此,在React中的函数组件应该只专注于一件事即:返回组件的HTML代码。这种根据输入的数据,只进行单纯的数据计算的函数,称为“纯函数”(在函数式编程中)。

Notes:以上参考了阮一峰老师的Blog。

三、副效应

首先考虑一个问题,函数式组件是纯函数,因此只能进行数据计算,那么诸如生成日志、存储数据、改变应用状态等等不涉及计算的操作就无法被函数组件直接包含,我们把它们称为副效应(side effect)。因为如果函数内部直接包含副效应,它便不再是纯函数了,所以在函数组件内部,我们通过间接的方法来包含副效应

四、钩子 Hook

简言之:钩子(hook)是React函数组件的副效应的解决方案,用来为函数组件引入副效应。

函数组件只用来返回组件的HTML代码,所有的副效应都通过钩子来引入。

由于副效应很多,所以相应的钩子也有很多。React为一些常见的副效应提供了专用的钩子。

  • useState(): 保存状态
  • useContext(): 保存上下文
  • useRef(): 保存引用

以上的这些钩子都是引入某种特定的副效应,而 useEffect()通用的副效应钩子。在找不到对应的钩子时,可以使用它。

五、useEffect() 的用法

1. 基本用法——第一个参数

useEffect() 本身是一个函数,由React提供,在函数组件内部调用即可。

useEffect() 的第一个参数是一个函数,它就是副效应。组件的首次加载到DOM以及以后组件的每次渲染,React都会自动执行该函数。

例如,我们希望组件加载以后,网页标题(document.title)会随之改变。那么,改变标题这个操作就是组件的副效应,可以通过 useEffect() 来实现。

1
2
3
4
5
6
7
8
import React, { useEffect } from 'react';

function Welcome(props) {
useEffect(() => {
document.title = '加载完成';
});
return <h1>Hello, {props.name}</h1>;
}

2. 基本用法——第二个参数

但是,有些时候,我们不希望 useEffect() 每次渲染都执行,这时候可以使用它的第二个参数。useEffect() 的第二参数是一个数组类型的参数,它可以用于指定副效应函数的依赖项只有当依赖项发生变化时,组件才会重新渲染

示例如下:

1
2
3
4
5
6
function Welcome(props) {
useEffect(() => {
document.title = `Hello, ${props.name}`;
}, [props.name]);
return <h1>Hello, {props.name}</h1>;
}

但是,如果第二个参数是一个空数组,就表明副效应函数没有任何依赖项。因此,副效应函数只会在组件加载到DOM后执行一次,此后即使组件重新渲染,也不再执行。

六、useEffect() 的用途

只要是副效应,都可以使用 useEffect() 来引入。它的常见用途有如下几种:

  • 获取数据(data fetching)
  • 事件监听或订阅(setting up a subscription)
  • 改变 DOM(changing the DOM)
  • 输出日志(logging)

示例如下(从远程服务器获取数据):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
const [data, setData] = useState({ hits: [] });

useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://getdataAPI/?id=1',
);

setData(result.data);
};

fetchData();
}, []);

return (
<ul>
{data.hits.map(item => {
<li key = {item.id}>
<a href={item.url}>{item.title}</a>
</li>
})}
</ul>
);
}

export default App;

在该示例中,useState() 用来生成一个状态变量和设置状态变量的方法以保存/更新获取的数据。useEffecr() 的副效应函数内部有一个异步函数 async 函数,用来从服务器异步获取数据,等拿到数据以后,再用 setData() 更新状态以出发组件重新渲染。

注意:此处的第二个参数为一个空数组,因为在该示例中只需获取数据一次即可。

七、useEffect() 的返回值

副效应是随着组件加载而产生的,那么当组件卸载时,有时也需要清理这些副效应。

useEffect() 允许返回一个函数在组件卸载时会执行该函数清理副效应(实际上,由于副效应函数在组件每次渲染时都会执行,因此清理副效应不仅会发生在组件卸载时,而且每次副效应执行前,都会执行一次来清理上一次渲染的副效应)。如果不需要清理副效应,那么 useEffect() 不用返回任何值。

例如,副效应为组件加载时订阅一个事件,那么在组件卸载时,我们需要取消订阅,因此我们需要清理这个副效应。示例如下:

1
2
3
4
5
6
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
}, [props.source]);

八、useEffect() 的注意点

使用 useEffect() 时,如果有多个副效应,**应该调用多个 useEffect()**,而不是合并写成一个。

Notes:以上内容借鉴自阮一峰老师的博客。