Hooks 之 useEffect

1,471 阅读3分钟

逛了一大圈发现 React 官网的文档写 useEffect 已经很好懂了,那直接就去看官网吧(笑)!!!哈哈哈,自个儿还是做个总结,官网的文档我也有些细节没能理解,梳理一下,加深下印象。

一、基本使用

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

1.无需清理副作用

使用生命周期函数,实现 count 发生变化就改变 document 的 title 属性的功能,要在挂载和更新的生命周期的钩子都要执行一段相同的代码。

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

而使用 Hook 的话

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

可以这样记住:effect 发生在“渲染之后”,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

2.需要清理副作用

而对于一些需要清除的副作用,例如订阅,监听,可以在 effect 返回一个函数,React 将会在执行清除操作时调用它,具体生命时候清除后面会详细谈。

import {useEffect,useState} from 'react'

export default function App () {
  const [ width, setWidth ] = useState(document.body.clientWidth)
  const onChange = () => {
    setWidth(document.body.clientWidth)
  }
  useEffect(() => {//监听resize事件
    window.addEventListener('resize', onChange, false)

    return () => {//取消监听
      window.removeEventListener('resize', onChange, false)
    }
  })
  return (
    <div>
      页面宽度: { width }
    </div>
    )
}

二、第二个参数

在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题

传递数组作为 useEffect 的第二个可选参数,

  • 不传:默认每一个渲染,useEffect 都会执行
  • 传空数组:只执行一次,仅在组件挂载和卸载时执行
  • 传一个数组,包含变量,变量发生变化时执行,注意哦,这里用的是浅比较

1.浅比较

浅比较就是只比较第一级,对于基本数据类型,只比较值;对于引用数据类型值,直接比较地址是否相同,不管里面内容变不变,只要地址一样,我们就认为没变。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

React 会确保 setState 函数的标识是稳定的,并且不会组件重新渲染时发生变化。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 setState

三、回调函数的相关理解

1.执行每一个 effect 前会对上一个effect进行清除

effect 发生在每次渲染之后,就相当于componentDidMountcomponentDidUpdateeffect 的清除阶段,在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。(它会在调用一个新的 effect 之前对前一个 effect 进行清理)

import {useEffect,useState} from 'react'

export default   function App () {
  const [ count, setCount ] = useState(0)
  const [ width, setWidth ] = useState(document.body.clientWidth)

  const onChange = () => {
    setWidth(document.body.clientWidth)
  }
  
  useEffect(() => {
    window.addEventListener('resize', onChange, false)
    console.log('执行副作用1')

    return () => {
      window.removeEventListener('resize', onChange, false)
      console.log('清除副作用1')
    }
  })
  useEffect(() => {
    document.title = count
    console.log('执行副作用2')
  })

  return (
    <div>
      页面名称: { count } 
      页面宽度: { width }
      <button onClick={() => { setCount(count + 1)}}>点我+1</button>
    </div>
    )
}

当我点击按钮加一时:控制台输出如图:请忽略工具警告(狗头)

image.png

其实这里边的细节有很多呢!

2. React 只会在浏览器绘制后运行 effects

这使得你的应用更流畅因为大多数 effects 并不会阻塞屏幕的更新。Effect 的清除同样被延迟了。上一次的 effect 会在重新渲染后被清除:(将窗口宽度从300拉到600的大致步骤如下:)

  1. React 渲染 页面宽度: 600 的UI。

  2. 浏览器绘制。我们在屏幕上看到 页面宽度: 600 的UI。

  3. React 清除页面宽度: 300的effect。

  4. React 运行页面宽度: 600的effect。

当 React 渲染组件时,会保存已使用的 effect,并在更新完 DOM 后执行它。这个过程在每次渲染时都会发生,包括首次渲染。也就是说: React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect ,useEffect 是异步执行的

使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快

你可能会好奇:如果清除上一次的effect发生变成 width 变成600之后,那它为什么还能“看到”旧的width 为300?

3.Effect 会捕获定义它们的那次渲染中的props和state。

组件内的每一个函数(包括事件处理函数,effects,定时器或者 API 调用等等)会捕获定义它们的那次渲染中的 props 和 state。

现在答案显而易见。effect 的清除并不会读取“最新”的 props。它只能读取到定义它的那次渲染中的 props 值:

函数组件每一次渲染的时候都会有自己的 Props 和 State,并且每一次的渲染中 props 和 state 是始终保持不变的,使用到了 props 和 state 的任何值也是独立的,包括(异步)事件处理函数

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      console.log(`You clicked ${count} times`);
    }, 3000);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

1, 2, 3, 4, 5 顺序打印 类组件确实不一样哦:

  componentDidUpdate() {
    setTimeout(() => {
      console.log(`You clicked ${this.state.count} times`);
    }, 3000);
  }

5, 5, 5, 5, 5 打印输出

并不是 count 的值在“不变”的 effect 中发生了改变,而是 effect 函数本身在每一次渲染中都不相同。每一个 effect 版本“看到”的 count 值都来自于它属于的那次渲染.

弄一篇这玩意可真不容易,欢迎各位大佬指点!!!

参考:

useEffect 完整指南

React 官方文档