基于Web Speech API与ChatGPT构建语音对话Web应用全解析
1. 项目概述与核心价值
最近在折腾一个挺有意思的玩意儿,一个能和ChatGPT进行语音对话的Web应用。这项目叫chatgpt-voice,是GitHub上一个开源的前端项目。说白了,它就是一个网页版的语音聊天机器人,你对着麦克风说话,它把你说的话转成文字发给ChatGPT,拿到回复后再用语音给你“念”出来,整个过程就像在跟一个会说话的AI助手打电话。
我之所以花时间研究它,是因为我觉得纯粹的文本交互还是有点“隔阂感”。很多时候,尤其是开车、做饭或者手头忙的时候,语音交互的便捷性是无可替代的。市面上虽然有一些语音助手,但要么功能单一,要么不够智能。而这个项目直接把目前最强大的语言模型ChatGPT和Web Speech API结合了起来,让你能在一个清爽的网页界面里,和AI进行有上下文记忆的、自然的语音对话。这对于想体验下一代人机交互的开发者,或者想给自己的应用快速集成语音对话功能的团队来说,是个非常棒的参考案例。
它的技术栈也很有意思,前端用的是React + TypeScript + Vite,构建速度飞快,类型安全也有保障。核心功能依赖两个关键的Web API:SpeechRecognition(语音转文本,STT)和SpeechSynthesis(文本转语音,TTS)。整个项目结构清晰,代码可读性高,非常适合学习和二次开发。接下来,我就带你从零开始,把这个项目跑起来,并深入聊聊它的实现细节、我踩过的坑,以及如何把它玩出花来。
2. 技术栈选型与架构解析
2.1 为什么是React + TypeScript + Vite?
这个项目的技术选型非常“现代”,几乎是当前前端开发的最佳实践组合。
React作为视图层框架,其组件化思想非常适合构建这种交互复杂的单页面应用。对话界面、消息列表、控制面板,天然就是一个个独立的组件。React Hooks(尤其是useState,useEffect,useRef)让管理语音API的异步状态和副作用变得异常清晰。比如,录音的状态(正在聆听、识别中、空闲)、对话消息列表,都很适合用useState来管理。
TypeScript的加入则是项目稳健性的关键。语音识别和合成API的返回值、ChatGPT接口的请求/响应格式,都充满了不确定性。没有类型约束,很容易在访问某个属性时得到undefined而崩溃。TypeScript能在编码阶段就帮你捕获这些潜在错误。例如,定义消息接口时,可以明确标出id,role(‘user’ 或 ‘assistant’),content,timestamp等字段,后续使用起来非常安心。
Vite作为构建工具,取代了传统的Webpack。它的优势在于极快的冷启动和热更新(HMR)。开发语音交互应用,你需要频繁地修改代码、测试效果。Vite的快速反馈能极大提升开发效率。它的配置也比Webpack简单得多,对于这样一个相对纯粹的前端项目,Vite是更轻量、更快速的选择。
2.2 核心依赖:Web Speech API的潜力与局限
项目的灵魂是Web Speech API,这是一个由浏览器原生提供的JavaScript API,包含SpeechRecognition和SpeechSynthesis两部分。它的最大优点就是“零依赖”,用户无需安装任何插件。
SpeechRecognition (语音转文本,STT):
- 工作原理:浏览器将麦克风的音频流实时上传到云端(通常是Google的服务器)进行识别,然后将识别出的文本流式地返回。这意味着你可以在说话的过程中就看到识别的中间结果。
- 优势:免费、无需配置、识别准确度尚可(尤其对主流语言)。
- 坑点与注意:
- 浏览器兼容性:这可能是最大的坑。
SpeechRecognition目前仅在基于Chromium的浏览器(Chrome, Edge, 新版Opera)中得到较好支持。Firefox和Safari的支持不完整或需要前缀。在实际开发中,必须做特性检测和降级处理。 - 网络依赖:识别过程必须联网,因为音频是在云端处理的。
- 隐私考量:需要明确告知用户语音数据会被发送到服务提供商(如Google)进行处理。
- 连续识别与单次识别:API支持两种模式。这个项目很可能用的是连续识别,启动后一直监听,直到主动停止,这样才符合自然对话的感觉。但需要处理好“静音检测”和“手动结束”的逻辑,避免误触发。
- 浏览器兼容性:这可能是最大的坑。
SpeechSynthesis (文本转语音,TTS):
- 工作原理:浏览器调用操作系统内置的语音合成引擎,将文本转换为语音音频播放出来。你可以选择不同的发音人(Voice)、调节语速(Rate)和音调(Pitch)。
- 优势:本地合成,无需网络(获取语音列表除外),延迟低。
- 坑点与注意:
- 发音人列表异步加载:
speechSynthesis.getVoices()在页面加载时可能返回空数组。通常需要监听voiceschanged事件来获取完整的列表。项目中的“自定义语音”功能,其核心就是调用这个API并提供一个下拉选择框。 - 跨浏览器差异:不同浏览器、不同操作系统可用的发音人差异巨大。在Windows上的Chrome里好听的语音,到了macOS的Safari上可能就完全不一样了。代码中不能对特定的语音名称做硬编码。
- 队列管理与打断:如果用户快速连续提问,或者中途想打断AI说话,就需要管理语音队列。直接调用
speechSynthesis.speak()会把新的语音加入队列,而speechSynthesis.cancel()可以立即停止当前和排队中的所有语音。这是一个重要的交互细节。
- 发音人列表异步加载:
注意:Web Speech API虽然方便,但用于生产环境需要慎重评估。其识别准确度、合成音质与专业的商用API(如Azure Cognitive Services, Google Cloud TTS)有差距。如果你的项目对语音质量、稳定性、多语言支持有更高要求,可能需要考虑后端集成专业的语音服务。这个开源项目为我们提供了一个完美的、低成本的原型验证方案。
2.3 项目架构:前后端分离与职责划分
从项目仓库的链接可以看到,它还有一个配套的 后端服务器 。这是一个典型的前后端分离架构。
前端 (chatgpt-voice) 职责:
- 提供用户界面(UI)。
- 控制麦克风,调用
SpeechRecognitionAPI进行录音和语音识别。 - 将识别出的文本,通过HTTP请求(很可能是
fetch或axios)发送到后端服务器。 - 接收后端返回的ChatGPT文本回复。
- 调用
SpeechSynthesisAPI将回复文本朗读出来。 - 管理对话历史(上下文)的显示与存储(可能用
localStorage或IndexedDB做临时缓存)。
后端 (chatgpt-server) 职责:
- 提供安全的API端点,接收前端发来的用户消息。
- 负责与OpenAI的官方API(或兼容API)进行通信,将用户消息和对话历史上下文一起发送给ChatGPT。
- 处理OpenAI API的响应,将AI的回复文本返回给前端。
- (可能)管理API密钥、处理限流、记录日志等安全与运维工作。
这种架构的好处是安全。你的OpenAI API密钥保存在后端,不会暴露给浏览器。前端只与你的后端服务器通信,后端再与OpenAI通信,这样你可以更好地控制访问权限、缓存策略和成本。
3. 本地开发环境搭建与配置详解
3.1 克隆项目与依赖安装
首先,我们把代码拿到本地。打开你的终端(Terminal, CMD, 或 PowerShell),执行以下命令:
# 克隆项目到本地,默认会在当前目录创建一个 `chatgpt-voice` 文件夹 git clone https://github.com/sonngdev/chatgpt-voice.git # 进入项目目录 cd chatgpt-voice接下来安装项目依赖。项目使用npm作为包管理器,package.json里已经定义好了所有需要的库。
# 安装依赖包。这个过程会根据网络情况花费一些时间。 npm install实操心得:在国内执行
npm install有时会因为网络问题失败。你可以尝试以下两种方法:
- 使用淘宝NPM镜像:先运行
npm config set registry https://registry.npmmirror.com,然后再执行npm install。- 如果项目使用了某些需要从GitHub下载的包,网络问题可能更复杂,可以考虑使用科学的上网工具或耐心重试。
安装完成后,你会看到一个node_modules文件夹,里面包含了React、TypeScript、Vite以及其他工具库。
3.2 启动开发服务器
依赖安装完毕,就可以启动开发服务器了。
# 启动Vite开发服务器 npm run dev执行成功后,终端通常会显示类似下面的信息:
VITE v4.x.x ready in xxx ms ➜ Local: http://localhost:5173/ ➜ Network: http://192.168.x.x:5173/现在,打开你的浏览器(强烈推荐使用Chrome或Edge,因为对Web Speech API支持最好),访问http://localhost:5173。
你应该能看到项目的用户界面了。它可能是一个简洁的聊天窗口,带有一个麦克风按钮。但是,此时点击麦克风很可能没有反应,或者提示需要后端服务。这是因为前端需要连接到一个能处理ChatGPT请求的后端服务器。
3.3 配置后端服务与环境变量
前端项目本身不包含与OpenAI通信的逻辑,这部分在另一个仓库chatgpt-server里。你需要按照那个仓库的README指引,将后端服务也运行起来。
后端服务通常也需要npm install和npm run dev(或start)。关键的步骤是配置你的OpenAI API密钥。在后端项目的根目录下,你需要创建一个如.env的环境变量文件:
# 在 chatgpt-server 项目根目录下 OPENAI_API_KEY=你的真实OpenAI API密钥 OPENAI_API_BASE_URL=https://api.openai.com/v1 # 如果你用的是官方API # 可能还有其他配置,如端口号 PORT=3000重要安全警告:绝对不要将你的
.env文件或其中的API密钥提交到Git仓库!确保它在.gitignore文件中。API密钥一旦泄露,他人可能会滥用导致你的账户产生高额费用。
启动后端服务器后(假设运行在http://localhost:3000),你需要让前端知道后端地址。前端项目通常会在代码中通过一个基础URL或代理来配置。
常见配置方式:
- 环境变量:前端项目可能有一个
.env.development文件,让你设置VITE_API_BASE_URL=http://localhost:3000。Vite会将以VITE_开头的变量注入到客户端代码中。 - Vite代理:在
vite.config.ts文件中,可能会配置了代理,将所有以/api开头的请求转发到后端服务器,这样前端代码里直接请求/api/chat即可,无需关心具体域名。 - 硬编码(不推荐):在开发阶段,代码里可能直接写死了
http://localhost:3000作为后端地址。
你需要检查前端项目的代码(通常是src目录下的某个api.ts或config.ts文件),找到它向后端发送请求的URL,并确保这个URL指向了你正在运行的后端服务。
验证连接:完成前后端配置并分别启动后,在浏览器前端页面点击麦克风说话。观察浏览器开发者工具(F12打开)的“网络”(Network)标签页。当你说话结束时,应该能看到一个向你的后端地址(如http://localhost:3000/chat)发出的POST请求。如果这个请求成功(状态码200)并返回了AI的回复,且页面能播放出声音,那么恭喜你,整个链路就通了!
4. 核心功能模块深度剖析与实现
4.1 语音识别模块的实现与优化
语音识别是对话的起点。我们来看看一个健壮的实现需要考虑哪些方面。
基础实现代码框架:
// 这是一个简化的核心逻辑示例 const useSpeechRecognition = () => { const [transcript, setTranscript] = useState(‘’); // 识别出的最终文本 const [interimTranscript, setInterimTranscript] = useState(‘’); // 识别中的中间文本 const [isListening, setIsListening] = useState(false); const recognitionRef = useRef<SpeechRecognition | null>(null); useEffect(() => { // 1. 特性检测 const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { console.error(‘您的浏览器不支持语音识别功能’); return; } // 2. 创建实例并配置 const recognition = new SpeechRecognition(); recognition.continuous = true; // 持续监听,直到手动停止 recognition.interimResults = true; // 返回中间结果 recognition.lang = ‘zh-CN’; // 设置语言,例如中文 // 3. 绑定事件处理器 recognition.onstart = () => setIsListening(true); recognition.onend = () => setIsListening(false); // 注意:在连续模式下,onend触发可能意味着错误或超时 recognition.onresult = (event) => { let interim = ‘’; let final = ‘’; for (let i = event.resultIndex; i < event.results.length; i++) { const transcript = event.results[i][0].transcript; if (event.results[i].isFinal) { final += transcript; } else { interim += transcript; } } setInterimTranscript(interim); if (final) { setTranscript(prev => prev + final); // 累积最终结果 // 触发发送逻辑:将最终识别结果发送给后端 onFinalTranscript(final); } }; recognition.onerror = (event) => { console.error(‘语音识别错误:’, event.error); setIsListening(false); // 处理特定错误,如“无麦克风权限”、“网络错误”等 }; recognitionRef.current = recognition; }, []); const startListening = () => { if (recognitionRef.current && !isListening) { setTranscript(‘’); // 开始新的识别时清空旧文本 setInterimTranscript(‘’); try { recognitionRef.current.start(); } catch (err) { console.error(‘启动识别失败:’, err); } } }; const stopListening = () => { if (recognitionRef.current && isListening) { recognitionRef.current.stop(); } }; return { transcript, interimTranscript, isListening, startListening, stopListening }; };关键优化点与避坑指南:
- 静音检测与自动停止:Web Speech API本身不提供“检测到静音自动停止”的功能。一个常见的做法是设置一个计时器。当
onresult事件最后一次触发后,如果超过一定时间(比如1.5秒)没有新的中间结果,就认为用户说完了,主动调用stopListening()。这能避免用户说完后还需要手动点击停止按钮的不便。 - 权限处理:首次调用
recognition.start()时,浏览器会弹出麦克风权限请求。必须优雅地处理用户拒绝的情况。可以将startListening函数用try...catch包裹,并在onerror事件中检查event.error === ‘not-allowed’,然后给用户友好的提示。 - 错误恢复:识别过程中可能发生网络错误或其他未知错误。一个健壮的系统应该在
onerror事件中,根据错误类型尝试自动恢复(例如,等待几秒后重新启动识别),或者至少提供一个清晰的错误状态给用户,并允许用户重试。 - 语言与区域设置:
recognition.lang属性至关重要。如果你主要面向中文用户,就设为‘zh-CN’。对于多语言应用,可能需要提供一个语言选择器,动态修改这个属性。注意,改变lang属性后,可能需要重启识别实例才能生效。
4.2 对话管理与上下文维护
与ChatGPT进行有记忆的对话,关键在于维护好“上下文”(Context)。上下文就是之前对话的历史消息列表。
消息数据结构:
interface ChatMessage { id: string; // 唯一标识,可以用 Date.now() 或 uuid 生成 role: ‘user’ | ‘assistant’ | ‘system’; // 发送者角色 content: string; // 消息内容 timestamp: number; // 时间戳 }上下文发送逻辑: 当你把用户的最新一条语音识别文本(比如“今天的天气怎么样?”)发送给后端时,你不能只发这一条。为了让ChatGPT知道之前聊过什么,你需要把最近几轮的对话历史一起发过去。
通常,我们会维护一个messages状态数组。发送请求时,构造一个包含历史消息和新消息的数组:
// 假设 messages 状态存储了所有历史消息 const sendToBackend = async (userInput: string) => { const newUserMessage: ChatMessage = { id: generateId(), role: ‘user’, content: userInput, timestamp: Date.now(), }; // 1. 更新本地UI显示 setMessages(prev => [...prev, newUserMessage]); // 2. 构造发送给后端的上下文。通常我们会限制上下文长度,避免token超限。 const contextMessages = [...messages, newUserMessage].slice(-10); // 只发送最近10条消息 // 3. 发送请求 const response = await fetch(‘/api/chat’, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’ }, body: JSON.stringify({ messages: contextMessages }), }); const data = await response.json(); const aiMessage: ChatMessage = { id: generateId(), role: ‘assistant’, content: data.reply, // 假设后端返回 { reply: ‘...’ } timestamp: Date.now(), }; // 4. 更新本地UI,显示AI回复 setMessages(prev => [...prev, aiMessage]); // 5. 触发语音合成 speakText(data.reply); };Token限制与上下文截断: OpenAI的模型有上下文窗口限制(例如,gpt-3.5-turbo早期是4096个token)。如果对话历史太长,就会超过限制导致请求失败。因此,一个成熟的应用必须实现上下文截断策略。
- 简单策略:像上面代码一样,只保留最近N条消息。
- 智能策略:计算所有消息的token总数(可以使用
gpt-3-encoder等库进行近似计算),当总数接近限制时,从最旧的消息开始逐条移除,直到满足要求。更高级的策略还可能尝试总结旧的对话内容,将总结文本作为一条system消息放入上下文。
4.3 语音合成模块与交互体验打磨
收到AI的文本回复后,下一步就是把它读出来。
基础合成代码:
const speakText = (text: string) => { // 安全检查 if (!(‘speechSynthesis’ in window)) { console.error(‘您的浏览器不支持语音合成功能’); return; } // 停止当前可能正在播放的语音 window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); // 配置语音参数(可以从用户设置中读取) const selectedVoice = voices.find(v => v.name === userSettings.voiceName); if (selectedVoice) { utterance.voice = selectedVoice; } utterance.rate = userSettings.speechRate || 1.0; // 语速,1.0为正常 utterance.pitch = userSettings.speechPitch || 1.0; // 音调,1.0为正常 utterance.onend = () => { // 语音播放结束,可以触发下一步操作,比如自动开始下一轮聆听 console.log(‘语音播放完毕’); if (autoListenAfterSpeech) { startListening(); } }; utterance.onerror = (event) => { console.error(‘语音合成错误:’, event.error); }; window.speechSynthesis.speak(utterance); };获取语音列表的异步问题: 如之前所述,语音列表的获取是异步的。一个标准的做法是:
const [voices, setVoices] = useState<SpeechSynthesisVoice[]>([]); useEffect(() => { const loadVoices = () => { const availableVoices = window.speechSynthesis.getVoices(); if (availableVoices.length > 0) { setVoices(availableVoices); // 可以在这里设置一个默认语音,比如第一个中文语音 const defaultChineseVoice = availableVoices.find(v => v.lang.startsWith(‘zh’)); if (defaultChineseVoice && !userSettings.voiceName) { setUserSettings(prev => ({ ...prev, voiceName: defaultChineseVoice.name })); } } }; loadVoices(); // 立即尝试加载一次 window.speechSynthesis.onvoiceschanged = loadVoices; // 监听变化事件 return () => { window.speechSynthesis.onvoiceschanged = null; // 组件卸载时清理 }; }, []);提升交互体验的关键细节:
- 语音播放状态反馈:在UI上,当AI正在“说话”时,应该有一个明确的视觉指示,比如消息气泡旁有一个跳动的声音波形图标,或者整个回复文本高亮。这可以通过监听
utterance的onstart和onend事件来实现。 - 打断功能:如果用户在AI说话时突然插话,应该能立即停止语音播放并开始新的识别。这需要将
stopListening和speechSynthesis.cancel()逻辑绑定在一起。 - 自动接听:一个流畅的对话体验是,AI说完后,自动开启麦克风准备聆听用户的下一句话。这可以通过在
utterance.onend事件中调用startListening()来实现。但要注意,如果环境嘈杂,可能会误触发。最好提供一个开关让用户选择是否启用此功能。
5. 常见问题、故障排查与进阶玩法
5.1 开发与运行中的常见问题
问题1:麦克风没有反应,或者页面提示“找不到麦克风”。
- 排查步骤:
- 检查浏览器权限:在浏览器地址栏左侧,应该有一个麦克风图标。点击它,确保允许该网站使用麦克风。你也可以在浏览器设置中清除该站点的权限后重试。
- 检查物理设备:确保电脑已连接麦克风,并且在系统设置中该麦克风是默认的输入设备。
- 检查代码:在开发者工具控制台查看是否有JavaScript错误。确认
SpeechRecognition对象被成功创建,并且recognition.start()被调用。 - HTTPS要求:
SpeechRecognitionAPI在大多数浏览器中要求页面通过HTTPS协议加载(localhost本地开发除外)。如果你将项目部署到线上,必须使用HTTPS。
问题2:语音识别准确率很低,或者全是乱码。
- 可能原因与解决:
- 语言设置错误:确认
recognition.lang设置的语言与你说的话一致。说中文却设置成‘en-US’,识别结果肯定不对。 - 网络问题:识别服务需要联网。检查网络连接,并注意某些网络环境可能无法访问Google的语音识别服务。
- 麦克风质量与环境噪音:尝试使用外接麦克风,并在安静的环境下测试。
- 浏览器差异:不同浏览器的识别引擎不同,准确度有差异。Chrome通常表现最好。
- 语言设置错误:确认
问题3:AI回复的语音听起来很机械,或者语速不对。
- 调整方法:
- 更换发音人:在项目的语音设置下拉框中,尝试选择不同的“Voice”。不同系统提供的语音包质量差异很大。在Windows上,“Microsoft Xiaoxiao Online (Natural)”等神经语音质量很高;在macOS上,可以选择“Siri”或“Ting-Ting”等语音。
- 调整语速和音调:找到设置中的“Speech Rate”(语速)和“Pitch”(音调)滑块。将语速调到0.9-1.1之间,音调微调,找到听起来最自然的值。
- 后端模型升级:语音的“智能感”和“自然度”很大程度上取决于ChatGPT生成的文本本身。确保后端使用的是较新的模型(如
gpt-4),其生成的文本通常更流畅、更口语化,读出来效果也更好。
问题4:对话进行几轮后,AI好像“失忆”了,不记得之前聊的内容。
- 原因:上下文长度超限,被后端截断了,或者前端没有正确发送历史消息。
- 排查:
- 打开浏览器开发者工具的“网络”标签,查看发送给后端的请求体。检查
messages数组是否包含了足够多的历史对话。 - 查看后端服务器的日志或代码,确认它接收到的消息列表是否正确,以及是否因为token超限而进行了截断。
- 在前端代码中,增加上下文消息的数量(比如从最近10条增加到20条),但要注意这可能增加API调用成本并可能触发token限制。
- 打开浏览器开发者工具的“网络”标签,查看发送给后端的请求体。检查
5.2 进阶玩法与二次开发思路
这个开源项目是一个绝佳的起点,你可以基于它进行很多有趣的扩展:
- 集成其他大模型:后端不一定非要连接OpenAI。你可以修改后端代码,使其支持国内的大模型API(如文心一言、通义千问、智谱GLM等),或者连接本地部署的Ollama、LM Studio运行的模型。这样,你就拥有了一个完全私有的、可定制的语音对话助手。
- 增加对话场景与角色预设:在前端增加一个“角色”或“场景”选择器。例如,选择“英语老师”,那么发送给AI的上下文开头就加上一条系统提示:“你是一个耐心且有趣的英语口语陪练老师,请用中文解释,并用简单的英语与我对话。” 这能让对话更有目的性。
- 实现语音唤醒:类似“Hey Siri”,实现一个离线关键词唤醒功能。这需要用到前端的
Web Audio API进行简单的实时音频分析,当检测到特定声音模式(如你说“小助手”)时,再自动开启SpeechRecognition。这能实现完全免提的交互。 - 对话历史存储与同步:目前对话历史可能只保存在浏览器内存中,刷新页面就没了。可以集成IndexedDB或后端数据库,实现对话历史的持久化存储、多设备同步,甚至支持搜索历史对话。
- UI/UX深度定制:现有的UI比较简洁。你可以完全重设计界面,加入动画效果(如语音识别的声波动画)、主题切换、布局调整,打造独一无二的交互体验。
5.3 部署上线注意事项
如果你想把这个项目部署到公网,让其他人也能访问,需要考虑以下几点:
- 后端服务部署:你需要一个云服务器(如AWS EC2, Google Cloud Run, 或国内的阿里云ECS)来运行Node.js后端。使用
pm2或docker来管理进程,保证服务稳定运行。 - API密钥安全管理:确保后端服务器的环境变量中的
OPENAI_API_KEY等敏感信息不被泄露。永远不要在前端代码中硬编码API密钥。 - HTTPS强制:购买域名和SSL证书(Let‘s Encrypt提供免费证书),为你的网站启用HTTPS。这是使用Web Speech API的强制要求(除localhost外)。
- 成本控制:OpenAI API是按Token收费的。如果你的应用公开使用,务必在后端实现速率限制(Rate Limiting)和用户认证,防止滥用导致账单爆炸。可以考虑要求用户使用自己的API密钥,或者提供付费套餐。
- 浏览器兼容性提示:在网站醒目位置提示用户“为获得最佳体验,请使用最新版的Chrome或Edge浏览器”。
折腾完这一整套,你不仅得到了一个可用的语音ChatGPT工具,更重要的是,你深入理解了现代Web应用如何整合前沿的AI能力和浏览器原生API。从项目克隆、环境配置、原理剖析到问题排查和扩展思考,每一步都是宝贵的全栈开发实践。这个项目的代码结构清晰,注释也相对完善,非常适合作为学习React状态管理、TypeScript类型设计、以及异步事件处理的范本。我建议你在能跑通基础功能后,多花点时间阅读源码,尝试修改一些功能,比如换个UI库、增加一个设置项,这才是从“会用”到“会做”的关键一步。
