大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- useEffect 的本质是什么?
- 三种 useEffect,你必须先分清
- 第一类:只执行一次的 effect
- 第二类:依赖变化驱动的 effect
- 第三类:同步外部系统的 effect
- 依赖数组的设计原则
- 原则 1:依赖数组不是“控制执行次数”的工具
- 原则 2:effect 里用到的“动态值”,都必须在依赖里
- 原则 3:不要让 effect 承担“派生状态”
- 依赖数组里的“高频雷区”
- 雷区 1:对象 / 数组作为依赖
- 雷区 2:函数作为依赖
- 雷区 3:依赖数组越来越长
- eslint 的 useEffect 提示,该不该信?
- 一个真实 Demo:重构依赖地狱
- 原始写法
- 重构后
- 什么时候你根本不该用 useEffect?
- 总结
前言
如果你写 React / RN 写了一段时间,大概率经历过下面这些瞬间:
- eslint 一直提示你“依赖缺失”,你直接关掉
- 依赖数组一加,页面就开始疯狂刷新
- 不加依赖,逻辑又不稳定
- 同事问你“这个 effect 为啥这么写”,你自己也说不清楚
useEffect 真正让人痛苦的,从来不是 API,而是依赖数组。
这篇文章的目标只有一个:
让你以后每写一个 useEffect,都知道依赖该怎么设计,而不是靠感觉。
useEffect 的本质是什么?
先把一句最重要的话说在前面:
useEffect = 当某些“值变化时”,我要去做一件“副作用”的事情
拆开来看只有两部分:
- 依赖数组:哪些值变化,会触发这段逻辑
- effect 函数:真正的副作用行为
很多问题,本质上都是:
- effect 在做不该它做的事
- 依赖数组在承担业务逻辑
三种 useEffect,你必须先分清
不是所有 useEffect 都是同一类。
第一类:只执行一次的 effect
useEffect(()=>{init()},[])适用场景:
- 初始化
- 订阅
- 事件监听
关键前提:
effect 内不能依赖“会变化的值”
如果你在这里用到了 props / state,
那这个 effect迟早会出问题。
第二类:依赖变化驱动的 effect
useEffect(()=>{fetchData(id)},[id])这是最“正宗”的 useEffect 使用方式。
判断标准很简单:
effect 内用到的、且可能变化的值,必须写进依赖
第三类:同步外部系统的 effect
useEffect(()=>{navigation.setOptions({title})},[title])这种 effect 本质是:
- React 世界 → 原生 / 外部世界
它不是业务逻辑,而是桥梁。
依赖数组的设计原则
原则 1:依赖数组不是“控制执行次数”的工具
很多人这样写:
useEffect(()=>{doSomething(a)// eslint-disable-next-line},[])本质是在说:
“我知道 a 会变,但我不想你再跑一次”
这是逻辑不一致,不是优化。
原则 2:effect 里用到的“动态值”,都必须在依赖里
useEffect(()=>{console.log(user.name)},[])这是一个隐性 bug:
- user 更新了
- effect 里的 user 还是旧的
正确写法:
useEffect(()=>{console.log(user.name)},[user.name])原则 3:不要让 effect 承担“派生状态”
反例:
useEffect(()=>{setTotal(price*count)},[price,count])这不是副作用,这是计算。
正确做法:
consttotal=price*count如果你在 effect 里 setState,先问自己:
这个状态是不是可以算出来?
依赖数组里的“高频雷区”
雷区 1:对象 / 数组作为依赖
useEffect(()=>{doSomething(config)},[config])如果 config 每次 render 都是新对象:
- effect 每次都会触发
解决方式:
constmemoConfig=useMemo(()=>config,[a,b])或者干脆拆成基础依赖。
雷区 2:函数作为依赖
useEffect(()=>{fn()},[fn])大多数情况下,fn 每次 render 都变。
解决方案:
constfn=useCallback(()=>{...},[deps])雷区 3:依赖数组越来越长
useEffect(()=>{...},[a,b,c,d,e,f])这是设计信号,不是语法问题。
通常意味着:
- effect 逻辑过重
- 该拆了
eslint 的 useEffect 提示,该不该信?
一句结论:
eslint 不是错的,是你的 effect 写错了
eslint 提示你补依赖,通常说明:
- effect 里混入了不该放进去的逻辑
- 你在 effect 里“偷懒”
正确处理方式不是关规则,而是重构 effect。
一个真实 Demo:重构依赖地狱
原始写法
useEffect(()=>{setLoading(true)fetchList(userId,filter).then(res=>{setList(res)setLoading(false)})},[userId,filter])问题:
- 逻辑复杂
- loading 被 effect 控制
重构后
functionuseList(userId,filter){const[list,setList]=useState([])useEffect(()=>{fetchList(userId,filter).then(setList)},[userId,filter])returnlist}页面里:
constlist=useList(userId,filter)effect 变简单,依赖也自然。
什么时候你根本不该用 useEffect?
你可以直接问自己这三个问题:
- 这是副作用吗?
- 这是状态同步吗?
- 这是外部系统交互吗?
如果都不是,
99% 情况下你不需要 useEffect。
总结
useEffect 难,不是因为 API 难,而是因为:
- 我们习惯把“不确定逻辑”塞进去
- 用依赖数组“控制执行次数”
- 把计算当副作用
记住这三句话:
- effect 只做副作用
- 依赖数组描述“变化关系”,不是执行策略
- 依赖写不清楚,说明设计有问题
当你开始用“设计视角”看 useEffect,
你会发现它反而是一个非常诚实的工具。