useEffect基础用法

2/23/2023 React

React Hook useEffect基础用法

# useEffect概念解释

第二个Hook(钩子函数)是useEffect,他的作用是"钩住"函数组件中某些生命周期函数。

都能钩住哪些生命周期函数?
componentDidMount(组件被挂载完成后)、componentDidUpdate(组件重新渲染完成后)、componentWillUnmount(组件即将被卸载前)

为什么是这3个生命周期函数?
因为修改数据可以使用前面的useState,数据变更会触发组件重新渲染,上面3个就是和组件渲染关联最紧密的生命周期函数。

那其他生命周期函数呢?
这个问题,直接引用React官方中文文档FAQ (opens new window),如下:

我们给 Hook 设定的目标是尽早覆盖 class 的所有使用场景。目前暂时还没有对应不常用的 getSnapshotBeforeUpdate,getDerivedStateFromError 和 componentDidCatch 生命周期的 Hook 等价写法,但我们计划尽早把它们加进来。

# useEffect是来解决类组件什么问题的?

useEffect是来解决类组件 某些执行代码被分散在不同的生命周期函数中 的问题。

举例1:若某个类组件变量a,默认值是0,当组件第一次挂载后或组件重新渲染后,将网页标题显示为a的值。
我们需要写的代码是:

// 为了更加清楚看到每次渲染,我们在网页标题中 a 的后面在增加一个随机数字
componentDidMount() {
    document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
}
componentDidUpdate() {
    document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
}
1
2
3
4
5
6
7

从上面这种代码你会看到,为了保证第一次被挂载、组件重新渲染都执行修改网页标题的行为,相同代码我们需要分别在componentDidMount、componentDidUpdate中写2次。

举例2:假设需要给上面那类组件新增一个功能,当组件第一次被挂载后执行一个自动累加器 setInterval,每1秒a的值+1,为了防止内存泄漏,我们在该组件即将被卸载前清楚累累加器。
我们需要写的代码是:

timer = null; // 新增一个可内部访问的累加器变量(注意:类组件定义属性时前面无法使用 var/let/const)
componentDidMount() {
    document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
    this.timer = setInterval(()=> {
        this.setState({a: this.state.a+1})}
    , 1000);
}
componentDidUpdate() {
    document.title = `${this.state.a} - ${Math.floor(Math.random()*100)}`;
}
componentWillUnmount() {
    clearInterval(this.timer); // 清除累加器
}
1
2
3
4
5
6
7
8
9
10
11
12
13

从上面的代码可以看到,增加累加器和清除累加器这2个相关的执行代码被分别定义在componentDidMount、componentWillUnmount两个生命周期中。

举例3:假设给上面的组件在新增一个变量b,当b的值发生变化后也会引起组件重新渲染,然后呢?有什么隐患呢?
b的值改变引发组件重新渲染,然后肯定是会触发componentDidUpdata函数,这时候会让修改网页标题的代码再次执行一次,尽管此时a的值并没有发生任何变化。

再来回顾一下上面的3个例子:

  1. 举例1中,相同的代码可能需要在不同生命周期函数中写2次;
  2. 举例2中,相同的代码可能需要再不同生命周期函数中定义;
  3. 举例3中,无论是哪个原因引发的组件重新渲染,都会触发生命周期函数的执行,造成一些不必要的代码执行。

以上就是 类组件"某些执行代码被分散在不同的生命周期函数中"引发的问题具体表现,而useEffect就是来解决这些问题的。

# useEffect函数源码

首先看一下React源码中的ReachHooks.js (opens new window)

export function useEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  const dispatcher = resolveDispatcher();
  return dispatcher.useEffect(create, deps);
}
1
2
3
4
5
6
7

还是跟useState一样,都贴出源码了,看不懂也无所谓,只是讲述一下如何使用Hook,不是Hook源码分析,之所以贴出源码只是显得本文比较有深度(装逼)。哈哈哈

# useEffect基本用法

useEffect(effect, [deps])函数可以传入2个参数,第1个参数为我们定义的执行函数、第2个函数是依赖关系(可选参数)。若一个函数组件定义了多个useEffect,那么他们实际执行顺序是按照在代码中定义的先后顺序来执行的。

具体说明如下:

第1个值effect是一个function,用来编写useEffect对应的执行代码。
开头提到的useEffect能钩住哪些生命周期函数?
componentDidMount、componentDidUpdate、componentWillUnmount,当这3个生命周期函数执行后,就会触发useEffect函数,进而执行第1个参数effect中的内容。

组件挂载后(componentDidMount)和组件重新渲染后(componentDidUpdata)对应的代码合并为一个函数这个容易理解,可是组件卸载前(componentWillUnmount)也能融入进来的吗?
那是必须的呀,通过effect中 return 一个函数来实现的。

关于第2个参数[deps],必须得知道这是个可选的参数,是Hook用来向React表明useEffect依赖关系的即可,用法后面再说。

代码形式:

useEffect(()=> {
    // 此处编写 组件挂载之后和组件重新渲染之后执行的代码
    ...
    
    return () => {
        // 此处编写 组件即将被卸载写在前执行的代码
        ...
    }
}, [deps])
1
2
3
4
5
6
7
8
9

之前说useEffect第一个参数effect是个 function,只是这个function稍微复杂些。

# useEffect拆解

# 'effect' 补充说明

  1. 若你不需要在组件卸载前执行任何代码,那么可以忽略不写 effect 中的return相关代码。

# '[deps]' 补充说明

  1. 若缺省,则组件挂载、组件重新渲染、组件即将被卸载前,每一次都会触发该useEffect;
  2. 若传值,则必须为数组,数组的内容是函数组件中通过useState自定义的变量或者是父组件传过来的props中的变量,告诉React只有数组内的变量发生变化时候才会触发useEffect;
  3. 若传值,但是传的是空数组 [],则表示该useEffect里的内容仅会在"挂载完成后和组件即将被卸载前"执行一次。

# useEffect使用示例

刚刚关于类组件"某些执行代码被分散在不同的生命周期函数中"引发的问题所举的3个例子。

举例1:若某类组件中有变量a,默认值为0,当组件第1次被挂载后或组件重新渲染后,将网页标题显示为a的值。
补充说明:

  1. 为了让a的值可以发生变化,在组件需要添加一个按钮,每次点击 a 的值+1;
  2. 为了更加清楚看到每次渲染,需要在网页标题中 a 的后面再增加一个随机数。
import React, { useState, useEffect } from 'react';

const docTitleComponent = () => {
    
    const [a, setA] = useState(0); // 定义变量a,并且默认值为0
    
    useEffect(()=> {
        // 无论是第一次挂载还是以后每次组件更新,修改网页标题的执行代码只需要在这里写一次即可
        document.title = `${a} - ${Math.floor(Math.random()*100)}`;
    }) 
    
    const handleClick = () => {
        setA(a+1);
    }
    
    return <div>
        {a}
        <button onClick={handleClick}>a+1</button>
    </div>
}

export default docTitleComponent;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

从上述代码可以看出,"类组件中相同的代码可能需要在不同生命周期函数中写2次"这个问题已经Hook useEffect已解决。

这里只是实现列 举例1 中的功能,是useEffect最基础的用法。举例2、举例3中的功能下个文章再写吧,最近想摆烂,公司工资拖欠了10来天了,有点慌~。