You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React组件卸载机制探究:return清理函数为何能正常工作?

React useEffect清理函数与事件监听移除的差异问题

我在React组件状态更新过程中发现一个现象:用useEffect返回清理函数的方式移除事件监听可以正常工作,但改用if-else逻辑直接添加/移除时却失效。

正常工作的代码(清理函数方式)

const [isVisible, setVisibility] = useState(false)

const onKeyDown = (event) => { console.log(event) }

useEffect(() => {
  window.addEventListener('keydown', onKeyDown)

  return () => { window.removeEventListener('keydown', onKeyDown) }
}, [isVisible])

return (
  <button onClick={() => setVisibility(!isVisible)}>Click me!</button>
)

失效的代码(if-else方式)

const [isVisible, setVisibility] = useState(false)

const onKeyDown = (event) => { console.log(event) }

useEffect(() => {
  if(isVisible) window.addEventListener('keydown', onKeyDown)
  else window.removeEventListener('keydown', onKeyDown) 
}, [isVisible])

return (
  <button onClick={() => setVisibility(!isVisible)}>Click me!</button>
)

我已经知道if-else失效的原因是状态变化触发重渲染后,onKeyDown会被重新创建,新旧函数引用不匹配导致无法移除监听。但我想知道:为什么用return返回清理函数的方式就能正常工作?是React保存了同一个handler函数吗?


问题解答

核心原因是useEffect的执行机制+闭包捕获,和React是否保存handler无关:

  1. 当isVisible变化时,React会先执行上一次effect对应的清理函数,再执行新的effect。
  2. 每次effect执行时,都会捕获当前渲染周期里的onKeyDown函数——也就是当时添加监听时用的那个函数引用。
  3. 当执行清理函数时,它移除的正是上一次effect添加的那个监听(因为清理函数里的onKeyDown和添加时的是同一个引用),自然能匹配成功。

举个具体流程例子:

  • 第一次点击按钮,isVisible变为true:执行当前effect,用此时的onKeyDownA添加监听。
  • 第二次点击按钮,isVisible变为false:React先执行上一次effect的清理函数,这个清理函数里的onKeyDown是onKeyDownA,所以能正确移除之前添加的监听;然后再执行新的effect(此时isVisible为false,不会添加新监听)。

而if-else的方式里,当isVisible从true变false时,新的effect里的onKeyDown是重新创建的onKeyDownB,用它去移除onKeyDownA添加的监听,因为函数引用不同,所以移除失败。

内容的提问来源于stack exchange,提问作者AMRITESH GUPTA

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:15:51