2024 年 4 月 24 日到 5 月 6 日,vue3-basic 连续完成了十二次提交。
第一天搭好 Vue CLI 和 TypeScript 环境,接着练习 ref、响应式和生命周期;4 月 29 日抽出鼠标位置 Hook;5 月 2 日加入 URL Loader,第二天再为返回值补泛型;最后几次提交停在 Teleport、Suspense 和全局 API 的笔记。
当前文章把这段经历概括成“让逻辑回到一个地方”。这句话太完整了。固定仓库里的过程更像一排小实验:每次只碰一个概念,能看到抽取逻辑的好处,也能看到异步和类型问题刚刚露头时,代码还没有继续处理。
第一个 Hook 只记录点击
useMousePosition.ts 定义了 x 和 y 两个响应式值。组件挂载时,它在 document 上注册一个 click 监听;点击发生后,回调读取 pageX 与 pageY;组件卸载时,再移除同一个监听函数。
这段代码把状态、监听注册、监听清理和返回值放在同一个函数里。注册和清理使用同一个函数引用,组件只接收 x 与 y。
名字也暴露了一个不够准确的地方。useMousePosition 听起来会持续追踪鼠标,代码实际监听的是点击,鼠标移动不会更新坐标。本文仍按文件名引用它,但只把行为写成“点击坐标”。
第二个 Hook 开始碰到异步
useURLLoader.ts 接收一个 URL,在函数调用时立即执行 axios.get。它返回四个状态:结果、加载中、已完成和错误。请求成功时更新结果并结束加载,失败时保存错误。
加入这个文件后的第二天,提交又将结果改为 ref<T | null>。调用端可以为响应声明类型,空值状态也被显式保留。这个变化很具体:同一个加载函数不再只服务某一种图片响应,类型从函数外传进来。
异步请求带来的问题也停在代码里。函数调用即发请求,没有挂载时机;组件卸载后请求仍可能返回;连续传入不同 URL 时,较早的响应可能覆盖较新的结果;仓库也没有取消、超时、重试或请求序号。
旧文章曾写“请求需要处理取消和过期结果”,这更像二〇二六年的审稿意见,不能当成二〇二四年已经完成的代码。重写后,这句话改为项目现状:当时已经学会把加载状态抽出来,还没有处理异步生命周期。
README 比代码走得更远
仓库 README 从生命周期一路写到 Teleport、Suspense 与全局 API。它保存了学习目录,也混入了不少框架介绍和示例。代码树能直接对应的内容主要是响应式、两个 Hook、一个异步组件和一个 Teleport 弹窗;其余段落更适合称为笔记。
package.json 声明的是 Vue ^3.2.13、TypeScript ~4.5.5、Vue CLI ~5.0.0 和 Axios ^1.6.8;锁文件实际解析到 Vue 3.4.25、TypeScript 4.5.5、Vue CLI 5.0.8 和 Axios 1.6.8。这些版本让示例可以被重新定位,README 里的性能数字和生命周期表没有被当作 2026 年的当前结论。
仓库没有测试脚本。监听器的注册与移除、请求成功与失败、组件卸载后的响应和连续请求顺序,都没有自动测试回执;当前文章只把它们列为未覆盖的路径。
重新构建时,锁文件先拦了一次
2026 年 8 月 9 日,我在临时目录检出最后一条提交。工作区的 pnpm 10.6.4 无法按冻结模式读取这份 v6 锁文件,安装直接停止。改用与旧锁文件兼容的 pnpm 8.15.9 后,依赖安装完成,生产构建以 0 退出并生成静态产物;构建阶段仍报告了 ESLint warning。
独立运行 lint 没有通过,结果是两个 error 和七个 warning。两个错误都在演示 JavaScript 文件中:一次给 const 变量重新赋值,一次在同一作用域重复声明。其余警告包括未使用的生命周期导入、未使用类型和显式 any。工作树在复测后保持干净。
这个结果比一句“项目还能跑”更有用。构建链在兼容包管理器下仍可工作,代码质量门禁却从未收口;锁文件还隐含了工具版本要求。若只记录 Build PASS,这两部分都会消失。
练习的范围比方法论更清楚
这份仓库没有形成一套通用的 Vue 架构。它做了两件较小的事:将页面点击的状态与清理抽进函数,又将一次 Axios 请求的结果和加载状态抽进另一个函数。前者把生命周期配对写了出来,后者刚走到泛型,异步竞态仍然空着。
旧文末尾谈契约、组件测试和请求取消,读起来更完善,却把后来知道的事情塞回了旧代码。现在的版本保留提交顺序、固定实现、构建结果和 lint 错误。最后一次代码提交仍是 2024 年 5 月 6 日,这篇文字则在 2026 年 8 月 9 日重写;两个时间不再混在一起。
本文于 2026-08-09 根据公开仓库固定提交和隔离构建重新整理;构建与 lint 结果只代表本次复核。