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

使用socket.io实现数据库变更推送时useEffect重复触发两次如何解决

问题根因

你遇到的重复打印、useEffect异常执行的核心原因是socket事件监听器重复注册:

  1. 你将监听posts事件的useEffect依赖设置为posts状态,每次posts更新都会重新执行该useEffect,注册一个新的posts事件监听器
  2. 你没有为该useEffect添加清理逻辑,旧的监听器不会被销毁,叠加的监听器会在每次socket推送时同时触发,就会出现多次打印的情况
  3. 你当前在回调中直接引用外部posts状态,导致必须将posts加入依赖,形成了“更新posts→重新注册监听器→多次触发”的恶性循环

修复方案

1. 重写socket监听useEffect

用setPosts的函数式更新语法,不需要依赖外部posts状态,同时添加监听器清理逻辑,useEffect仅在组件挂载时执行一次即可:

// 替换你原来的socket监听useEffect
useEffect(() => {
  const handlePostChange = (item) => {
    setPosts(prevPosts => {
      // 可选添加去重逻辑,避免同一个帖子被重复添加(比如前端提交后接口已更新,socket又推送一次的场景)
      const postExisted = prevPosts.some(post => post._id === item._id);
      if (postExisted) {
        // 如果是更新操作,可以替换旧帖子数据
        return prevPosts.map(post => post._id === item._id ? item : post);
      }
      // 新增帖子直接追加
      return [...prevPosts, item];
    })
  }

  // 注册监听器
  socket.on('posts', handlePostChange);

  // 清理逻辑:组件卸载或依赖变动时移除旧监听器
  return () => {
    socket.off('posts', handlePostChange);
  }
// 依赖为空,仅执行一次
}, [])

2. 可选服务端校验(按需调整)

你当前服务端开启了两个独立的Change Stream监听,虽然按你的逻辑insert和update会分开触发,但可以合并为一个监听减少资源占用:

const db = mongoose.connection;
db.once('open', () => {
    console.log(chalk.blueBright("Setting change streams"));
    // 合并为一个watch,同时支持insert和update
    db.collection('posts').watch({ fullDocument: 'updateLookup' })
    .on('change', change => {
        if(change.operationType === 'insert'){
            console.log(chalk.yellowBright('INSERTED'))
            io.emit('posts', change.fullDocument);
        } else if(change.operationType === 'update'){
            console.log(chalk.yellowBright('UPDATED'));
            io.emit('posts', change.fullDocument);
        }
    });
}); 

修复后重复触发的问题就会完全解决,不需要依赖React.StrictMode相关的配置调整。

内容的提问来源于stack exchange,提问作者james williams

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 23:06:03