useEffect

作用:

  1. 推迟代码执行
    渲染完成之后再执行,常用于 数据读取

  2. 完成移除 - 组件卸载的时候做一些清理的工作
    例如:移除定时器、事件监听器等
    有关组件卸载:
    触发时机:当组件从 DOM 中移除时,会触发useEffect里返回的函数,useEffect里的内容不会执行,执行也没有意义了。当组件出现在DOM中时,会执行useEffect里的代码,useEffect里返回的函数不执行。

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

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

  useEffect(() => {
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // 返回的函数将在组件卸载时执行
    return () => {
      clearInterval(timer);
      console.log('Timer cleared');
    };
  }, []); // 空依赖数组,effect 只在挂载和卸载时运行

  return <div>Timer: {count}</div>;
}

export default TimerComponent;

该行为是模拟了componentWillUnmount,常用于清理操作,例如取消网络请求、清除计时器或移除事件监听器。

补充:何时执行componentWillUnmount?

  • 组件从页面移除:当用户导航到另一个页面或条件渲染使得组件不再显示时,componentWillUnmount 会被调用。在上面的例子中,当 TimerComponent 被移除时,计时器会被清除。

  • 父组件卸载:如果父组件卸载了,所有子组件也会卸载,它们的 componentWillUnmount 也会被调用。

  1. 第二个参数来模拟生命周期。
    对于缓存的对象来说,因为每次函数都重新执行,因此对象的引用会变,所以useEffect第二个参数缓存的是引用的地址。

useEffect存在一种bail out(保释) / (补救)机制:

当你尝试设置相同的状态,仅仅让你把函数执行完,但不会再触发effect。

同理,在class组件中的setState也是一样。

setState

state设置重复的值 —— 只会重复渲染一次 +1


1. 推迟代码执行

例如:

上面其实本来就是一种错误的写法,只是React官方帮你兜了个底。

正确的做法是:
在这里插入图片描述

2. 完成移除 定时器、监听

function HOC() {
    const [count, setCount] = useState(0);
    useEffect(() => {
        console.log('创建')
        return () => {
            console.log('销毁')
        }
    });
    return(
        <>
            <button onClick={() => setCount(count + 1)}>按钮</button>
        </>
    )
}
export default HOC;

当状态变化的时候,重新渲染,重新渲染导致useEffect执行(前提是没有依赖项)。在第一次刷新的时候,不执行useEffect返回的函数,只执行useEffect里的内容。后面状态变化导致重新渲染的时候,会优先执行useEffect返回的函数,然后再执行useEffect里的内容

例如:

页面初始化时:

第一次点击按钮时:


useEffect - clean-up阶段有两个:

  1. 删除组件
  2. 重新渲染当前组件
1. 第一个,删除组件的时候执行:

在这里插入图片描述

  • 注意:
    1. useEffect里的回调如果有返回值的话,必须是一个函数。
    2. 会先执行这个return函数,例如下面的代码。
2. 第二个,重新渲染的时候执行:

在这里插入图片描述

上面两种情景这样做的官方总结:

react官方:
1.必要性:极大的提升程序的可靠性
2.函数组件:简单、小型组件

程序可靠性
程序性能

结论:重新渲染的时候就先移除然后重新添加,目的是为了增强稳定性,如果有很庞大的初始化、移除操作,还应该用class(didMount、willUnmount)

注意:

错误的使用state,会导致资源泄露


例如:
1.某种异步操作开始——非常长
2.组件已经被卸载了
3.setState - 资源(内存泄露、socket泄露,主要是内存泄露)泄露

代码如下:
在这里插入图片描述

会报如下警告⚠️:

Warning: Can’t perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in Unknown (at App.js:13)

解决方法:
在这里插入图片描述


3. 第三种, 第二个参数来模拟生命周期。

useEffect选择执行:
在这里插入图片描述
当a变化,才打印effect a;
当b变化,才打印effect b;

类似于 [a] 这种形式 官方叫做 ”跳过“

useEffect模拟生存周期函数:

useEffect(fn, [值]);

如:componentDidMount
在这里插入图片描述


useEffect() 的callback返回函数的case,其实是根据当前操作来的,是添加移除定时器还是添加移除dom,执行效果是不一样的,具体可以参考useEffect() 有始有终


useLayoutEffect 是 React 提供的一个 Hook,你可以把它理解为**“在浏览器把画面绘制出来之前,React 给你的最后一次修改 DOM 的机会”**。

它的功能签名和 useEffect 几乎一模一样,但核心区别在于执行时机

useEffect vs useLayoutEffect

为了理解它,我们需要看浏览器的渲染流程:
React 更新 DOMuseLayoutEffect 执行浏览器绘制 (Paint)useEffect 执行

特性 useEffect (常用) useLayoutEffect (特殊)
执行时机 浏览器绘制完成后异步执行 浏览器绘制之前同步执行
视觉体验 如果修改了布局,用户可能会看到闪烁(先显示旧位置,再跳到新位置) 用户看不到中间状态,直接看到最终结果,无闪烁
性能影响 不阻塞浏览器绘制,性能较好 会阻塞浏览器绘制,用多了会导致页面卡顿
比喻 演出开始后,你在后台改道具 幕布拉开前,你冲上去把道具摆好

💡 什么时候必须用 useLayoutEffect?

只有当你需要读取 DOM 布局(如宽高、位置)并立即同步修改 DOM 时,才使用它。主要用于解决“视觉闪烁”问题。

典型场景 1:防止弹窗/气泡定位闪烁

假设你要做一个 Tooltip(气泡提示),它需要显示在按钮的上方。

  • 如果用 useEffect:页面先渲染 Tooltip 在默认位置(比如左上角) -> 浏览器绘制 -> useEffect 计算按钮位置 -> 修改 Tooltip 位置 -> 用户看到 Tooltip “闪”了一下跳到了按钮上方。
  • 如果用 useLayoutEffect:React 更新 DOM -> useLayoutEffect 立即计算位置并修改 -> 浏览器绘制(直接画出正确位置的 Tooltip) -> 用户完全感觉不到位置修正的过程。
典型场景 2:根据内容调整布局

比如你需要根据一个容器的实际宽度,来决定里面放几个卡片。你需要先测量容器宽度,再决定渲染逻辑,这个过程必须在绘制前完成。

典型场景 3:强制同步滚动

比如进入聊天室,你希望滚动条直接到底部,不想让用户看到“先在顶部再跳到底部”的过程。

⚠️ 什么时候不要用?

绝大多数情况下,请优先使用 useEffect

以下场景不要useLayoutEffect

  • 数据请求 (API 调用)
  • 事件订阅/监听
  • 日志记录、埋点
  • 写入 LocalStorage
  • 任何不依赖 DOM 测量的操作

因为在这些场景下使用 useLayoutEffect 只会徒增性能开销,阻塞页面渲染,带来卡顿风险。

📌 总结口诀

“涉及测量和布局修正,怕闪就用 useLayoutEffect;其他所有副作用,统统用 useEffect。”

useLayoutEffect 是 React 16.8.0 版本引入的。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐