React useLayoutEffect vs useEffect:页面闪一下的元凶与正确修法

发布时间:2026/7/20 14:06:07

React useLayoutEffect vs useEffect:页面闪一下的元凶与正确修法
React useLayoutEffect vs useEffect:页面闪一下的元凶与正确修法你写了个 Tooltip,根据触发元素的位置动态算坐标。跑起来功能没问题,但每次弹出时它会先在左上角闪现一下,再跳到正确位置。这种肉眼可见的抖动,十有八九是因为你用了useEffect而不是useLayoutEffect。这两个 Hook 签名一模一样,90% 的场景可以互换,但剩下 10% 用错就会闪屏。这篇讲清它们的执行时机差异,以及什么时候必须换成useLayoutEffect。核心区别:在浏览器绘制之前还是之后React 更新 DOM 的大致流程是:执行组件函数 → 提交 DOM 变更 →浏览器绘制到屏幕→ 用户看到画面。两个 Hook 的差别就在于相对「绘制」这一步的时机:useEffect:在浏览器绘制之后异步执行。用户已经看到了这一帧,你的 effect 才跑。useLayoutEffect:在 DOM 变更后、浏览器绘制之前同步执行。它跑完,用户才看到画面。关键推论:如果你的 effect 里读取 DOM 布局(尺寸、位置)然后又修改 DOM,用useEffect会让用户先看到「修改前」的一帧、再看到「修改后」的一帧——这就是闪屏。而useLayoutEffect在绘制前就改完了,用户只看到最终结果。复现闪屏:一个定位 Tooltipimport { useEffect, useRef, useState } from react; function Tooltip({ targetRef, children }) { const tipRef useRef(null); const [pos, setPos] useState({ top: 0, left: 0 }); useEffect(() { // 读取触发元素的位置,算出 tooltip 该放哪 const rect targetRef.current.getBoundingClientRect(); setPos({ top: rect.bottom 8, left: rect.left }); // 问题:此时浏览器已经把 tooltip 画在了 {0,0}, // setPos 触发重渲染后才移到正确位置 —— 用户看到它跳了一下 }, [targetRef]); return ( div ref{tipRef} style{{ position: fixed, top: pos.top, left: pos.left }} {children} /div ); }初始pos是{0,0},浏览器先把 tooltip 绘制在左上角,useEffect绘制之后才跑、才 setPos、才移到正确位置。中间那一帧的左上角残影,就是用户看到的闪烁。修法:换成 useLayoutEffect只改一个词:import { useLayoutEffect, useRef, useState } from react; function Tooltip({ targetRef, children }) { const tipRef useRef(null); const [pos, setPos] useState({ top: 0, left: 0 }); useLayoutEffect(() { const rect targetRef.current.getBoundingClientRect(); setPos({ top: rect.bottom 8, left: rect.left }); // useLayoutEffect 在绘制前同步执行,setPos 引发的重渲染也在绘制前完成, // 浏览器一次性画出最终位置 —— 没有中间帧,不闪 }, [targetRef]); return ( div ref{tipRef} style{{ position: fixed, top: pos.top, left: pos.left }} {children} /div ); }React 会在useLayoutEffect里的 setState 之后、绘制之前完成重渲染,所以用户直接看到正确位置。这就是它存在的意义:同步测量 修改 DOM,消除中间帧。那什么时候该用哪个?判断很简单,问一句:这个 effect 会不会在绘制前读取布局并修改视觉结果?用useLayoutEffect的典型场景(都涉及「测量后立即改样式」):读取元素尺寸/位置来定位(Tooltip、Popover、下拉菜单)。根据内容高度做动画的初始值(比如手风琴展开)。滚动位置的同步恢复(避免先跳到顶部再跳回)。其余情况一律用useEffect,包括:// 数据请求 —— 和布局无关,用 useEffect useEffect(() { fetch(/api/data).then(/* ... */); }, []); // 订阅事件、埋点、定时器 —— 都用 useEffect useEffect(() { const id setInterval(tick, 1000); return () clearInterval(id); }, []);别滥用:useLayoutEffect 会阻塞绘制既然useLayoutEffect能防闪屏,为什么不全用它?因为它是同步、阻塞绘制的:浏览器必须等它(以及它引发的重渲染)跑完才能画下一帧。如果你在里面干重活(复杂计算、大量 DOM 操作),会直接拖慢首屏、造成卡顿。原则:默认用useEffect,只有出现闪屏、或明确需要绘制前测量 DOM 时,才换useLayoutEffect。不要「为了保险」全部用它。附带一个 SSR 警告如果你用 Next.js 等服务端渲染,useLayoutEffect在服务端会打印一条警告:「useLayoutEffect does nothing on the server」。因为服务端没有真实 DOM 可测量。处理方式:确认这段逻辑确实只需在客户端跑,可以用「客户端挂载后才渲染」的模式规避,或把纯副作用挪回useEffect。不要为了消警告无脑改回useEffect而让闪屏复活——先判断它到底需不需要绘制前执行。小结useEffect在浏览器绘制后异步执行,useLayoutEffect在绘制前同步执行。「读取 DOM 布局 → 修改视觉结果」的 effect 用useEffect会闪一帧,换useLayoutEffect消除中间帧。数据请求、订阅、定时器等和布局无关的副作用,一律用useEffect。useLayoutEffect阻塞绘制,别滥用;里面别干重活。SSR 下useLayoutEffect会告警,按「是否真需要绘制前执行」来决定处理方式,而不是无脑改回去。一句话记忆点:画面闪一下、元素跳到最终位置,先想想那个 effect 是不是该从 useEffect 换成 useLayoutEffect。

相关新闻

UE5数字孪生中基于SuperMap SDK的横断面分析实现

UE5数字孪生中基于SuperMap SDK的横断面分析实现

2026/7/20 14:06:07

1. 项目概述:当高保真三维GIS遇上游戏引擎最近在做一个智慧城市相关的数字孪生项目,客户要求在UE5(Unreal Engine 5)里不仅能流畅地浏览大范围、高精度的实景三维模型,还得能进行专业的空间分析,其中“横断…

Python logging 正确配置:告别 print 调试与日志重复打印

Python logging 正确配置:告别 print 调试与日志重复打印

2026/7/20 14:06:07

Python logging 正确配置:告别 print 调试与日志重复打印 线上服务出问题,你翻日志却发现:要么啥都没有(全靠 print 早被丢进 stdout 黑洞),要么同一条日志打了三遍,要么日志文件涨到几个 G 把磁盘撑爆。Python 的 logging 模块功能齐全,但默认行为反直觉,踩坑的人比用对的人多…

CTF命令注入实战:绕过空格与关键词过滤的Shell技巧

CTF命令注入实战:绕过空格与关键词过滤的Shell技巧

2026/7/20 14:06:07

1. 项目概述:一次典型的命令注入挑战在CTF(Capture The Flag,夺旗赛)的Web安全赛道上,命令注入(Command Injection)是经久不衰的经典题型。它模拟了攻击者如何通过Web应用接口,将恶意…

如何用Python自动化工具实现B站会员购抢票:终极完整指南

如何用Python自动化工具实现B站会员购抢票:终极完整指南

2026/7/21 8:07:14

如何用Python自动化工具实现B站会员购抢票:终极完整指南 【免费下载链接】biliTickerBuy b站会员购购票辅助工具 项目地址: https://gitcode.com/GitHub_Trending/bi/biliTickerBuy B站会员购抢票总是一票难求?手动操作总是慢人一步?今…

基于STM32单片机智能无线点餐物联网餐厅菜谱无线WiFi/蓝牙/视频监控APP设计DIY-T168

基于STM32单片机智能无线点餐物联网餐厅菜谱无线WiFi/蓝牙/视频监控APP设计DIY-T168

2026/7/21 8:07:14

本系统由STM32F103C8T6单片机核心板、无线蓝牙/WIFI模块-可选、TFT1.44寸彩屏液晶显示电路、蜂鸣器报警驱动电路、按键电路及电源电路。注意视频监控及WIFI套餐才拥有视频监控(含WIFI功能)!【1】硬件相当于服务员手持终端点餐器,上位机APP(即手机)相当于饭店结算下单…

Windows Hello生物识别技术解析与配置指南

Windows Hello生物识别技术解析与配置指南

2026/7/21 8:07:14

1. Windows Hello生物识别技术解析Windows Hello是微软在Windows 10中引入的生物特征认证系统,它彻底改变了传统密码验证方式。作为系统级的身份验证框架,它通过三种主要方式实现安全登录:指纹识别、面部识别和PIN码。这项技术并非简单的硬件…

C++实现MFCC特征提取:从原理到工程实践

C++实现MFCC特征提取:从原理到工程实践

2026/7/21 8:07:14

1. 项目概述:从声音到数字的桥梁做语音相关的项目,无论是识别、合成还是分类,第一步永远绕不开特征提取。你把一段原始音频波形直接扔给模型,效果通常不会太好,因为波形数据包含了太多冗余信息,比如环境噪音…

数据恢复工具选择与实战技巧指南

数据恢复工具选择与实战技巧指南

2026/7/21 8:07:14

1. 文件恢复工具的核心价值与选择逻辑 当你在深夜赶工的重要文档被误删,或是清理磁盘时不小心格式化了存有珍贵照片的分区,那种头皮发麻的窒息感我深有体会。作为经历过数十次数据抢救的老手,可以明确告诉你:90%的误删文件都能完整…

嵌入式系统高速启动:GPIO与XINTF并行引导模式深度解析

嵌入式系统高速启动:GPIO与XINTF并行引导模式深度解析

2026/7/21 7:57:14

1. 项目概述与引导模式核心价值 在嵌入式系统开发中,Bootloader(引导加载程序)是系统上电后运行的第一段代码,其重要性不亚于应用程序本身。它就像一位尽职尽责的“系统唤醒师”,负责在CPU从复位状态苏醒后&#xff0c…

微服务进阶:服务网格与Istio

微服务进阶:服务网格与Istio

2026/7/21 5:45:57

541|微服务进阶:服务网格与Istio 上篇文章我们聊了微服务的基本概念和拆分方法。 但微服务多了,问题也多了: 服务之间怎么通信? 怎么监控每个服务的调用链路? 熔断、限流、重试怎么做? 安全认证怎么统一? 以前这些都靠SDK库(比如Hystrix、Feign),每个服务都要集成…

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

2026/7/20 2:33:13

一、零售门店全域协同业务背景与行业痛点 1.1 门店超级终端设备矩阵(连锁便利店/商超标准配置) 自助收银Kiosk一体机:顾客结算、自助核销优惠券、商品素材预览;运营折叠平板:店长后台商品上新、图片录入、活动配置、…

噗叽短视频界面分析

噗叽短视频界面分析

2026/7/21 3:09:32

1 和小红书类似,可以采用类似判断方法------------其实他比小红书好判断,因为他没有图片,控件位置几乎是固定的,都不用判断------------2 因为他没有点赞按钮------------而且几乎所有控件位置都是完全一样的,所以我就…

GraphRAG Local + Ollama:微软知识图谱本地化

GraphRAG Local + Ollama:微软知识图谱本地化

2026/7/21 0:06:35

普通 RAG 有个老毛病:你问它「这堆文档整体在讲什么」,它答不上来。因为它只会把问题切成向量,去几十个文本块里捞最相似的几段拼给模型看。可「整体讲什么」这种问题,答案根本不在任何单独一段里——它散在全篇的联系里。 微软的…

AI 数据产品化思考:让分析能力变成可售卖的数据服务

AI 数据产品化思考:让分析能力变成可售卖的数据服务

2026/7/21 0:06:35

AI 数据产品化思考:让分析能力变成可售卖的数据服务 大家好,我是朱大喜。这周一直在复盘具体的项目和技术,最后一篇聊点不一样的东西——数据产品化。做了这么多年数据分析,我发现一个规律:能卖出去的从来不是"分…

基于人机协作的 AI 研发新体系架构:从 Harness 工程到 Loop 工程实践

基于人机协作的 AI 研发新体系架构:从 Harness 工程到 Loop 工程实践

2026/7/21 0:06:35

本文完整呈现了企业级 AI Coding 落地的核心方法论:从 Harness 工程的微观/宏观定义,到 Loop 工程的六大构建模块,再到基于 SDD(规范驱动开发)的工程化落地路径。干货较多,建议收藏细读。 我从 22 年开始就…