Files
zhuangang/topics/frontend/react/examples/task-panel/TaskPanel.tsx
T

36 lines
1.4 KiB
TypeScript

// 不自动运行(依赖 react,需要在 React 项目中使用)
// 用 useReducer 管理任务状态,用 useEffect 订阅服务端事件,并在卸载 / 任务变化时清理。
import { useEffect, useReducer } from "react";
import { initialState, taskReducer, type TaskEvent } from "./taskReducer.ts";
export function TaskPanel({ taskId, subscribe }: {
taskId: string;
subscribe: (id: string, onEvent: (e: TaskEvent) => void) => () => void; // 返回取消订阅函数
}) {
const [state, dispatch] = useReducer(taskReducer, initialState);
useEffect(() => {
dispatch({ type: "submitted", id: taskId });
const unsubscribe = subscribe(taskId, dispatch); // dispatch 引用稳定,不需要放进依赖数组
return unsubscribe; // 清理:taskId 变化或组件卸载时取消订阅,避免旧任务的事件串进来
}, [taskId, subscribe]);
return (
<section aria-live="polite">
<p>状态:{state.status}</p>
<ol>
{state.steps.map((s) => (
<li key={s.name}>{s.done ? "✓" : "…"} {s.name}</li>
))}
</ol>
{state.status === "failed" && (
<p role="alert">
{state.error} <button onClick={() => dispatch({ type: "retry" })}>重试</button>
</p>
)}
{state.result && <a href={state.result.url}>{state.result.title}</a>}
{state.status === "running" && <button onClick={() => dispatch({ type: "cancel" })}>取消</button>}
</section>
);
}