React中正确使用socket.on:爬虫项目Socket事件监听异常问题
解决React中Socket.io事件逐个监听的问题
我来帮你分析下问题所在,然后给出针对性的修复方案:
问题根源
- Socket实例重复创建:你在组件顶层直接写
socket = io(ENDPOINT),这会导致组件每次重新渲染时都生成新的Socket连接,之前绑定的事件监听会直接失效。 - 闭包导致状态更新异常:
useEffect的依赖是空数组,里面引用的crawlingBlog始终是组件初始化时的空数组,每次concat操作都是基于旧值,无法正确累积新收到的博客数据。 - 缺少监听清理逻辑:组件卸载时没有移除Socket监听、断开连接,可能引发内存泄漏问题。
修复后的完整代码
import { useState, useEffect, useRef } from 'react'; import io from 'socket.io-client'; const App = () => { const [crawlingBlog, setCrawlingBlog] = useState([]); const [crawledBlogs, setCrawledBlogs] = useState([]); const [timeRecords, setTimeRecords] = useState([]); const socketRef = useRef(null); // 用useRef保存socket实例,避免重复创建 const ENDPOINT = 'http://localhost:4000'; // 记得加上http协议头 useEffect(() => { // 初始化socket连接 socketRef.current = io(ENDPOINT); // 定义各事件的处理函数 const handleCrawling = (blog) => { // 用函数式更新避免闭包问题 setCrawlingBlog(prevState => [...prevState, blog]); }; const handleCrawled = (blog) => { setCrawledBlogs(prevState => [...prevState, blog]); }; const handleTimeTaken = (data) => { setTimeRecords(prevState => [...prevState, data]); }; // 绑定事件监听 socketRef.current.on('crawling', handleCrawling); socketRef.current.on('crawled', handleCrawled); socketRef.current.on('timeTaken', handleTimeTaken); // 组件卸载时清理资源 return () => { // 精准移除对应监听 socketRef.current.off('crawling', handleCrawling); socketRef.current.off('crawled', handleCrawled); socketRef.current.off('timeTaken', handleTimeTaken); // 断开socket连接 socketRef.current.disconnect(); }; }, []); // 空依赖确保只初始化一次连接和监听 return ( <div className="app-container"> <h2>正在爬取的博客</h2> <ul> {crawlingBlog.map((blog, idx) => ( <li key={idx}>{blog}</li> ))} </ul> <h2>已爬取完成的博客</h2> <ul> {crawledBlogs.map((blog, idx) => ( <li key={idx}>{blog}</li> ))} </ul> <h2>爬取耗时记录</h2> <ul> {timeRecords.map((record, idx) => ( <li key={idx}>{record.blog}: {record.time}ms</li> ))} </ul> </div> ); }; export default App;
关键修复点说明
- 用
useRef保存Socket实例:useRef的取值在组件整个生命周期内保持稳定,不会因为组件渲染重新创建连接,确保所有监听都绑定在同一个Socket实例上。 - 函数式状态更新:
setCrawlingBlog(prev => [...prev, blog])这种写法依赖前一次的状态值,彻底避免了闭包导致的旧状态问题,能正确累积每次收到的博客数据。 - 单独定义监听函数:把每个事件的处理逻辑单独抽成函数,这样在组件卸载时可以精准移除对应的监听(如果用匿名函数,
off方法无法匹配到对应的监听)。 - 添加清理逻辑:在
useEffect的返回函数中移除监听并断开连接,防止组件卸载后仍有无效的监听占用资源。 - 补充协议头:Socket连接地址要加上
http://(或https://),否则部分环境下会出现连接失败的情况。
这样修改后,你的React组件就能正确逐个监听服务端发送的所有事件,状态也会实时同步更新。
内容的提问来源于stack exchange,提问作者Aayush Agrawal
相关产品推荐
相关产品推荐

