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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 21:53:23