Skip to content

状态管理与实时通信


1. 状态管理策略

本项目采用 React 内置状态管理useState + useEffect),未引入 Redux/Zustand 等外部状态管理库。原因:

  • 页面间数据独立,无跨页面共享状态的需求
  • 组件层级浅(最多 3 层),无 prop drilling 问题
  • 数据获取模式统一(页面级 fetch → 本地 state),自定义 Hook 已封装复用逻辑

2. 自定义 Hooks

2.1 usePolling — 通用轮询 Hook

文件hooks/usePolling.ts

功能:以固定间隔轮询指定 API 函数,返回最新数据。

签名

typescript
function usePolling<T>(
  fetcher: () => Promise<T>,
  intervalMs: number,
  enabled: boolean
): {
  data: T | null;
  loading: boolean;
  error: string | null;
  refetch: () => void;
}

参数

  • fetcher — 返回 Promise 的数据获取函数
  • intervalMs — 轮询间隔(毫秒)
  • enabled — 是否启用轮询

行为

  1. 组件挂载时立即执行一次 fetcher()
  2. enabled=true,以 intervalMs 间隔持续调用 fetcher()
  3. 组件卸载时自动清除定时器
  4. refetch() 可手动触发立即刷新

使用场景

  • HealthCheck 页面:每 15 秒轮询 GET /health/nodes

示例

typescript
const { data, loading, error, refetch } = usePolling<NodeHealthResponse>(
  fetchNodeHealth,
  15_000,      // 15 秒
  autoRefresh  // 由 checkbox 控制
);

2.2 useRunPolling — 运行状态轮询 Hook

文件hooks/useRunPolling.ts

功能:针对运行实时状态设计的专用轮询 Hook,返回结构化的运行状态数据。

签名

typescript
function useRunPolling(options: {
  runId: string | null;
  interval?: number;
  enabled?: boolean;
}): {
  run: Run | null;
  currentStep: number;
  totalSteps: number;
  latestMetrics: MetricItem | null;
  loading: boolean;
  error: string | null;
}

参数

  • runId — 要监控的 Run ID(null 时不轮询)
  • interval — 轮询间隔,默认 1000ms
  • enabled — 是否启用,默认 true

内部逻辑

  1. 调用 GET /runs/{runId}/status 获取 RunStatusResponse
  2. 解构返回的 runcurrent_steptotal_stepslatest_metrics
  3. 运行进入终态(finished/stopped/failed)后自动停止轮询

使用场景

  • LiveMonitor 页面:1 秒间隔轮询正在运行的实例

示例

typescript
const { run, currentStep, totalSteps, latestMetrics, loading, error } =
  useRunPolling({
    runId: selectedRunId,
    interval: 1000,
    enabled: !!selectedRunId,
  });

2.3 useWebSocket — WebSocket 连接 Hook

文件hooks/useWebSocket.ts

功能:管理 WebSocket 连接的生命周期,接收实时事件推送。

签名

typescript
function useWebSocket(options: {
  url: string | null;
  onMessage?: (event: RunEventEnvelope) => void;
  enabled?: boolean;
}): {
  connected: boolean;
  error: string | null;
  send: (data: unknown) => void;
}

参数

  • url — WebSocket URL(null 时不连接),格式:/ws/runs/{runId}
  • onMessage — 收到消息时的回调
  • enabled — 是否启用连接

内部逻辑

  1. url 非空且 enabled=true 时建立 WebSocket 连接
  2. 收到消息后解析 JSON 为 RunEventEnvelope 并调用 onMessage
  3. 连接断开时尝试重连(指数退避)
  4. 组件卸载时自动关闭连接

消息格式(与后端 RunEventEnvelope 对应):

typescript
interface RunEventEnvelope {
  type: "state" | "step" | "schedule" | "metrics" | "sync" | "artifacts";
  run_id: string;
  ts_ms: number;
  payload: Record<string, unknown>;
}

当前使用状态

  • Hook 已实现并可用
  • LiveMonitor 页面当前选择使用 useRunPolling(HTTP 轮询)而非 WebSocket
  • WebSocket 方案保留为后续性能优化路径

3. 数据流转图

3.1 页面初始化流程

页面组件 mount

    ├── useEffect(() => { fetchXxx().then(setData) }, [])
    │       │
    │       └── api/client.ts → fetch('/endpoint')
    │                │
    │                └── Vite proxy → http://localhost:8000/endpoint
    │                        │
    │                        └── FastAPI route handler
    │                                │
    │                                └── JSON response
    │                                        │
    │                                setData(response) → re-render

    └── 组件渲染(使用 state 数据)

3.2 实时轮询流程(useRunPolling)

LiveMonitor mount

    ├── useRunPolling({ runId, interval: 1000 })
    │       │
    │       ├── 立即执行 fetchRunStatus(runId)
    │       │       └── GET /runs/{runId}/status → RunStatusResponse
    │       │               └── 更新 run / currentStep / latestMetrics
    │       │
    │       └── setInterval(fetchRunStatus, 1000)
    │               │
    │               ├── 每秒获取最新状态
    │               │
    │               └── if (终态) → clearInterval(自动停止)

    └── 组件渲染(实时更新指标卡片)

3.3 WebSocket 流程(可选)

组件 mount

    ├── useWebSocket({ url: '/ws/runs/{runId}' })
    │       │
    │       ├── new WebSocket(url)
    │       │       └── 连接建立
    │       │               │
    │       │               ├── 服务端推送 state 事件
    │       │               ├── 服务端推送 metrics 事件
    │       │               └── 后续持续推送 step/schedule/metrics 事件
    │       │
    │       └── ws.onmessage → JSON.parse → onMessage(envelope)
    │               └── 组件根据 type 更新对应 state

    └── 组件卸载 → ws.close()

4. API 客户端封装

文件api/client.ts

所有后端 HTTP 调用集中在此文件,统一处理请求构造和错误转换。

主要函数

函数请求返回类型
fetchHealth()GET /healthHealthResponse
fetchNodeHealth()GET /health/nodesNodeHealthResponse
fetchConfig()GET /configConfigResponse
fetchStrategies()GET /strategiesStrategyListResponse
fetchScenariosMerged()GET /scenariosScenarioMergedListResponse
fetchScenarioFull(id)GET /scenarios/{id}ScenarioFullResponse
createScenario(data)POST /scenariosScenarioFullResponse
updateScenario(id, data)PUT /scenarios/{id}ScenarioFullResponse
deleteScenario(id)DELETE /scenarios/{id}void
exportScenario(id)POST /scenarios/{id}/exportExportResponse
fetchRuns(params)GET /runsRunListResponse
createRun(data)POST /runsRunItem
startRun(id)POST /runs/{id}/startRunItem
stopRun(id)POST /runs/{id}/stopRunItem
fetchRunDetail(id)GET /runs/{id}RunDetailResponse
fetchRunStatus(id)GET /runs/{id}/statusRunStatusResponse
fetchRunMetrics(id)GET /runs/{id}/metricsRunMetricsResponse
fetchRunTraces(id, params)GET /runs/{id}/tracesRunTracesResponse
fetchRunArtifacts(id)GET /runs/{id}/artifactsRunArtifactsResponse
getArtifactDownloadUrl(runId, filename)string(URL 构造)
runLocationInit()POST /config/location-init/runLocationInitRunResponse

错误处理

typescript
async function api<T>(path: string, init?: RequestInit): Promise<T> {
  const res = await fetch(path, init);
  if (!res.ok) {
    const body = await res.json().catch(() => null);
    throw new Error(body?.detail ?? `HTTP ${res.status}`);
  }
  return res.json();
}

所有 API 函数共用 api<T>() 封装:

  • 非 2xx 响应统一转换为 Error,消息取 detail 字段
  • 网络异常透传 TypeError
  • 页面组件在 .catch() 中处理错误(设置 error state 或忽略)

5. TypeScript 类型定义

5.1 types/run.ts

定义与运行相关的核心类型,与后端 schemas.py 中的 Pydantic 模型一一对应:

前端类型后端模型说明
RunRunItem运行基本信息
RunSummaryItemRunSummaryItem运行摘要统计
MetricItemMetricItem步进指标
TraceItemTraceItem通信追踪记录
ArtifactFileItemArtifactFileItem产物文件
HealthResponseHealthResponse全局健康检查
NodeHealthResponseNodeHealthResponse节点健康检查
HealthNodeCheckHealthNodeCheck单节点健康
ConfigResponseConfigResponse系统配置
ConfigNodeEndpointConfigNodeEndpoint节点端点
StrategyItemStrategyItem策略信息

5.2 types/scenario.ts

定义场景管理相关的类型:

前端类型后端模型说明
ScenarioDbItemScenarioDbItem场景列表项
ScenarioFullResponseScenarioFullResponse场景完整信息
ScenarioNodeItemScenarioNodeItem场景节点
ScenarioTrafficItemScenarioTrafficItem流量配置
ScenarioRunConfigScenarioRunConfig运行参数
LocationInitRunResponseLocationInitRunResponse位置初始化结果

6. 连接状态管理

HTTP 轮询连接状态

useRunPolling 通过组合判断推导连接状态:

typescript
const isConnected = !!selectedRunId && !error && !loading;

LiveMonitor 页面以圆点指示器展示:

  • 绿色 轮询中:正常获取数据
  • 橙色 等待选择...:未选择运行
  • 红色 请求失败:API 调用异常

WebSocket 连接状态

useWebSocket 维护 connected: boolean 状态:

  • 连接建立:ws.onopensetConnected(true)
  • 连接断开:ws.onclosesetConnected(false)
  • 错误发生:ws.onerrorsetError(message)

7. 性能考量

  • 轮询频率useRunPolling 默认 1 秒间隔,对后端产生 1 QPS 负载
  • 自动停止:运行进入终态后轮询自动停止,避免无用请求
  • 条件加载RunDetail 页仅在运行结束后才加载 metrics/traces/artifacts
  • 分页:traces 查询默认 20 条/页,避免大量数据传输
  • 组件卸载清理:所有定时器和 WebSocket 连接在组件 unmount 时自动清理