React集成Chakra UI时useEffect未触发Socket.io事件的问题
问题
用create-react-app加Chakra UI做入门开发,目标是搭一个基于react-plotly的实时数据查看器。Socket.io服务器收到send_plot_data事件后,每秒会推送新的JSON数据,UI用plotly-react绘图。不带Chakra UI的代码完全正常,但加了之后,useEffect里请求配置的get_config和请求绘图数据的send_plot_data这两个socket.emit操作没触发(已经查过Socket.io服务器日志确认)。除了按钮相关代码,其他基本没改,按钮本来还要触发事件,根据接收的JSON数据填充下拉框选项。
原代码
import React, { useEffect, useState } from 'react'; import {io} from 'socket.io-client'; import Plot from 'react-plotly.js'; import { ChakraProvider, Flex, Box, Spinner, Container, Text } from '@chakra-ui/react'; const socket = io("http://192.168.0.101:5000"); const App = () => { const [data, setData] = useState(); const [loading, setLoading] = useState(true); useEffect(() => { socket.on("connect",()=>{ console.log(`Connected`); socket.emit("send_plot_data", {symbol: "default"}); socket.emit("get_config", {config: ""}); }); socket.on("do_graph", res => { console.log(`Graph plot ok`) if(loading===true){ setLoading(false); } let response = JSON.parse(res); response.config = {responsive: true} setData(response); }); socket.on("connect_error", (err) => { console.log(`connect_error due to ${err.message}`); }); return () => socket.disconnect(); //eslint-disable-next-line }, []); return ( <ChakraProvider resetCSS> <Box> {loading?( <Flex flexDirection="column" alignItems="center"> <Container minHeight="10vh"> </Container> <Spinner /> <Text fontSize='lg'> Loading plot data ... </Text> </Flex> ):( <Plot {...data} /> )} </Box> </ChakraProvider> ); }
原因分析与修复方案
核心原因
Socket实例在组件外部提前初始化了,ChakraProvider会改变React组件树的初始化时机,导致socket的connect事件在组件的useEffect注册监听之前就已经触发了,后续useEffect里的connect事件监听根本捕获不到已经完成的连接,所以不会执行那两个emit操作。
修复代码
把Socket实例的初始化移到组件内部的useEffect里,确保连接事件监听在socket创建后立刻注册,不会错过连接时机:
import React, { useEffect, useState } from 'react'; import {io} from 'socket.io-client'; import Plot from 'react-plotly.js'; import { ChakraProvider, Flex, Box, Spinner, Container, Text } from '@chakra-ui/react'; const App = () => { const [data, setData] = useState(); const [loading, setLoading] = useState(true); // 不再在外部初始化socket let socket; useEffect(() => { // 在useEffect内部创建socket实例 socket = io("http://192.168.0.101:5000"); socket.on("connect",()=>{ console.log(`Connected`); socket.emit("send_plot_data", {symbol: "default"}); socket.emit("get_config", {config: ""}); }); socket.on("do_graph", res => { console.log(`Graph plot ok`) if(loading===true){ setLoading(false); } let response = JSON.parse(res); response.config = {responsive: true} setData(response); }); socket.on("connect_error", (err) => { console.log(`connect_error due to ${err.message}`); }); return () => socket.disconnect(); }, []); return ( <ChakraProvider resetCSS> <Box> {loading?( <Flex flexDirection="column" alignItems="center"> <Container minHeight="10vh"> </Container> <Spinner /> <Text fontSize='lg'> Loading plot data ... </Text> </Flex> ):( <Plot {...data} /> )} </Box> </ChakraProvider> ); }
额外说明
如果后续需要在组件其他地方(比如按钮点击事件)使用socket,记得确保socket实例已经初始化,也可以把socket存在useRef里避免重复创建:
// 用useRef保存socket实例 const socketRef = useRef(null); useEffect(() => { socketRef.current = io("http://192.168.0.101:5000"); // 后续用socketRef.current调用emit等方法 // ... }, []);
内容的提问来源于stack exchange,提问作者HermDP
相关产品推荐
相关产品推荐

