useEffect 使用指南

Hooks 是 React 16.8 的新增特性,至今经历两年的时间,它可以让你在不编写 Class 组件的情况下使用 state 以及其他 React 特性。 useEffect 是基础 Hooks 之一,我在项目中使用较为频繁,但总有些疑惑 ,比如:

  • 如何正确使用 useEffect
  • useEffect 的执行时机 ?
  • useEffect 和生命周期的区别 ?
    本文主要从以上几个方面分析 useEffect ,以及与另外一个看起来和 useEffect 很像的 Hook useLayoutEffect 的使用和它们之间的区别。

useEffect 简介

首先介绍两个概念,纯函数和副作用函数。

  • 纯函数( Pure Function ):对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,这样的函数被称为纯函数。
  • 副作用函数( Side effect Function ):如果一个函数在运行的过程中,除了返回函数值,还对主调用函数产生附加的影响,这样的函数被称为副作用函数。useEffect 就是在 React 更新 DOM 之后运行一些额外的代码,也就是执行副作用操作,比如请求数据,设置订阅以及手动更改 React 组件中的 DOM 等。

正确使用 useEffect

基本使用方法:useEffect(effect)

根据传参个数和传参类型,useEffect(effect) 的执行次数和执行结果是不同的,下面一一介绍。

  • 默认情况下,effect 会在每次渲染之后执行。示例如下:
1
2
3
4
5
6
7
8
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});

  • 也可以通过设置第二个参数,依赖项组成的数组 useEffect(effect,[]) ,让它在数组中的值发生变化的时候执行,数组中可以设置多个依赖项,其中的任意一项发生变化,effect 都会重新执行。示例如下:
1
2
3
4
5
6
7
8
9
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source],
);

需要注意的是:当依赖项是引用类型时,React 会对比当前渲染下的依赖项和上次渲染下的依赖项的内存地址是否一致,如果一致,effect 不会执行,只有当对比结果不一致时,effect 才会执行

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
function Child(props) {

useEffect(() => {
console.log("useEffect");
}, [props.data]);

return <div>{props.data.x}</div>;
}

let b = { x: 1 };

function Parent() {
const [count, setCount] = useState(0);
console.log("render");
return (
<div>
<button
onClick={() => {
b.x = b.x + 1;
setCount(count + 1);
}}
>
Click me
</button>
<Child data={b} />
</div>
);
}

上面实例中, 组件的内存地址没有变化,所以 console.log(“useEffect”) 不会执行,useEffect 不会被打印。为了解决这个问题,修改如下:

1
2
3
4
5
6
7
8
function Child(props) {

useEffect(() => {
console.log("useEffect");
}, [props.data.x]);

return <div>{props.data.x}</div>;
}

可见 useEffect 函数中的 console.log(“useEffect”) 被执行,打印出 useEffect。

  • 当依赖项是一个空数组 [] 时 , effect 只在第一次渲染的时候执行。