React
useEffect 根据依赖重新连接,并在返回函数中清理旧连接。
在线预览
组件关系与数据流
StateComponentEffectConnection
差异焦点useEffect 属于提交后的同步阶段,依赖必须手动列出。
实线表示依赖、数据或命令传递,虚线表示事件、回调或清理路径。
flowchart TB
accTitle: React useEffect 提交后同步机制
accDescr: roomId 更新先让 ReactDemo 重新渲染并提交 DOM,提交完成后 React 比较依赖数组,清理旧 Effect 再连接新房间。
room(["roomId state"]) --> demo["ReactDemo render"]
demo --> effect{{"commit 后比较 [roomId]"}}
effect -.->|"先执行 cleanup"| old[("旧 Connection")]
effect -->|"再 connect"| current[("新 Connection")]
current --> status(["status + events"])
class room,status state
class demo component
class effect process
class old,current resource
classDef state fill:#ffffff,stroke:#149eca,color:#172033,stroke-width:1.2px
classDef component fill:#e5f6fb,stroke:#149eca,color:#172033,stroke-width:2px,font-weight:600
classDef process fill:#f4fbfd,stroke:#149eca,color:#172033,stroke-width:1.6px
classDef resource fill:#f3f5f8,stroke:#778195,color:#172033,stroke-width:1.4pxstate 更新先完成 render/commit,再按依赖数组执行 cleanup 和新 Effect。
实际运行源码
import { useEffect, useRef, useState } from "react";
type RoomId = "general" | "react" | "vue";
type ConnectionEvent = { id: number; message: string;};
const rooms: { id: RoomId; label: string }[] = [ { id: "general", label: "综合讨论" }, { id: "react", label: "React 交流" }, { id: "vue", label: "Vue 交流" },];
function createConnection(roomId: RoomId, notify: (message: string) => void) { const roomName = rooms.find((room) => room.id === roomId)?.label ?? roomId; let timer: number | undefined;
return { connect() { notify(`正在连接:${roomName}`); timer = window.setTimeout(() => notify(`已连接:${roomName}`), 400); }, disconnect() { window.clearTimeout(timer); notify(`已断开:${roomName}`); }, };}
export function ReactDemo() { const [roomId, setRoomId] = useState<RoomId>("general"); const [status, setStatus] = useState("等待连接"); const [events, setEvents] = useState<ConnectionEvent[]>([]); const nextEventId = useRef(1);
useEffect(() => { const connection = createConnection(roomId, (message) => { const event = { id: nextEventId.current++, message }; setStatus(message); setEvents((current) => [event, ...current].slice(0, 5)); });
connection.connect(); return connection.disconnect; }, [roomId]);
return ( <div className="demo-stack"> <label className="demo-field"> 当前房间 <select className="demo-select" value={roomId} onChange={(event) => setRoomId(event.target.value as RoomId)} > {rooms.map((room) => ( <option key={room.id} value={room.id}> {room.label} </option> ))} </select> </label>
<div className="demo-card"> <span className="demo-tag">连接状态</span> <h2>{status}</h2> <p>切换房间会先清理旧连接,再建立新连接。</p> </div>
<p className="demo-muted">连接日志(最近 5 条)</p> <ul className="demo-list" aria-live="polite"> {events.map((event) => ( <li key={event.id}>{event.message}</li> ))} </ul> </div> );}