news 2026/5/25 15:25:51

react(二)useEffect 和 useRef

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
react(二)useEffect 和 useRef

useEffect

副作用

在 React 中,副作用指的是在组件渲染过程中,除了返回 JSX 之外进行的任何操作,这些操作会影响组件外部或与外部系统进行交互。

在函数式编程和 React 上下文中:
纯函数:相同的输入 ⇒ 相同的输出,无外部影响
副作用:函数执行过程中对外部环境产生的可观察变化

// 纯函数 - 无副作用functionadd(a,b){returna+b;// 只进行计算,不影响外部}// 有副作用的函数functionupdateTitle(title){document.title=title;// 影响外部 DOMconsole.log(title);// 影响外部控制台fetch('/api');// 影响网络}

常见的副作用包括:

  • 数据获取(API 调用)
  • 事件订阅(WebSocket、setTimeout、事件监听器)
  • 手动修改 DOM
  • 记录日志
  • 存储数据到 localStorage

副作用之所以特殊,是因为它们可能在不同时间执行,可能影响其他组件,并且可能导致不一致的 UI 状态。

使用 useEffect 统一管理副作用

useEffect 是 React 专门为函数组件设计的副作用管理系统。其核心关系是:
useEffect = 副作用声明 + 生命周期管理 + 清理机制

作用:

  • 分离关注点:将副作用逻辑与渲染逻辑分离
  • 生命周期模拟:在函数组件中模拟类组件的生命周期方法
  • 声明式副作用:通过依赖数组声明副作用执行的条件

useEffect 的执行机制详解

  1. 执行时机与浏览器渲染流程
  • 组件渲染
  • React 更新 DOM
  • 浏览器绘制屏幕
  • useEffect 执行

关键特性:

  • useEffect 的回调函数是异步执行的
  • 不会阻塞浏览器绘制
  • 布局和绘制之后执行(类似 componentDidMount和 componentDidUpdate)
// 不能直接在函数体执行副作用functionComponent(){// 错误:每次渲染都会执行fetch('/api/data');document.title='新标题';return<div>内容</div>;}// 必须用 useEffect 包装functionComponent(){useEffect(()=>{fetch('/api/data');// 正确执行(DOM 更新后)document.title='新标题';// 正确执行(DOM 更新后)},[]);return<div>内容</div>;}

语法:

useEffect(()=>{// 副作用逻辑return()=>{// 清理函数(可选)};},[dependencies]);// 依赖项数组(可选)关于依赖项下面有深度解析

副作用执行时机:在组件渲染到屏幕之后执行。这确保副作用不会阻塞浏览器绘制。

三种依赖模式
模式 A:无依赖数组

useEffect(()=>{console.log('每次渲染后都会执行');// 潜在性能问题:可能导致无限循环});
  • 执行时机:每次组件渲染(包括初始渲染和每次更新)后
  • 清理时机:每次执行新副作用前,执行上一次的清理函数
  • 使用场景:极少使用,通常只在需要严格同步副作用与渲染时使用

模式 B:空依赖数组 []

useEffect(()=>{console.log('仅在挂载时执行一次');return()=>{console.log('仅在卸载时执行清理');};},[]);

执行时机:

  • 初始渲染后执行一次
  • 组件卸载时执行清理函数
    等价于:类组件中的 componentDidMount+ componentWillUnmount
    常见用途:
  • 事件监听器绑定/解绑
  • 一次性数据获取
  • 第三方库初始化

模式 C:有依赖的数组 [dep1, dep2]

useEffect(()=>{console.log('依赖变化时执行');// 当 count 或 name 变化时执行return()=>{console.log('执行上一次的清理');};},[count,name]);// 依赖项

执行时机:

  • 初始渲染后执行
  • 依赖项数组中任一值发生变化时重新执行
  • 重新执行前,先执行上一次的清理函数
    依赖比较:使用 Object.is进行浅比较
    优化技巧:
// 避免对象/数组作为依赖时的无限更新const[user,setUser]=useState({id:1,name:'Alice'});// 每次渲染 user 都是新对象,会导致副作用无限执行useEffect(()=>{console.log(user);},[user]);// 不推荐// 解决方案1:提取具体值useEffect(()=>{console.log(user.id);},[user.id]);// 推荐// 解决方案2:使用 useMemoconstmemoizedUser=useMemo(()=>user,[user.id,user.name]);

useEffect 的清理机制

  1. 清理函数的作用
  • 防止内存泄漏:清除定时器、取消订阅
  • 避免状态不一致:取消未完成的异步操作
  • 资源管理:关闭连接、释放资源
  1. 清理执行的实际顺序
// 示例:多个 useEffect 的执行顺序functionComponent(){useEffect(()=>{console.log('Effect 1 - 设置');return()=>console.log('Effect 1 - 清理');},[]);useEffect(()=>{console.log('Effect 2 - 设置');return()=>console.log('Effect 2 - 清理');},[]);// 执行顺序:// 挂载时: Effect 1 - 设置 → Effect 2 - 设置// 更新时: Effect 1 - 清理 → Effect 1 - 设置 → Effect 2 - 清理 → Effect 2 - 设置// 卸载时: Effect 2 - 清理 → Effect 1 - 清理}

useEffect 的实践模式

  1. 数据获取模式
useEffect(()=>{letisMounted=true;// 解决竞态条件constfetchData=async()=>{try{constresult=awaitfetch(`/api/data/${id}`);constdata=awaitresult.json();if(isMounted){setData(data);}}catch(error){if(isMounted){setError(error);}}};fetchData();return()=>{isMounted=false;// 清理时标记组件已卸载};},[id]);
  1. 事件监听模式
useEffect(()=>{consthandleScroll=(event)=>{console.log('滚动位置:',window.scrollY);};// 节流优化constthrottledHandleScroll=throttle(handleScroll,100);window.addEventListener('scroll',throttledHandleScroll,{passive:true});return()=>{window.removeEventListener('scroll',throttledHandleScroll);};},[]);
  1. 定时器模式
useEffect(()=>{constintervalId=setInterval(()=>{setCount(prev=>prev+1);},1000);return()=>{clearInterval(intervalId);};},[]);

useRef

什么是 useRef?

作用:返回一个可变的 ref 对象,其 .current属性被初始化为传入的参数。它在整个组件的生命周期内保持不变。

特点

  • 返回一个可变对象,其 .current属性被初始化为传入的参数
  • 不触发组件重新渲染​ 当值变化时
  • 引用在组件生命周期中保持不变

常用于:

  • 直接访问和操作 DOM 元素
  • 存储不会触发重新渲染的可变值
  • 保持对某些值的持久引用

不是状态值,是储存值

基本语法

import{useRef}from'react';constrefContainer=useRef(initialValue);

主要用途

1. 访问 DOM 元素(最常见用途)

import{useEffect,useRef}from'react'exportconstTextInputFocus=()=>{constinputRef=useRef<HTMLInputElement>(null)useEffect(()=>{inputRef.current?.focus()console.log(inputRef.current);// 可以访问到input元素},[])// 组件挂载后自动聚焦输入框return(<div><input ref={inputRef}type="text"/><button>聚焦输入框</button></div>)}

2. 存储可变值(不会触发重新渲染)
存储组件实例变量

import{useEffect,useRef}from'react'exportconstTextInputFocus=()=>{constinputRef=useRef<HTMLInputElement>(null)constisMountedRef=useRef<boolean>(false)// 组件挂载后自动聚焦输入框,并记录组件是否已挂载useEffect(()=>{isMountedRef.current=trueinputRef.current?.focus()console.log(isMountedRef.current);},[])return(<div><input ref={inputRef}type="text"/><button>聚焦输入框</button></div>)}

定时器

import{useRef,useState}from'react';exportconstTimerComponent=()=>{const[seconds,setSeconds]=useState<number>(0);consttimerRef=useRef<number>(0);// 存储定时器IDconststartTimer=()=>{if(timerRef.current)return;// 如果已经有定时器,不重复创建timerRef.current=setInterval(()=>{// 定时器回调,每秒执行setSeconds(s=>s+1);},1000);console.log(timerRef.current);};conststopTimer=()=>{clearInterval(timerRef.current);timerRef.current=0;// 清除引用};return(<div><p>已过去:{seconds}</p><button onClick={startTimer}>开始</button><button onClick={stopTimer}>停止</button></div>);}

3. 存储上一次的状态或 props
React 函数组件没有内置的方法来获取上一次渲染的值,useRef可以解决这个问题。

import{useEffect,useRef,useState}from'react';exportconstPreviousValueTracker=()=>{const[value,setValue]=useState<string>('');constprevValueRef=useRef<string>('');// 存储上一个值// revValueRef.current = value; 不可以再渲染期间渲染useEffect(()=>{// 注意:这里在组件渲染后才执行prevValueRef.current=value;// 更新上一个值},[value]);// 依赖于 value 的变化consthandleChange=(e:React.ChangeEvent<HTMLInputElement>)=>{// 更新 value,触发重新渲染setValue(e.target.value);// 这里 previousValueRef.current 还没更新!};return(<><p>当前值:{value}</p><p>上一个值:{prevValueRef.current}</p><input type="text"value={value}onChange={handleChange}/></>)}

工作原理详解

// 组件渲染过程:
// 第一次渲染: value = ‘’, previousValueRef.current = ‘’
// 用户输入 “a”:
// → 触发 onChange: setValue(‘a’)
// → 组件重新渲染
// → useEffect 运行(在渲染后): previousValueRef.current = ‘a’
// 第二次渲染: value = ‘ab’, previousValueRef.current = ‘a’(但显示的是上一次的值)

使用 ref 存储状态/props 的关键要点

  1. 时机很重要:ref 的赋值应该在 useEffect中,而不是在渲染函数体中
  2. 异步更新:ref 的更新是同步的,但读取可能在 React 渲染周期中的不同时间点
  3. 不要依赖它来渲染:ref 的值变化不会触发渲染,所以不能用它来驱动 UI
  4. 组件卸载时清理:如果 ref 存储了资源(如定时器),需要在卸载时清理

与 useState 的区别

特性useRefuseState
触发重新渲染不会
值更新时机立即更新在下一次渲染时更新
存储位置组件实例组件状态
异步更新同步异步

总结:如果需要值变化时触发组件重新渲染,使用 useState;如果不需要,使用 useRef。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/3 6:40:39

Windows环境下KingbaseES数据库快速部署与ksql高效连接实战教程

1. Windows下KingbaseES数据库快速部署指南 第一次接触KingbaseES的朋友可能会觉得数据库安装很复杂&#xff0c;其实在Windows环境下部署KingbaseES比想象中简单得多。我去年在客户现场部署过几十套KingbaseES环境&#xff0c;总结出了一套最稳妥的安装流程。下面就把这个&quo…

作者头像 李华
网站建设 2026/4/1 10:01:33

OpenClaw Tokens消耗优化1-分层路由机制

一、背景&#xff1a;大模型 Agent 的“固定成本”困境 一个功能完备的 AI Agent&#xff08;如 OpenClaw、Claude Code 等&#xff09;通常会集成大量工具、技能、系统提示和文件。例如&#xff1a; 工具定义 20 个&#xff08;每个工具包含名称、描述、参数结构&#xff09;…

作者头像 李华
网站建设 2026/4/4 8:15:01

解放双手:多平台网课自动化学习效率工具全攻略

解放双手&#xff1a;多平台网课自动化学习效率工具全攻略 【免费下载链接】auto-play-course 简单好用的刷课脚本[支持平台:职教云,智慧职教,资源库] 项目地址: https://gitcode.com/gh_mirrors/hc/auto-play-course 你是否曾遇到这样的困境&#xff1a;面对堆积如山的…

作者头像 李华
网站建设 2026/4/4 8:14:59

500+格式通解:UniExtract2全能文件提取工具深度指南

500格式通解&#xff1a;UniExtract2全能文件提取工具深度指南 【免费下载链接】UniExtract2 Universal Extractor 2 is a tool to extract files from any type of archive or installer. 项目地址: https://gitcode.com/gh_mirrors/un/UniExtract2 UniExtract2是一款专…

作者头像 李华
网站建设 2026/4/7 16:33:08

DDrawCompat:如何在Windows 11上轻松解决老游戏兼容性问题?

DDrawCompat&#xff1a;如何在Windows 11上轻松解决老游戏兼容性问题&#xff1f; 【免费下载链接】DDrawCompat DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11 项目地址: https://gitcode.com/gh_m…

作者头像 李华