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无关:
- 当
isVisible变化时,React会先执行上一次effect对应的清理函数,再执行新的effect。 - 每次effect执行时,都会捕获当前渲染周期里的
onKeyDown函数——也就是当时添加监听时用的那个函数引用。 - 当执行清理函数时,它移除的正是上一次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
相关产品推荐
相关产品推荐

