使用socket.io实现数据库变更推送时useEffect重复触发两次如何解决
问题根因
你遇到的重复打印、useEffect异常执行的核心原因是socket事件监听器重复注册:
- 你将监听
posts事件的useEffect依赖设置为posts状态,每次posts更新都会重新执行该useEffect,注册一个新的posts事件监听器 - 你没有为该useEffect添加清理逻辑,旧的监听器不会被销毁,叠加的监听器会在每次socket推送时同时触发,就会出现多次打印的情况
- 你当前在回调中直接引用外部
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
相关产品推荐
相关产品推荐

