如何使用 socket.io 处理同步的 React 状态更新
How to handle simultaneous React state updates with socket.io
给定以下 React 功能组件:
const DataLog: FC = (props) => {
const [events, setEvents] = useState<string[]>([]);
const pushEvent = (event: string) => {
const updatedEvents = [...events];
updatedEvents.push(event);
setEvents([...updatedEvents]);
};
useEffect(() => {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
'http://localhost:9000'
);
socket.on('connect', () => {
console.log('Web socket connected');
});
socket.on('event1', (event: string) => {
pushEvent(event);
});
socket.on('event2', (event: string) => {
pushEvent(event);
});
}, []);
return (
<div>
{events.map((event: string, index: number) => {
return <div key={index}>{event}</div>;
})}
</div>
);
};
export default DataLog;
每次通过 socket.io websocket 连接收到 event1
或 event2
消息时,我想更新 events
状态变量。如果消息以合理的间隔传入,则此方法工作正常,但当 event1
和 event2
,或多个 event1
或 event2
消息非常快地传入时,问题就会出现。在处理下一个事件之前状态更新没有足够的时间完成,导致 events
数组被最后一次调用 setEvents
覆盖。有没有办法确保每条新消息都添加到 events
状态对象?我需要手动批量状态更新吗?我可以使用另一个挂钩来保存更新之间的状态吗?
pushEvent
函数是 useEffect
的依赖项,但不在其依赖项数组中。这意味着更新使用指向原始空 events
数组的初始函数。
您可以将其添加为 useEffect()
的依赖项:
useEffect(() => {
// your code
}, [pushEvent]);
但是,这会导致在每次渲染时调用 useEffect
,因为 pushEvent
是在渲染时重新创建的,因此您应该避免这种情况。
相反,使用 functional updates 设置状态以使新状态依赖于前一个状态:
const DataLog: FC = (props) => {
const [events, setEvents] = useState<string[]>([]);
useEffect(() => {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
'http://localhost:9000'
);
socket.on('connect', () => {
console.log('Web socket connected');
});
socket.on('event1', (event: string) => {
setEvents(prev => [...prev, event]);
});
socket.on('event2', (event: string) => {
setEvents(prev => [...prev, event]);
});
}, []);
return (
<div>
{events.map((event: string, index: number) => {
return <div key={index}>{event}</div>;
})}
</div>
);
};
问题
我看到的问题是,如果多个 event1 或 event2 事件碰巧在单个渲染周期内将状态更新排入队列,那么每个处理过的更新都会停止先前的更新。实际上,最后处理的状态更新是在后续渲染周期中看到的。
解决方案
使用 functional state 更新从以前的状态正确更新,而不是使用当前 callback/render 范围内可能关闭的任何内容。您还可以将 pushEvent
处理程序 移动到 useEffect
回调中,以将其作为外部依赖项删除。
示例:
useEffect(() => {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
'http://localhost:9000'
);
socket.on('connect', () => {
console.log('Web socket connected');
});
const pushEvent = (event: string) => {
setEvents(events => [...events, event]); // functional update
};
socket.on('event1', (event: string) => {
pushEvent(event);
});
socket.on('event2', (event: string) => {
pushEvent(event);
});
}, []);
给定以下 React 功能组件:
const DataLog: FC = (props) => {
const [events, setEvents] = useState<string[]>([]);
const pushEvent = (event: string) => {
const updatedEvents = [...events];
updatedEvents.push(event);
setEvents([...updatedEvents]);
};
useEffect(() => {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
'http://localhost:9000'
);
socket.on('connect', () => {
console.log('Web socket connected');
});
socket.on('event1', (event: string) => {
pushEvent(event);
});
socket.on('event2', (event: string) => {
pushEvent(event);
});
}, []);
return (
<div>
{events.map((event: string, index: number) => {
return <div key={index}>{event}</div>;
})}
</div>
);
};
export default DataLog;
每次通过 socket.io websocket 连接收到 event1
或 event2
消息时,我想更新 events
状态变量。如果消息以合理的间隔传入,则此方法工作正常,但当 event1
和 event2
,或多个 event1
或 event2
消息非常快地传入时,问题就会出现。在处理下一个事件之前状态更新没有足够的时间完成,导致 events
数组被最后一次调用 setEvents
覆盖。有没有办法确保每条新消息都添加到 events
状态对象?我需要手动批量状态更新吗?我可以使用另一个挂钩来保存更新之间的状态吗?
pushEvent
函数是 useEffect
的依赖项,但不在其依赖项数组中。这意味着更新使用指向原始空 events
数组的初始函数。
您可以将其添加为 useEffect()
的依赖项:
useEffect(() => {
// your code
}, [pushEvent]);
但是,这会导致在每次渲染时调用 useEffect
,因为 pushEvent
是在渲染时重新创建的,因此您应该避免这种情况。
相反,使用 functional updates 设置状态以使新状态依赖于前一个状态:
const DataLog: FC = (props) => {
const [events, setEvents] = useState<string[]>([]);
useEffect(() => {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
'http://localhost:9000'
);
socket.on('connect', () => {
console.log('Web socket connected');
});
socket.on('event1', (event: string) => {
setEvents(prev => [...prev, event]);
});
socket.on('event2', (event: string) => {
setEvents(prev => [...prev, event]);
});
}, []);
return (
<div>
{events.map((event: string, index: number) => {
return <div key={index}>{event}</div>;
})}
</div>
);
};
问题
我看到的问题是,如果多个 event1 或 event2 事件碰巧在单个渲染周期内将状态更新排入队列,那么每个处理过的更新都会停止先前的更新。实际上,最后处理的状态更新是在后续渲染周期中看到的。
解决方案
使用 functional state 更新从以前的状态正确更新,而不是使用当前 callback/render 范围内可能关闭的任何内容。您还可以将 pushEvent
处理程序 移动到 useEffect
回调中,以将其作为外部依赖项删除。
示例:
useEffect(() => {
const socket: Socket<ServerToClientEvents, ClientToServerEvents> = io(
'http://localhost:9000'
);
socket.on('connect', () => {
console.log('Web socket connected');
});
const pushEvent = (event: string) => {
setEvents(events => [...events, event]); // functional update
};
socket.on('event1', (event: string) => {
pushEvent(event);
});
socket.on('event2', (event: string) => {
pushEvent(event);
});
}, []);