讲师中心 微信公众号
AI工具推荐 视频效率加速

react 中 useEffect 钩子作用 react 中 useEffect 钩子的使用场景

落杰酱_6681

落杰酱_6681

发布时间:2025-07-22 20:14:01

|

750人浏览过

|

来源于php中文网

原创

useeffect用于处理函数组件中的副作用,如数据请求、事件监听等,通过依赖项数组控制执行时机;2. 依赖项遗漏会导致闭包捕获旧值,必须将所有用到的变量加入依赖数组或使用函数式更新;3. 不稳定依赖(如每次渲染重建的函数)会引发无限循环或频繁执行,需用usecallback或usememo固化引用;4. useeffect在dom更新后异步执行不阻塞渲染,uselayouteffect则同步执行适合需立即测量dom的场景;5. 拆分多个useeffect按不同依赖独立执行更高效,复杂逻辑应封装成自定义hook提升复用性和可读性。

react 中 useEffect 钩子作用 react 中 useEffect 钩子的使用场景

useEffect 在 React 函数组件中扮演着“副作用”处理器的角色。简单来说,它让你能够在组件渲染完成后,执行一些与渲染结果本身无关但又必须进行的操作,比如数据请求、订阅外部事件、手动修改 DOM 或者设置计时器等等。你可以把它理解为函数组件版的生命周期方法集合,但它提供了更精细的控制粒度,能够根据特定的数据变化来决定何时执行或重新执行这些副作用。

react 中 useEffect 钩子作用 react 中 useEffect 钩子的使用场景

解决方案

useEffect 的基本用法是接收两个参数:一个回调函数和一个可选的依赖项数组。

useEffect(() => {
  // 这里放置你的副作用逻辑
  console.log('组件渲染后或依赖项变化后执行');

  // 可选:返回一个清理函数
  return () => {
    console.log('组件卸载时或副作用重新执行前清理');
    // 清理资源,例如取消订阅、清除计时器
  };
}, [依赖项1, 依赖项2]); // 依赖项数组
  • 回调函数: 这个函数会在组件首次渲染后,以及每次依赖项数组中的任何一个值发生变化时执行。
  • 依赖项数组: 这是 useEffect 最核心的部分。
    • 如果你提供一个空数组 [],副作用函数只会在组件挂载时执行一次,并且在组件卸载时执行清理函数。这非常适合那些只需要初始化一次的逻辑,比如数据请求。
    • 如果你省略这个数组,副作用函数会在每次组件渲染后都执行。这通常不是你想要的,因为会导致不必要的重复操作,除非你确实需要监听每一次渲染。
    • 如果你提供一个包含特定值的数组,比如 [count, name],那么只有当 count 或 name 的值发生变化时,副作用函数才会重新执行。React 会进行浅比较来判断依赖项是否改变。

返回的清理函数是 useEffect 的另一个强大之处。它会在组件卸载时执行,或者在下一次副作用函数执行之前(如果依赖项发生变化),用于清理上一次副作用留下的资源,防止内存泄漏。比如,你订阅了一个事件,就需要在清理函数中取消订阅;你设置了一个定时器,就需要在这里清除它。这就像是 componentWillUnmount 和 componentDidUpdate 中清理逻辑的结合。

react 中 useEffect 钩子作用 react 中 useEffect 钩子的使用场景

React useEffect 依赖项陷阱:如何避免不必要的重复执行与无限循环?

关于 useEffect,最让人头疼的可能就是它的依赖项了。我见过太多因为依赖项处理不当而导致的性能问题或者难以追踪的 bug。理解它,几乎就掌握了 useEffect 的一半精髓。

最大的坑,往往是依赖项的遗漏或不稳定的依赖项。

react 中 useEffect 钩子作用 react 中 useEffect 钩子的使用场景
  1. 遗漏依赖项(Stale Closures): 当你的副作用函数内部使用了组件作用域中的变量或函数,但你却没有把它们添加到依赖项数组中,useEffect 就不会知道这些值可能已经改变了。结果就是,你的副作用函数会捕获到旧的(“陈旧的”)值,导致逻辑出错。React 的 ESLint 插件通常会很聪明地提醒你,但如果你手动关闭了这些警告,那可就麻烦了。

    const [count, setCount] = useState(0);
    
    useEffect(() => {
      // 这里的 count 总是初始的 0,因为 count 没有作为依赖项
      // 如果你希望它在 count 变化时重新执行,就需要加上 [count]
      const timer = setInterval(() => {
        console.log('当前 count:', count); // 可能会打印旧的 count 值
      }, 1000);
      return () => clearInterval(timer);
    }, []); // ❌ 缺少 count 依赖

    正确的做法是:}, [count]); 或者使用函数式更新 setCount(prevCount => prevCount + 1) 来避免对 count 的直接依赖。

  2. 不稳定的依赖项: 另一个常见问题是把那些每次渲染都会重新创建的值(比如在组件内部定义的函数、对象或数组)作为依赖项。即使它们的内容没有变,但因为引用变了,useEffect 也会认为它们“变了”,从而导致副作用函数不必要的重复执行。

    const fetchData = () => { /* ... */ }; // 每次渲染都会重新创建
    
    useEffect(() => {
      fetchData();
    }, [fetchData]); // ❌ fetchData 每次都是新的引用,导致无限循环或频繁执行

    解决这类问题,通常会用到 useCallback 来 memoize 函数,或者 useMemo 来 memoize 对象和数组,确保它们在依赖项不变的情况下引用保持稳定。

    const memoizedFetchData = useCallback(() => {
      // ...
    }, [/* fetchData 内部的依赖 */]);
    
    useEffect(() => {
      memoizedFetchData();
    }, [memoizedFetchData]); // ✅ 只有当 memoizedFetchData 的依赖变化时,它才变化
  3. 无限循环: 这是最显而易见的错误,通常发生在 useEffect 内部更新了某个状态,而这个状态又是 useEffect 的依赖项时。

    const [data, setData] = useState(null);
    
    useEffect(() => {
      // 假设这里获取数据
      fetch('/api/data').then(res => res.json()).then(newData => {
        setData(newData); // 触发 data 变化
      });
    }, [data]); // ❌ data 变化又导致 useEffect 重新执行,无限循环

    要避免这种情况,你需要仔细审视你的逻辑,确保状态更新不会直接导致 useEffect 的无限触发。通常,数据获取只在组件挂载时执行一次 ([]),或者在特定的外部 ID 变化时执行。

掌握 React useEffect 实用技巧:从数据获取到事件监听的常见应用场景解析

useEffect 的强大之处在于它的通用性。一旦你理解了它的核心机制,你会发现它能解决许多组件交互和外部系统集成的问题。

  1. 数据获取 (Data Fetching): 这是 useEffect 最经典的用例。你通常希望在组件首次渲染后加载数据。

    page-agent 浏览器控制(CDP)
    page-agent 浏览器控制(CDP)

    使用 page-agent CLI(CDP)控制浏览器;CLI只能通过 GitHub Release 的 .tgz 安装,npm 官方包暂未发布。

    下载
    import React, { useState, useEffect } from 'react';
    
    function UserProfile({ userId }) {
      const [user, setUser] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        const fetchUserData = async () => {
          setLoading(true);
          setError(null);
          try {
            const response = await fetch(`/api/users/${userId}`);
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
            const data = await response.json();
            setUser(data);
          } catch (e) {
            setError(e);
          } finally {
            setLoading(false);
          }
        };
    
        fetchUserData();
      }, [userId]); // 只有当 userId 改变时才重新获取数据
    
      if (loading) return <div>加载中...</div>;
      if (error) return <div>错误: {error.message}</div>;
      if (!user) return null;
    
      return (
        <div>
          <h2>{user.name}</h2>
          <p>邮箱: {user.email}</p>
        </div>
      );
    }

    这里,userId 作为依赖项,确保当用户 ID 变化时,数据会重新加载。

  2. 事件监听与清理: 当你需要与全局对象(如 window, document)或者其他 DOM 元素进行交互时,useEffect 是理想的选择。关键在于,你必须在组件卸载时移除这些监听器,否则会造成内存泄漏。

    import React, { useEffect } from 'react';
    
    function ScrollLogger() {
      useEffect(() => {
        const handleScroll = () => {
          console.log('页面滚动了!', window.scrollY);
        };
    
        window.addEventListener('scroll', handleScroll);
    
        // 返回一个清理函数,在组件卸载时移除事件监听器
        return () => {
          window.removeEventListener('scroll', handleScroll);
          console.log('滚动监听器已移除。');
        };
      }, []); // 空数组表示只在组件挂载时添加,卸载时移除
    
      return <div>请滚动页面查看控制台输出。</div>;
    }
  3. 定时器设置与清除: 类似事件监听,如果你在组件中使用了 setInterval 或 setTimeout,也需要确保在组件不再需要时清除它们。

    import React, { useState, useEffect } from 'react';
    
    function Timer() {
      const [seconds, setSeconds] = useState(0);
    
      useEffect(() => {
        const intervalId = setInterval(() => {
          setSeconds(prevSeconds => prevSeconds + 1);
        }, 1000);
    
        // 清理函数,在组件卸载时清除定时器
        return () => {
          clearInterval(intervalId);
          console.log('计时器已清除。');
        };
      }, []); // 空数组表示只在组件挂载时启动一次
    
      return <div>计时器: {seconds} 秒</div>;
    }
  4. DOM 操作: 尽管 React 提倡声明式地更新 UI,但在某些特定场景下,你可能需要直接操作 DOM,比如集成第三方库、测量元素尺寸等。useEffect 可以在渲染后安全地进行这些操作。

    import React, { useRef, useEffect } from 'react';
    
    function FocusInput() {
      const inputRef = useRef(null);
    
      useEffect(() => {
        // 组件挂载后,自动聚焦到输入框
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, []); // 仅在挂载时执行一次
    
      return <input ref={inputRef} type="text" placeholder="我应该自动聚焦" />;
    }

这些只是 useEffect 的几个常见应用。它的灵活性使得它能够处理几乎所有需要在组件生命周期中执行的副作用。

React useEffect 性能优化:何时考虑 useLayoutEffect 或其他替代方案?

在使用 useEffect 时,性能优化是一个不得不考虑的话题。虽然它非常强大,但如果不加注意,也可能导致不必要的计算或渲染。

  1. useEffect vs. useLayoutEffect: 这是一个非常重要的区别,但很多人容易混淆。

    • useEffect: 在浏览器完成 DOM 更新并绘制(paint)之后异步执行。这意味着你的副作用逻辑不会阻塞浏览器绘制,用户会先看到更新后的 UI,然后你的副作用才运行。这对于大多数副作用(如数据请求、事件监听)来说是理想的,因为它避免了 UI 阻塞。
    • useLayoutEffect: 在浏览器完成 DOM 更新后,但在浏览器绘制之前同步执行。如果你的副作用需要测量 DOM 布局(例如,获取元素的宽度或位置),然后根据这些测量结果来修改 DOM,并且这些修改必须在用户看到 UI 之前完成,以避免“闪烁”或不一致的视觉效果,那么你就应该使用 useLayoutEffect。 举个例子,如果你需要根据某个元素的实际高度来调整另一个元素的位置,并且这个调整必须在浏览器绘制前完成,否则用户会看到元素先在错误的位置,然后跳到正确的位置,这时 useLayoutEffect 就是你的救星。
    import React, { useRef, useLayoutEffect } from 'react';
    
    function Tooltip({ children, position }) {
      const tooltipRef = useRef(null);
      const [style, setStyle] = useState({});
    
      useLayoutEffect(() => {
        if (tooltipRef.current) {
          // 假设我们要把 tooltip 放在 children 的正上方
          const childRect = tooltipRef.current.parentElement.getBoundingClientRect();
          const tooltipRect = tooltipRef.current.getBoundingClientRect();
    
          setStyle({
            top: childRect.top - tooltipRect.height - 10, // 向上偏移
            left: childRect.left + (childRect.width / 2) - (tooltipRect.width / 2),
            position: 'absolute'
          });
        }
      }, [children, position]); // 依赖项变化时重新计算
    
      return (
        <div style={style} ref={tooltipRef}>
          {children}
        </div>
      );
    }

    经验法则: 优先使用 useEffect。只有当你发现 UI 出现视觉上的“闪烁”或不一致,并且确定是由于 DOM 测量和修改的时机问题时,才考虑 useLayoutEffect。过度使用 useLayoutEffect 可能会阻塞 UI 渲染,影响用户体验。

  2. 依赖项的优化:useCallback 和 useMemo: 前面提到了不稳定的依赖项会导致 useEffect 不必要的重新执行。useCallback 和 useMemo 就是解决这个问题的利器。

    • useCallback 用于缓存函数实例。
    • useMemo 用于缓存计算结果或对象/数组实例。 通过它们,你可以确保传递给 useEffect 的函数或对象在依赖项没有变化时,引用保持不变,从而避免 useEffect 的过度触发。
  3. 拆分 useEffect: 如果你的一个 useEffect 里面包含了多种不相关的副作用,或者它们依赖于不同的数据,那么最好把它们拆分成多个 useEffect。这样可以提高代码的可读性,也使得 React 能够更精细地控制每个副作用的执行时机,避免不必要的耦合。

    // ❌ 不推荐:一个 useEffect 处理多种逻辑
    useEffect(() => {
      // 订阅事件
      // 获取数据
      // 设置计时器
    }, [dependencyA, dependencyB, dependencyC]);
    
    // ✅ 推荐:拆分为多个独立的 useEffect
    useEffect(() => {
      // 订阅事件
    }, [dependencyA]);
    
    useEffect(() => {
      // 获取数据
    }, [dependencyB]);
    
    useEffect(() => {
      // 设置计时器
    }, [dependencyC]);
  4. 自定义 Hook 封装: 当你的 useEffect 逻辑变得复杂且需要在多个组件中复用时,将其封装成自定义 Hook 是一个非常好的实践。这不仅能提高代码复用性,还能让组件本身的逻辑更清晰,更专注于 UI 渲染。比如,一个 useFetch 或 useEventListener 这样的自定义 Hook,就能很好地封装 useEffect 内部的复杂逻辑。

    // useFetch.js
    import { useState, useEffect } from 'react';
    
    function useFetch(url, dependencies = []) {
      const [data, setData] = useState(null);
      const [loading, setLoading] = useState(true);
      const [error, setError] = useState(null);
    
      useEffect(() => {
        const fetchData = async () => {
          setLoading(true);
          setError(null);
          try {
            const response = await fetch(url);
            if (!response.ok) throw new Error(response.statusText);
            const json = await response.json();
            setData(json);
          } catch (e) {
            setError(e);
          } finally {
            setLoading(false);
          }
        };
        fetchData();
      }, [url, ...dependencies]); // url 和额外依赖作为 useEffect 的依赖
    
      return { data, loading, error };
    }
    
    // 在组件中使用
    function MyComponent() {
      const { data, loading, error } = useFetch('/api/some-data');
      // ...
    }

    通过这些方法,我们不仅能更有效地利用 useEffect,还能写出更健壮、性能更好的 React 应用。

热门AI工具

更多
DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

SkildArt
SkildArt Hot

SkildArt是一款AI文本写作工具,一站式 AI 视觉创作平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

相关专题

更多
counta和count的区别
counta和count的区别

Count函数用于计算指定范围内数字的个数,而CountA函数用于计算指定范围内非空单元格的个数。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3628

2023.11.20

go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

4873

2025.07.29

DOM是什么意思
DOM是什么意思

dom的英文全称是documentobjectmodel,表示文件对象模型,是w3c组织推荐的处理可扩展置标语言的标准编程接口;dom是html文档的内存中对象表示,它提供了使用javascript与网页交互的方式。想了解更多的相关内容,可以阅读本专题下面的文章。

6541

2024.08.14

PHP 高并发与性能优化
PHP 高并发与性能优化

本专题聚焦 PHP 在高并发场景下的性能优化与系统调优,内容涵盖 Nginx 与 PHP-FPM 优化、Opcode 缓存、Redis/Memcached 应用、异步任务队列、数据库优化、代码性能分析与瓶颈排查。通过实战案例(如高并发接口优化、缓存系统设计、秒杀活动实现),帮助学习者掌握 构建高性能PHP后端系统的核心能力。

19353

2025.10.16

PHP 数据库操作与性能优化
PHP 数据库操作与性能优化

本专题聚焦于PHP在数据库开发中的核心应用,详细讲解PDO与MySQLi的使用方法、预处理语句、事务控制与安全防注入策略。同时深入分析SQL查询优化、索引设计、慢查询排查等性能提升手段。通过实战案例帮助开发者构建高效、安全、可扩展的PHP数据库应用系统。

453

2025.11.13

JavaScript 性能优化与前端调优
JavaScript 性能优化与前端调优

本专题系统讲解 JavaScript 性能优化的核心技术,涵盖页面加载优化、异步编程、内存管理、事件代理、代码分割、懒加载、浏览器缓存机制等。通过多个实际项目示例,帮助开发者掌握 如何通过前端调优提升网站性能,减少加载时间,提高用户体验与页面响应速度。

348

2025.12.30

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

343

2026.03.06

宝塔面板Nginx性能优化与高并发配置实践
宝塔面板Nginx性能优化与高并发配置实践

本专题围绕宝塔环境下的 Nginx 优化展开,深入讲解缓存策略、连接数调优、Gzip 压缩、静态资源加速及反向代理配置。帮助用户提升网站访问速度与并发处理能力。

1056

2026.03.25

Docker Desktop怎么设置端口映射
Docker Desktop怎么设置端口映射

本专题聚焦Docker Desktop容器端口映射与访问配置,整理从创建容器、设置端口绑定到验证本地服务访问的操作流程。针对端口被占用、映射配置错误、容器服务无法访问等情况,也提供相应的检查方向,方便用户完成端口配置。

0

2026.11.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
DBeaver驱动管理手册
DBeaver驱动管理手册

共0课时 | 0人学习

夸克AI浏览器使用手册
夸克AI浏览器使用手册

共0课时 | 0人学习

jQuery官方API文档
jQuery官方API文档

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn