
1. 前端人学AI到底在学什么先把话说在前头前端转AI或者补AI知识不是让你去训大模型、调参炼丹。绝大多数前端岗位真正用得上的AI能力集中在三块——调用大模型API做产品功能、理解AI应用的前端交互范式、用AI工具提升自己的开发效率。这三块跟TypeScript、React、Vue这些老本行是强绑定的学起来并不需要你把数学系重读一遍。我自己是从2023年开始在业务里接AI功能的做过对话式客服、文档问答、流式输出的代码助手也踩过SSE断流、Token超限、流式渲染卡顿这些坑。这篇文章就把前端学AI的路线拆开讲清楚需要补哪些知识、按什么顺序补、每块知识对应什么实际场景、以及哪些是看着唬人其实可以先跳过的。适合已经会React或Vue、想往AI方向靠的前端也适合面试2026岗位时被问到你了解AI吗不知道怎么答的人。核心结论先给出来前端学AI优先级最高的是流式通信SSE/WebSocket 大模型API调用 提示词工程基础其次是AI应用的状态管理和向量检索的前端配合最后才是模型原理的科普级理解。下面按这个顺序展开。2. 前端学AI的知识地图与优先级排序2.1 先搞清楚哪些AI知识前端真的用得上很多人一上来就去啃Transformer论文、看注意力机制推导结果两周后放弃。这不是学习能力问题是路线错了。前端在AI链路里的位置是最靠近用户的那一层你负责的是把模型的输出优雅地呈现出来、把用户的输入准确地送进去、把中间的状态管理好。具体拆一下前端在AI应用里承担的工作大致是这些输入侧文本、图片、语音的采集与预处理文件上传、格式校验、大小限制通信侧调用大模型API处理流式响应SSE最常见处理超时、重试、中断渲染侧流式文本逐字渲染、Markdown解析、代码高亮、图表生成状态侧多轮对话上下文管理、会话历史、Token用量统计交互侧停止生成、重新生成、编辑重发、引用溯源你看这里面没有一条需要你会反向传播。需要的是你对异步通信、状态管理、渲染性能的理解而这些恰恰是前端的强项。所以前端学AI的本质是用已有的工程能力去接住AI这个新变量。2.2 一张优先级表别把时间花错地方优先级知识模块为什么重要建议投入P0大模型API调用与鉴权所有AI功能的地基1周P0SSE流式通信对话类产品的标配1周P0提示词工程基础决定输出质量持续P1AI应用状态管理多轮对话必需1周P1Markdown/代码流式渲染体验关键3天P1TypeScript类型建模接口契约清晰结合项目P2向量检索与RAG前端配合知识库类产品1周P2WebSocket与轮询对比特定场景2天P3模型原理科普面试能聊碎片时间P3本地推理WebGPU等前沿探索按需这张表的逻辑是先能做出东西再理解为什么。你先把一个能流式输出的对话页面跑起来再去补原理动力和效果完全不一样。2.3 为什么是TypeScript打底而不是先学Python经常有人问学AI是不是得先学Python对前端来说不是。你的战场在浏览器和Node层Python那套是算法工程师的活。你要做的是定义好和AI服务之间的接口契约这时候TypeScript的价值就出来了。举个实际例子大模型返回的结构化数据经常是JSON但字段可能缺失、类型可能漂移。用TypeScript把响应类型定义清楚配合运行时校验比如zod能省掉大量调试时间// 定义流式响应的数据块类型 interface StreamChunk { id: string; object: chat.completion.chunk; choices: Array{ delta: { content?: string; role?: string }; finish_reason: string | null; }; } // 解析SSE数据行 function parseSSELine(line: string): StreamChunk | null { if (!line.startsWith(data: )) return null; const payload line.slice(6).trim(); if (payload [DONE]) return null; try { return JSON.parse(payload) as StreamChunk; } catch { return null; } }这段代码看着简单但类型定义清楚之后你在处理delta.content时编辑器会给你补全不会出现拼错字段名这种低级错误。顺带提一句TypeScript 7.0里baseUrl选项要废弃了新项目直接用paths配合moduleResolution: bundler别再用老写法。3. 核心知识点逐个拆解与实操要点3.1 大模型API调用从鉴权到错误处理调用大模型API这件事表面上是发个POST请求实际上坑不少。我按实际项目里的顺序讲。鉴权。绝大多数服务用Bearer Token放在请求头里。这里有个铁律API Key绝对不能出现在前端代码里。浏览器里能看到的一切都是公开的Key泄露了别人就能刷你的额度。正确做法是前端调你自己的后端后端再转发给模型服务Key存在服务端环境变量里。// 前端只调自己的后端不直接碰模型服务 async function chat(messages: Message[], signal?: AbortSignal) { const res await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ messages, stream: true }), signal, }); if (!res.ok) { throw new Error(请求失败: ${res.status}); } return res.body; }参数。常用的几个model选模型temperature控制随机性0到2越低越确定max_tokens限制输出长度stream决定是否流式。做客服问答这种要稳定的场景temperature给0.2到0.5做创意文案可以给0.8以上。max_tokens要结合你的计费方式算输出越长越贵。错误处理。这是最容易被忽略的。常见错误码401是Key无效429是限流500是服务端问题还有超时。429要配合退避重试不能死循环猛打。我一般这样处理async function fetchWithRetry(url: string, options: RequestInit, retries 3) { for (let i 0; i retries; i) { try { const res await fetch(url, options); if (res.status 429) { // 指数退避第i次等 2^i 秒 await new Promise(r setTimeout(r, 1000 * 2 ** i)); continue; } return res; } catch (e) { if (i retries - 1) throw e; } } throw new Error(重试次数用尽); }注意重试只对幂等的请求做流式对话中途断了不要盲目重发否则用户会看到重复内容。正确做法是记录已接收的内容从断点续传或者提示用户重新生成。3.2 SSE流式通信对话产品的命脉为什么AI对话都用SSE而不是普通请求因为大模型生成是逐Token的如果等全部生成完再返回用户要盯着空白屏幕好几秒。SSEServer-Sent Events能让服务端一边生成一边推给前端用户看到文字一个个蹦出来体验完全不同。SSE的本质是基于HTTP的长连接服务端返回的Content-Type是text/event-stream数据格式是data: xxx\n\n。前端用EventSource或者直接读fetch的response.body。我推荐后者因为EventSource只支持GET没法传复杂的请求体。async function streamChat(messages: Message[], onChunk: (text: string) void) { const res await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ messages, stream: true }), }); const reader res.body!.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); // 按双换行切分事件块 const parts buffer.split(\n\n); buffer parts.pop() ?? ; for (const part of parts) { const line part.trim(); if (!line.startsWith(data:)) continue; const data line.slice(5).trim(); if (data [DONE]) return; try { const json JSON.parse(data); const content json.choices?.[0]?.delta?.content; if (content) onChunk(content); } catch { // 忽略解析失败的行 } } } }这段代码有几个关键点。第一decoder.decode要传{ stream: true }否则多字节字符比如中文被切断时会乱码。第二要用buffer缓存不完整的行因为网络传输不保证一次给你一个完整的事件块。第三[DONE]是结束标志收到就退出。实操心得中文乱码是SSE最常见的坑。我一开始没加{ stream: true }结果你好变成你加一个乱码符号。加上之后就好了。另外buffer的切分一定要用\n\n用单个\n会把一个事件切成两半。3.3 流式渲染的性能与体验优化拿到流式文本之后怎么渲染也有讲究。最朴素的做法是每来一个chunk就setState但这样在高频输出时会疯狂触发重渲染页面卡顿。我的做法是用ref累积内容用requestAnimationFrame批量更新function useStreamText() { const [text, setText] useState(); const bufferRef useRef(); const rafRef useRefnumber(); const append useCallback((chunk: string) { bufferRef.current chunk; if (rafRef.current) return; rafRef.current requestAnimationFrame(() { setText(bufferRef.current); rafRef.current undefined; }); }, []); const reset useCallback(() { bufferRef.current ; setText(); }, []); return { text, append, reset }; }这样无论模型输出多快渲染频率都被限制在每帧一次也就是最多60fps人眼完全够用。Markdown渲染是另一个重点。AI输出的内容经常带Markdown格式代码块、列表、表格都有。直接用dangerouslySetInnerHTML有XSS风险要用react-markdown这类库配合remark-gfm支持表格和删除线。代码高亮用shiki或highlight.js注意流式过程中代码块可能不完整要处理未闭合的符号否则渲染会错乱。自动滚动也要处理。用户往上翻看历史时新内容不应该强制把页面拉到底。判断逻辑是如果滚动条本来就在底部附近比如距底部小于50px才自动滚否则显示一个回到底部的按钮。3.4 多轮对话的状态管理单轮问答简单多轮就复杂了。核心问题是上下文怎么存、怎么传、怎么控制长度。大模型本身是无状态的每次请求都要把历史消息一起传过去。消息格式一般是{ role: user | assistant | system, content: string }的数组。system消息放人设和规则user和assistant交替。interface Message { id: string; role: system | user | assistant; content: string; createdAt: number; } // 发送时只取最近N轮控制Token function buildContext(messages: Message[], maxRounds 10): Message[] { const system messages.filter(m m.role system); const dialog messages.filter(m m.role ! system); const recent dialog.slice(-maxRounds * 2); return [...system, ...recent]; }为什么要截断因为Token是按量计费的而且模型有上下文窗口上限。传太多历史不仅贵还可能超出窗口导致报错。截断策略可以是按轮数也可以是按估算的Token数粗略算法中文约1.5字符1个Token英文约4字符1个Token。状态管理上React用useReducer或者zustand都行Vue用Pinia。关键是把会话列表和当前会话的消息分开管理切换会话时不要丢历史。我见过有人把所有消息塞一个数组结果切换会话时全乱了。注意assistant的消息在流式过程中是半成品要标记一个streaming状态。如果用户这时候切换会话或者刷新页面要能正确处理这个未完成的消息要么标记为中断要么丢弃。3.5 提示词工程前端也要懂的那部分提示词工程听起来是产品经理或算法的事但前端经常要写默认提示词、做提示词模板、处理用户输入拼接。懂一点能省很多沟通成本。核心原则就几条。明确角色和任务别让模型猜。给例子few-shot比讲一堆规则管用。约束输出格式比如要求返回JSON前端好解析。处理边界比如用户输入为空、超长、含特殊字符时怎么兜底。const SYSTEM_PROMPT 你是一个代码助手。回答要求 1. 只回答与编程相关的问题 2. 代码用Markdown代码块包裹标注语言 3. 不确定的内容明确说不确定不要编造 4. 回答控制在300字以内; function buildPrompt(userInput: string): string { const trimmed userInput.trim().slice(0, 2000); if (!trimmed) throw new Error(输入不能为空); return trimmed; }结构化输出是前端特别需要的。让模型返回JSON时要在提示词里写清楚schema并且前端一定要做运行时校验因为模型不保证100%遵守格式。用zod校验一遍失败了就重试或者降级处理。3.6 向量检索与RAG的前端配合RAG检索增强生成是知识库类产品的核心。流程是用户提问 → 前端把问题发给后端 → 后端向量检索找到相关文档片段 → 拼进提示词 → 模型生成答案 → 返回给前端附带引用来源。前端在这条链路里的活是上传文档、展示引用、处理引用跳转。上传要做格式校验PDF、Word、Markdown等、大小限制、进度展示。引用展示要把模型回答里的引用标记比如[1]渲染成可点击的链接点了能定位到原文片段。interface Citation { index: number; docId: string; chunkId: string; content: string; score: number; } // 把回答里的 [1] [2] 替换成可点击元素 function renderWithCitations(text: string, citations: Citation[]) { return text.replace(/\[(\d)\]/g, (match, num) { const cite citations.find(c c.index Number(num)); if (!cite) return match; return sup classcitation>npm create vitelatest ai-chat-demo -- --template react-ts cd ai-chat-demo npm install npm install react-markdown remark-gfm shiki zustand依赖说明react-markdown渲染Markdownremark-gfm支持表格等扩展语法shiki做代码高亮比highlight.js好看zustand管状态比Redux轻。4.2 后端转发层的搭建前面说了Key不能放前端所以要有个薄薄的后端。用Node Express写一个转发几十行搞定import express from express; import fetch from node-fetch; const app express(); app.use(express.json()); app.post(/api/chat, async (req, res) { const { messages } req.body; res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); res.setHeader(Connection, keep-alive); const upstream await fetch(https://api.example.com/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.API_KEY}, }, body: JSON.stringify({ model: your-model, messages, stream: true }), }); upstream.body.pipe(res); }); app.listen(3001);这个转发层做了三件事接收前端请求、加上Key转发给模型服务、把流式响应原样pipe回前端。生产环境还要加鉴权、限流、日志但原型阶段这样就够。4.3 前端流式接收与渲染的完整实现把前面讲的拼起来一个完整的对话组件大概长这样function ChatPage() { const [messages, setMessages] useStateMessage[]([]); const [input, setInput] useState(); const [streaming, setStreaming] useState(false); const abortRef useRefAbortController(); async function send() { if (!input.trim() || streaming) return; const userMsg: Message { id: crypto.randomUUID(), role: user, content: input, createdAt: Date.now() }; const assistantId crypto.randomUUID(); const assistantMsg: Message { id: assistantId, role: assistant, content: , createdAt: Date.now() }; setMessages(prev [...prev, userMsg, assistantMsg]); setInput(); setStreaming(true); const controller new AbortController(); abortRef.current controller; try { await streamChat( [...messages, userMsg], (chunk) { setMessages(prev prev.map(m m.id assistantId ? { ...m, content: m.content chunk } : m )); }, controller.signal ); } catch (e) { if ((e as Error).name ! AbortError) { setMessages(prev prev.map(m m.id assistantId ? { ...m, content: m.content \n\n[生成中断] } : m )); } } finally { setStreaming(false); abortRef.current undefined; } } function stop() { abortRef.current?.abort(); } return ( div classNamechat div classNamemessages {messages.map(m ( div key{m.id} className{msg msg-${m.role}} ReactMarkdown remarkPlugins{[remarkGfm]}{m.content}/ReactMarkdown /div ))} /div div classNameinput-bar textarea value{input} onChange{e setInput(e.target.value)} / {streaming ? button onClick{stop}停止/button : button onClick{send}发送/button} /div /div ); }这里有几个细节值得说。AbortController用来实现停止生成用户点了停止就中断fetch服务端连接也会断。assistant消息先占位这样流式内容有地方追加。错误处理区分AbortError用户主动停止不算错误不显示中断提示。4.4 参数计算与性能调优Token估算。前面提过粗略算法实际项目里我会写个函数估算用来决定是否截断历史function estimateTokens(text: string): number { // 中文按1.5字符1Token英文按4字符1Token粗略估算 const chinese (text.match(/[\u4e00-\u9fa5]/g) || []).length; const other text.length - chinese; return Math.ceil(chinese / 1.5 other / 4); }渲染节流。前面用requestAnimationFrame批量更新实测在模型每秒输出50个Token时页面依然流畅。如果不做节流每秒50次setStateReact会明显卡顿。虚拟滚动。对话超过100条时全部渲染DOM会拖慢页面。用react-window或virtua做虚拟滚动只渲染可视区域的消息。不过要注意流式中的消息高度在变化虚拟滚动要处理好动态高度。内存管理。长时间对话会累积大量消息浏览器内存会涨。我的做法是超过一定条数比如200条时把早期消息折叠成摘要或者只保留最近N条在内存里更早的存IndexedDB。5. 常见问题与排查技巧实录5.1 流式输出相关的高频问题现象可能原因排查方法解决中文乱码TextDecoder没开stream检查decode参数加{ stream: true }内容重复重试逻辑不当看网络面板请求次数流式请求不自动重试卡在最后一段buffer没处理完检查循环退出后buffer退出前再解析一次buffer逐字渲染卡顿每chunk都setState看React DevTools渲染次数rAF批量更新代码块渲染错乱流式中代码块未闭合看Markdown结构补全未闭合的连接莫名断开代理或超时看响应头设置合理超时加心跳5.2 我踩过的三个真实坑第一个坑SSE被中间层缓冲。有次部署到线上本地好好的流式输出线上变成一次性全出来。查了半天发现是反向代理默认开了缓冲把SSE数据攒着一起发。解决办法是在响应头加X-Accel-Buffering: no或者配置代理关闭缓冲。这个坑很隐蔽因为本地开发环境没有代理根本复现不了。第二个坑AbortController没清理。用户快速连续点发送前一个请求还没结束就发下一个结果两个流同时往一个消息里写内容错乱。解决办法是发送前先abort上一个请求或者用请求ID标记只处理最新请求的响应。第三个坑Markdown的XSS。早期图省事用dangerouslySetInnerHTML渲染Markdown结果模型输出里带了script标签虽然现代浏览器大多拦截了但风险实打实存在。换成react-markdown之后它默认不渲染原始HTML安全多了。如果确实需要渲染HTML用rehype-sanitize过滤。5.3 面试中关于AI的常见问法2026年的前端面试AI相关的问题越来越常见。我整理几个高频的SSE和WebSocket有什么区别AI对话为什么用SSE答SSE是单向的服务端推送基于HTTP实现简单自动重连WebSocket是双向的更重。AI对话主要是服务端推内容给前端SSE够用且更轻。流式渲染怎么优化性能答rAF批量更新、虚拟滚动、Markdown增量解析。怎么保证API Key安全答前端不存Key走后端转发服务端做鉴权和限流。多轮对话上下文怎么管理答消息数组、按轮数或Token截断、system消息单独处理。回答这类问题的关键是结合项目讲别背概念。你说我做过一个流式对话用rAF把渲染频率控制在60fps比说流式渲染要注意性能有说服力得多。5.4 学习资源与练习路径最后给个练习路径照着做一遍比看十篇文章管用第一周用现成的模型API写一个最简单的单轮问答跑通请求和响应第二周改成流式用fetch读body实现逐字渲染第三周加多轮对话处理上下文和状态管理第四周加Markdown渲染、代码高亮、停止生成、重新生成第五周加文档上传和引用展示理解RAG流程持续关注模型API的更新新模型、新参数、新能力TypeScript的类型建模贯穿始终每接一个接口就把类型定义清楚。React和Vue选一个深入就行另一个了解概念即可核心的流式通信和状态管理思路是通的。我个人在实际操作中的体会是前端学AI最大的障碍不是技术难度而是心理上的畏难。总觉得AI很高深其实你真正要碰的那部分就是异步通信加状态管理都是老本行。把第一个流式对话跑起来后面的路就顺了。至于模型内部怎么工作知道个大概就行面试能聊两句实际开发中用不上。