面试官:useEffect 为什么总背刺?我:闭包、ref 和依赖数组的三角恋

🧠 系列前言:

面试题千千万,我来帮你挑重点。每天一道,通勤路上、蹲坑时、摸鱼中,技术成长不设限!本系列主打幽默 + 深度 + 面霸必备语录,你只管看,面试场上稳拿 offer!

💬 面试官发问:

"说说你对 useEffect 的理解?依赖项为什么总是填不对?闭包陷阱怎么解?"

哎哟妈呀,这题一出,多少前端人梦回凌晨 2 点 debug 页面逻辑,满脸问号:我明明写对了,怎么又触发了?

🎯 快答区(面霸速记版)

  • useEffect 是一个副作用钩子,默认在组件渲染后执行
  • 依赖项数组控制副作用的触发时机
  • 如果你不理解闭包和引用变化useEffect 就会变身背刺小王子
  • React 的规则是:只要依赖项变了就重新执行

所以填错依赖数组 = 自找 bug

🧬 useEffect 的爱恨情仇

🪝 一、useEffect 到底干嘛的?

在类组件中我们有:

js 复制代码
componentDidMount() // 初始化执行一次
componentDidUpdate() // 每次更新都执行
componentWillUnmount() // 组件卸载时执行清理

而在函数组件里,一个 useEffect 全包了:

js 复制代码
useEffect(() => {
  console.log('副作用逻辑来咯~')

  return () => {
    console.log('组件卸载 or 依赖变化,清理啦!')
  }
}, [依赖项])

你可以认为:

useEffect = didMount + didUpdate + willUnmount 的组合技。

🎭 二、为什么依赖项这么重要?

你写副作用:

js 复制代码
useEffect(() => {
  fetchData(keyword)
}, [])

看起来没毛病吧?但 keyword 改了,页面没更新,debug 一看:

啊这......你把 keyword 忘写进依赖数组了!

React 的机制是:

只要依赖数组里的值发生变化useEffect 就重新执行。

而且还有 ESLint 小助手在旁边耳语:

"你漏了依赖项,要不要加上?"

别不信邪,真不加,等着被 bug 追着打。

🧟‍♂️ 三、闭包 + useEffect = 鬼打墙现场

来看经典误区:

js 复制代码
const [count, setCount] = useState(0)

useEffect(() => {
  const timer = setInterval(() => {
    console.log(count) // 👈 永远是 0!
  }, 1000)
}, [])

你以为能打印 count 的实时值?结果它永远是 0。为啥?

闭包记住的是第一次的 count 值,后续不会变。

React 不会每次都重新创建这个函数,它只在第一次 [] 时执行了一次副作用。

✅ 正确解法 1:依赖更新版本

js 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log(count)
  }, 1000)

  return () => clearInterval(timer)
}, [count]) // 每次 count 变化都重新注册

但这样每次都清除+重新 setInterval,其实效率不高。

✅ 正确解法 2:使用 ref 保存可变值

js 复制代码
const countRef = useRef(count)

useEffect(() => {
  countRef.current = count
}, [count])

useEffect(() => {
  const timer = setInterval(() => {
    console.log(countRef.current) // 永远拿到最新值
  }, 1000)

  return () => clearInterval(timer)
}, [])

完美解决闭包问题,让副作用逻辑始终拿到最新值。

🕳 四、useEffect 执行时机:同步还是异步?

很多人以为 useEffect 是异步的,其实更准确地说是:

useEffect在浏览器完成 paint 之后 执行的副作用,也就是 非阻塞渲染

🎥 补充一个:

  • useEffect页面绘制后执行
  • useLayoutEffectDOM 变更后、页面绘制前同步执行(可能会阻塞渲染)

一般推荐默认用 useEffect,只有你要测量 DOM 或强制修改布局时,才上 useLayoutEffect

🎯 五、React 官方建议怎么写依赖项?

✅ 尽可能声明清晰依赖

js 复制代码
useEffect(() => {
  fetchData(keyword)
}, [keyword])

❌ 不推荐写成这样:

js 复制代码
useEffect(() => {
  fetchData(keyword)
}, []) // 靠闭包?你会后悔的

✅ 对象依赖,记得 memo

js 复制代码
const filter = useMemo(() => ({ name }), [name])

useEffect(() => {
  fetchData(filter)
}, [filter])

避免每次都触发,因为 { name } 每次都是新对象。

🎓 装 X 语录(限时使用)

"useEffect 的本质是响应式副作用收集器,依赖数组的变化驱动副作用重跑。"
"闭包陷阱其实是 JS 的机制,不是 React 的锅。ref 是解决数据脱离组件周期的利器。"
"副作用的清理逻辑相当于生命周期中的 willUnmount,能防止内存泄漏和状态污染。"

说完记得压低语气、语速慢一点,表现你是"老油条 + 热爱原理派"。

✅ 总结一句话

useEffect = 渲染之后的副作用管理器,依赖数组驱动重跑,闭包问题靠 ref 或更新依赖解决

写对它,你是高手;写错它,它就是你项目里的定时炸弹💣。

🔮 明日预告

明天我们聊聊 useCallbackuseMemo,它们到底是性能优化神器,还是"性能幻想剂"?怎么用才能不白费 CPU?⚙️

📌 点赞 + 收藏 + 关注系列,React Hook 不再"Hook"住你!

相关推荐
弓.长.7 分钟前
ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-svg(CAPI) — 矢量图形组件
react native·react.js·harmonyos
军军君0114 分钟前
Three.js基础功能学习十五:智能黑板实现实例二
开发语言·前端·javascript·vue.js·3d·threejs·三维
IT枫斗者21 分钟前
构建具有执行功能的 AI Agent:基于工作记忆的任务规划与元认知监控架构
android·前端·vue.js·spring boot·后端·架构
hotlinhao22 分钟前
Nginx rewrite last 与 redirect 的区别——Vue history 模式短链接踩坑记录
前端·vue.js·nginx
ZC跨境爬虫25 分钟前
海南大学交友平台开发实战day7(实现核心匹配算法+解决JSON请求报错问题)
前端·python·算法·html·json
下北沢美食家28 分钟前
CSS面试题2
前端·css
weixin_4617694035 分钟前
npm create vue@latest 错误
前端·vue.js·npm
WindrunnerMax36 分钟前
从零实现富文本编辑器#13-React非编辑节点的内容渲染
前端·架构·github
四千岁36 分钟前
Ollama+OpenWebUI 最佳组合:本地大模型可视化交互方案
前端·javascript·后端
写不来代码的草莓熊38 分钟前
el-date-picker ,自定义输入数字自动转换显示yyyy-mm-dd HH:mm:ss格式
前端·javascript·vue.js