hooks useEffect

1 初探Effect Hook

useEffect(effect,depsArr) => void | Destructor // Destructor 析构函数(c++中的概念) 析构函数和构造函数相反,主要用于对象销毁的时候执行,做一些清理工作

//(alias) useEffect(effect: React. EffectCallback, deps?: React. DependencyList | undefined): void
// type EffectCallback = () => (void | Destructor); // 析构函数(destructor)

useEffect函数接收两个形参,一个函数,一个数组,同时拥有一个函数或者void作为返回值
对于形参函数,react会保存这个函数,我们称这个函数的内容为Effect,react组件需要在渲染后执行这个函数。

对于形参数组:

  1. 如果没有,那么组件每次渲染都会执行Effect
  2. 如果是空数组,那么挂载执行一次Effect添加副作用,卸载时执行一次析构函数清除副作用
  3. 如果有依赖,那么挂载执行一次Effect添加副作用,卸载执行一次析构函数清除副作用。在依赖状态更新的时候,首先执行返回值--析构函数清除上次产生的副作用,然后执行一次Effect添加副作用

什么时候需要清除副作用,什么时候无需清除?

无需清除副作用的情况:
如果组件在每次渲染的时候都要执行某个操作,并且这个操作不存在内存泄露的情况,如定时器和事件监听,那么就没必要清除副作用,此时返回值可不写,比如更新DOM标题

需要清除副作用的情况:
事件的监听和解绑,点击获取鼠标位置等等

形参数组什么时候设置参数,什么时候不设置参数?

无需依赖数组:
如果组件每次渲染都一定要做些添加副作用的操作,那么就没必要设置依赖数组的形参

依赖数组为空:
如果组件只需在挂载的时候做些添加副作用的操作,在卸载的时候再清除副作用,那么就没必要给依赖数组设置元素。对应场景是鼠标点击获取位置的功能组件,只需要在组件卸载的时候,清除事件监听即可

需要依赖数组:
如果组件一定要依赖于某个状态更新的时候清除上次副作用,然后再做些添加副作用的操作,那么就需要给依赖数组设置元素。对应场景是,每次点击都要根据点击次数给文档title赋值,依赖于点击次数这个状态去添加副作用

https://blog.csdn.net/yunfeihe233/article/details/106616674/?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242

执行顺序:

useEffect和useLayoutEffect都会拿到最新dom,区别是useLayoutEffect拿到的是最新的DOM,而useEffect拿到的是浏览器渲染组件到屏幕之后的最新DOM,useEffect里面再次调用setState会触发浏览器二次渲染,即重绘与回流。
useEffect是异步的,useLayoutEffect是同步的,异(同)步是相对于浏览器执行刷新屏幕Task来说的。因此useLayoutEffect会阻塞浏览器渲染,但是useEffect会导致浏览器的回流与重绘。

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新

添加副作用和清除上次副作用都在render之前

  1. 初次挂载添加副作用
  2. render
  3. 状态更新,清除上次更新添加的副作用
  4. 给本次状态更新设置副作用
  5. render
    对于3,析构函数执行去清除上一次副作用的时候,可以通过打印得知拿到的state是更新前的值

1.2 无需清除的Effect

代码时间:使用useEffect使用DOM完成标题更新
因为组件加载和更新的时候执行同样的操作
就是React组件在每次渲染的时候都必须调用它,但是class组件没用提供这样的方法
因此我们必须在这两个方法中写同样的逻辑


    componentDidMount(){
        document.title = `You clicked ${this.state.count} times` ;
    }

    componentDidUpdate(){
        document.title = `You clicked ${this.state.count} times` ;
    }

import React, { useState, useEffect } from "react"; 
const LikeButton: React. FC = () => {

    const [like, setLike] = useState(0)
    const [on, setOn] = useState(true)

    /* 
    (alias) useEffect(effect: React.EffectCallback, deps?: React.DependencyList | undefined): void
    形参接收一个函数,react会保存这个形参函数,我们称这个函数的内容为Effect,react组件需要在渲染后执行这个函数
    useEffect在第一次渲染和每次渲染之后都会执行,不用在意是渲染完成还是更新的时候触发
     */
    //  type EffectCallback = () => (void | Destructor);
    useEffect(()=>{
        document.title = `点击了${like}次`

    })
    return (
        <>
            <button onClick={() => setLike(like + 1)} >
                {like}🐮
            </button>
            <button onClick={() => {setOn(!on)}}>
                {on ? 'ON' : 'OFF'}
            </button>
        </>
    )

}

export default LikeButton

1.3 需要清除的Effect

还要一些副作用是需要清楚的,比如dom事件的移除等等,防止内存泄露

react会在组件卸载的时候执行清除操作
react会在执行当前Effect(也就是组件渲染时)之前对上一个Effect进行清除

useEffect 添加和清除副作用的执行时机:

a. 如果没有给useEffect的依赖数组赋值

1. 组件卸载
2. 组件渲染的时候,执行当前Effect之前,清除上一个Effect(对事件的添加、删除很有用)

b. 如果useEffect的依赖数组是一个空数组

1. 组件初次渲染执行useEffect添加副作用
2. 组件卸载执行useEffect的返回值函数,清除副作用

c. 如果useEffect的依赖数组有值

1. 基于b项
2. 依赖数组的状态对应的状态发生变化时,清除上个Effect的副作用,然后执行useEffect添加副作用

案例: 使用useEffect完成一个鼠标跟踪器

使用class实现


    componentDidMount() {
        // 三参为true代表在捕获阶段触发
        document.addEventListener('click',this.updateMouse)    
    }

    componentWillMount() {
        document.removeEventListener('click',this.updateMounse)
    }

hooks实现

我们发现这种写法几乎每次组件渲染都会给document添加、移除事件,性能不好
因此探讨一下如何限制useEffect执行次数是很有必要的

import React, { useState, useEffect } from 'react'

const MouseTracker: React. FC = () => {

    const [positions, setPositions] = useState({
        x: 0, y: 0
    })
    useEffect(() => {
        // 副作用不清除会导致后续点击一次输出多次inner,因为有多个事件在监听
        const mouseClicker = (e: MouseEvent) => {
            console.log('inner')
            setPositions({
                x: e.clientX,
                y: e.clientY
            })
        }
        document.addEventListener('click', mouseClicker)
        // type EffectCallback = () => (void | Destructor)
        // // Destructors are only allowed to return void.
        // type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };

        // 清除副作用
        return () => {
            console.log('outer')
            document.removeEventListener('click', mouseClicker)
        }
    })

    return (
        <p>{positions.x + " " + positions.y}</p>
    )

}

export default MouseTracker

1.3 useEffect执行顺序

// 点击之前
render函数执行之前  0
执行useEffect添加副作用  0
// 点击之后
event handler run 
before render 页面渲染之前  320 
执行useEffect返回值析构函数DeStructor 清除副作用 0 // 先清除旧的副作用
执行useEffect添加副作用 320 // 再添加新的副作用

2 如何控制Effect调用次数

通过1.1 和 1.2 我们发现,每次组件渲染都会执行useEffect,感觉有些浪费,会导致性能问题
有什么办法能规避这个问题呢?

2.1 只在组件挂载和卸载的时候执行

案例: 修改前两个例子,控制useEffect的执行,如何只执行一次useEffect和清除副作用的返回值函数?

如果useEffect二参传入空数组,那么相当于告诉react你的useEffect并不依赖于任意props或者state

如果我们这样改写,那么副作用的添加和清除只会在组件挂载、清除的时候各自执行一次,即组件挂载只执行一次添加副作用,组件卸载只执行一次清除副作用

APP.tsx

import React, {useState} from 'react'; 
import MouseTracker from './components/MouseTracker'; 

const App: React. FC = () => {
  const [show, setShow] = useState(true)
  return (

    <div className="App">
        {show && <MouseTracker/>}
        <button onClick={()=>setShow(!show)}>toggle MouseTracker</button>
    </div>

  ); 
}

export default App; 

Component

import React, { useState, useEffect } from 'react'

const MouseTracker: React. FC = () => {

    const [positions, setPositions] = useState({
        x: 0, y: 0
    })                        
    useEffect(() => {
        const mouseClicker = (e: MouseEvent) => {
            setPositions({
                x: e.clientX,
                y: e.clientY
            })
        }
        document.addEventListener('click', mouseClicker)
        return () => {
            document.removeEventListener('click', mouseClicker)
        }
    }, [])

    return (
        <p>{positions.x + " " + positions.y}</p>
    )

}

export default MouseTracker

2.2 根据依赖数组中的状态执行

import React, { useState, useEffect } from "react"; 
const LikeButton: React.FC = () => {

    const [like, setLike] = useState(0)
    const [on, setOn] = useState(true)

    // 点击了多少次并不关系开关的状态,因此只需要依赖于like的值进行更新,只需要在like的值改变的情况下执行
    useEffect(() => {
        console.log('执行')
        document.title = `点击了${like}次`
    },[like])

    return (
        <>
            <button onClick={() => setLike(like + 1)} >
                {like}🐮
            </button>
            <button onClick={() => { setOn(!on) }}>
                {on ? 'ON' : 'OFF'}
            </button>
        </>
    )

}

export default LikeButton
posted @ 2021-04-15 23:11  IslandZzzz  阅读(110)  评论(0编辑  收藏  举报