phase 5 part 1: typescript, vue, react, ssr(Next.js 16) topics with lessons, scenarios, interviews, verified examples

This commit is contained in:
Shikui Wang committed 2026-10-23 16:24:06 +08:00
1 parent 7a32d2c9a6
commit 7b5cae7d24
39 files changed
+2482 -48

No files matched your search

@@ -0,0 +1,45 @@
"use client";
// 不自动运行
// 客户端组件:有状态和事件处理。读取路由处理器返回的流,逐段显示。
import { useRef, useState } from "react";
export function Chat() {
const [answer, setAnswer] = useState("");
const [busy, setBusy] = useState(false);
const ctrl = useRef<AbortController | null>(null);
async function ask(formData: FormData) {
ctrl.current?.abort();
ctrl.current = new AbortController();
setAnswer("");
setBusy(true);
try {
const resp = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question: formData.get("q") }),
signal: ctrl.current.signal,
});
if (!resp.body) throw new Error("没有响应体");
const reader = resp.body.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
setAnswer((a) => a + value);
}
} catch (e) {
if ((e as Error).name !== "AbortError") setAnswer("出错了,请重试");
} finally {
setBusy(false);
}
}
return (
<form action={ask}>
<input name="q" placeholder="问点什么" required />
<button disabled={busy}>发送</button>
{busy && <button type="button" onClick={() => ctrl.current?.abort()}>停止</button>}
<p aria-live="polite">{answer}</p>
</form>
);
}