React中EventEmitter失效问题:事件驱动的setState()执行异常
问题分析与修复方案
1. 问题原因
这是典型的闭包陷阱导致的状态异常:
- 你的
useEffect依赖数组是空的,组件挂载时只会执行一次内部逻辑,此时绑定的事件监听器会捕获到初始状态的numbers(空数组)。 - 后续组件状态更新时,这个监听器并不会重新创建,里面的
numbers始终是第一次挂载时的空数组。 - 第二次点击添加按钮时,执行的是
setNumbers([...[], 新数字]),相当于把数组重置为只包含新数字的数组,看起来像是替换了之前的元素,而非追加。
2. 修复方案(保留EventEmitter)
方案1:使用函数式更新状态(推荐)
React的setState支持传入函数,该函数会接收当前最新的状态值作为参数,完全避开闭包捕获旧状态的问题:
import { useEffect, useState } from 'react' import { EventEmitter } from 'events' const emitter = new EventEmitter() export default () => { const [numbers, setNumbers] = useState<number[]>([]) useEffect(() => { // 定义独立的处理函数,方便后续清理 const handleAdd = (data: number) => { // 使用函数式更新,prev是当前最新的numbers数组 setNumbers(prev => [...prev, data]) } const handleRemove = (data: number) => { setNumbers(prev => prev.filter(item => item !== data)) } emitter.on('add', handleAdd) emitter.on('remove', handleRemove) // 组件卸载时移除监听器,防止内存泄漏 return () => { emitter.off('add', handleAdd) emitter.off('remove', handleRemove) } }, []) // 依赖数组保持为空,因为函数式更新不依赖外部的numbers return ( <div> {numbers.length} {numbers.map(n => <div>{n}</div>)} <button type='button' onClick={() => emitter.emit('add', Math.floor(Math.random() * 100))}>Add a random number</button> <button type='button' onClick={() => emitter.emit('remove', numbers[numbers.length - 1])}>Remove the last number</button> </div> ) }
方案2:添加状态依赖并清理旧监听器
如果业务逻辑必须依赖外部的numbers变量,可以将numbers加入useEffect的依赖数组,同时每次依赖变化时清理旧监听器再绑定新的:
import { useEffect, useState } from 'react' import { EventEmitter } from 'events' const emitter = new EventEmitter() export default () => { const [numbers, setNumbers] = useState<number[]>([]) useEffect(() => { const handleAdd = (data: number) => { setNumbers([...numbers, data]) } const handleRemove = (data: number) => { setNumbers(numbers.filter(item => item !== data)) } emitter.on('add', handleAdd) emitter.on('remove', handleRemove) // 清理旧监听器,避免重复绑定和内存泄漏 return () => { emitter.off('add', handleAdd) emitter.off('remove', handleRemove) } }, [numbers]) // 依赖数组加入numbers,每次状态变化时重新绑定监听器 return ( <div> {numbers.length} {numbers.map(n => <div>{n}</div>)} <button type='button' onClick={() => emitter.emit('add', Math.floor(Math.random() * 100))}>Add a random number</button> <button type='button' onClick={() => emitter.emit('remove', numbers[numbers.length - 1])}>Remove the last number</button> </div> ) }
此方案每次状态变化都会重新绑定监听器,性能略逊于方案1,仅在必要时使用。
内容的提问来源于stack exchange,提问作者TrulyBright
相关产品推荐
相关产品推荐

