🎭《哈姆雷特》如果会写 React:useChat 自定义 Hook 的 AI 炼金术

🌌 1. 开场:一个异步请求的奥德赛

想象浏览器是一座剧院,舞台上的演员分别是:

角色 职责 道具
用户 提问 键盘
AI 回答 fetch
React 导演 hooks
我们 剧务 useChat

传统写法像即兴表演:

"台词、灯光、音效一起上!"------结果舞台塌了(内存泄漏、竞态、loading 闪烁)。
useChat 则像提前彩排好的百老汇,灯光、走位、提词器,一键到位。


🧰 2. 目录(舞台布置图)

  1. API 设计:演员的剧本
  2. 代码剖析:拆台、换幕、打光
  3. 内存与竞态:后台的幽灵
  4. 彩蛋:把 useChat 变成"时间旅行机"
  5. 一键启动:npm i @ai-chat/use-chat(假装我们有包)

🧩 3. API 设计:给哈姆雷特一把 TypeScript 的剑

ts 复制代码
// 用 JSDoc 冒充 TypeScript,浏览器也能看懂
/**
 * @param {Object} config
 * @param {string} config.api  - AI 服务端地址
 * @param {number} config.maxHistory - 记忆长度(防止 token 爆炸)
 * @returns {{
 *   messages: Array<Message>,
 *   send: (text: string) => Promise<void>,
 *   isLoading: boolean,
 *   abort: () => void
 * }}
 */

🎨 4. 代码剖析:把哈姆雷特拆成乐高

以下代码可直接丢进 Vite / CRA / Next.js 的 src/hooks/useChat.js

js 复制代码
import { useState, useRef, useEffect, useCallback } from 'react';

export default function useChat({ api, maxHistory = 20 }) {
  // 1. 舞台灯光:react 状态
  const [messages, setMessages] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  // 2. 打光师:控制 fetch 的 AbortController
  const abortRef = useRef(null);

  // 3. 提词器:真正发送消息的函数
  const send = useCallback(async (text) => {
    // 3-a 防止空字符串的小丑上场
    if (!text.trim()) return;

    // 3-b 临时演员:用户消息
    const userMsg = { role: 'user', content: text };
    setMessages(prev => [...prev, userMsg]);

    // 3-c 点亮"加载中"霓虹灯
    setIsLoading(true);

    // 3-d 新建 AbortController,给幽灵一个名字
    abortRef.current = new AbortController();

    try {
      const res = await fetch(api, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ prompt: text, history: messages.slice(-maxHistory) }),
        signal: abortRef.current.signal,
      });

      if (!res.ok) throw new Error(await res.text());

      const { reply } = await res.json();

      // 3-e 把 AI 放上舞台
      setMessages(prev => [...prev, { role: 'assistant', content: reply }]);
    } catch (err) {
      if (err.name !== 'AbortError') {
        // 真正的错误,亮起红灯
        console.error(err);
        setMessages(prev => [...prev, { role: 'error', content: err.message }]);
      }
    } finally {
      setIsLoading(false);
    }
  }, [api, messages, maxHistory]);

  // 4. 拉闸:观众起哄,演员退场
  const abort = useCallback(() => {
    abortRef.current?.abort();
    setIsLoading(false);
  }, []);

  // 5. 幕布落下时清理后台幽灵
  useEffect(() => {
    return () => abortRef.current?.abort();
  }, []);

  return { messages, send, isLoading, abort };
}

🧠 5. 内存 & 竞态:后台的幽灵

幽灵 出没地点 驱魔咒语
竞态 快速连发两次 send AbortController
内存泄漏 组件卸载仍在 fetch useEffect 清理
无限历史 token 爆炸 slice(-maxHistory)

⏳ 6. 彩蛋:时间旅行机(undo/redo)

js 复制代码
// 在 useChat 里再加两行
const [historyStack, setHistoryStack] = useState([]);
const undo = () => setMessages(historyStack.pop());

messages 每次深拷贝 push 进栈即可。

"Undo 是前端人的月光宝盒。"


🖼️ 7. 配图:舞台全景

graph TD A[用户敲字] -->|text| B(useChat.send) B --> C{fetch 请求} C -->|成功| D[AI 回复] C -->|失败/取消| E[错误/abort] D --> F[setMessages] E --> F F --> G[React 重渲染] style C fill:#f9f,stroke:#333 style G fill:#bbf,stroke:#333

🚀 8. 一键起飞

bash 复制代码
npm create vite@latest my-ai-chat --template react
cd my-ai-chat
# 把上面的 useChat.js 丢进 src/hooks/
npm run dev

🎭 尾声

useChat 像一支魔法羽毛笔,把异步、竞态、内存三大怪兽关进 React 的生命周期笼子。

今晚,让你的组件也念一段独白:

"To fetch, or not to fetch, that is no longer a question."

相关推荐
zhensherlock1 分钟前
Protocol Launcher 系列:Working Copy 提交与同步全攻略
javascript·git·typescript·node.js·自动化·github·js
天若有情6731 分钟前
【开源推荐】form-validator-cn 轻量级中文表单校验库 | TS 零依赖、极简开箱即用
前端·npm·开源·node·js·表单校验
shjita18 分钟前
maven涉及的配置
java·前端·maven
changshuaihua00124 分钟前
useState 状态管理
开发语言·前端·javascript·react.js
鹏程十八少30 分钟前
6. 2026金三银四 面试官最爱的 Binder:一次拷贝、Activity 启动流程,这篇全搞定
前端·面试·前端框架
知识分享小能手34 分钟前
ECharts入门学习教程,从入门到精通,综合实战——ECharts数据大屏 - 完整知识点(9)
前端·学习·echarts
是吗乔治36 分钟前
vuetify实现excel表格粘贴效果
前端·vue.js·vue·excel
Java后端的Ai之路41 分钟前
React 快速入门到精通教程:从零基础到能写项目
前端·react.js·前端框架
是上好佳佳佳呀43 分钟前
【前端(九)】CSS Transform 2D/3D 变换笔记:分清两个原点,搞懂多重变换
前端·css·笔记
阿钱真强道1 小时前
19 基于 ComfyUI 工作流学习 AnimateDiff:单图生成视频的入门实践与问题分析
aigc·animatediff·stable-diffusion·comfyui·视频生成·图生视频·单图转视频