模块一 · 起步与基础
起步:用 Vite 跑通第一个 Vue 应用
从装环境到跑起第一个 SFC,讲清脚手架选项、四个脚本、目录结构、模块解析与 HMR,让"项目能跑"不再是靠运气。
读完这一节,你能做到
- 判断本机环境是否满足 Vue 3 项目要求,并选对包管理器。
- 用官方脚手架起项目,说清每个选项与脚本的作用。
- 读懂目录结构,知道一个
.vue文件从磁盘到浏览器的完整路径。 - 定位"改了代码页面不生效"的四类原因。
一、先装对环境
Vue 3 项目至少要三样东西:Node、包管理器、编辑器插件。
| 工具 | 要求 | 检查方式 | 不满足的后果 |
|---|---|---|---|
| Node | 18 LTS 及以上(20/22 更稳) | node -v | Vite 起不来,报 crypto.hash is not a function |
| 包管理器 | npm 9+ 或 pnpm 8+ | npm -v | 依赖装不齐、幽灵依赖 |
| 编辑器插件 | Vue - Official(Volar) | 扩展面板 | 模板无类型提示、.vue 报错 |
| 浏览器插件 | Vue DevTools | 开发者工具多一个面板 | 无法看组件树与响应式数据 |
两个常见误区:
- 装 Vetur。Vetur 是 Vue 2 时代插件,在 Vue 3 +
<script setup>下会给出大量错误提示,必须禁用。 - 团队里 npm 与 pnpm 混用。锁文件不同(
package-lock.json与pnpm-lock.yaml),依赖树会漂移。团队只允许一种,并在package.json里写"packageManager"字段固定版本。
版本还要看语法:defineModel() 需要 Vue 3.4+,useTemplateRef() 需要 3.5+。老项目里看到这两个 API 报未定义,先查 package.json 里的 vue 版本,而不是怀疑写法。
二、三种起项目的方式
| 方式 | 命令 | 适用 | 代价 |
|---|---|---|---|
| CDN 单文件 | 引入 vue.global.js | 试语法、给静态页加交互 | 无构建、无 SFC、性能差 |
| Vite 脚手架 | npm create vue@latest | 绝大多数项目 | 需要 Node 环境 |
| Nuxt | npx nuxi init | 需要 SSR/SEO/文件路由 | 约定多、调试链路长 |
新手从 Vite 脚手架开始。命令会进入交互式提问:
npm create vue@latest my-app
cd my-app
npm install
npm run dev
交互项的建议:
| 选项 | 建议 | 理由 |
|---|---|---|
| TypeScript | 选是 | 组件 props 与接口类型能省掉大量运行时错误 |
| JSX | 一般不选 | 模板够用,JSX 只在极动态场景需要 |
| Vue Router | 选是 | 多页面必然要路由 |
| Pinia | 选是 | 跨页面状态迟早要有 |
| Vitest | 选是 | 至少能写组合式函数的单测 |
| E2E | 按需 | Playwright 体积大,先用 Vitest |
| ESLint / Prettier | 选是 | 团队协作的底线 |
不要一次全选。每多一个选项,就多一套配置要先理解;先最小跑通,再按需补。
三、四个脚本各自做什么
{
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"type-check": "vue-tsc --build"
}
}
dev:启动开发服务器,不打包,浏览器请求哪个模块就编译哪个模块。build:先做类型检查,再把模板预编译成 render 函数,产物在dist/。preview:用本地静态服务器托管dist/,用来验证生产产物。type-check:vue-tsc会检查模板里的表达式类型,这是纯tsc做不到的。
注意 preview 不是生产服务器。它只做静态托管,没有 gzip、缓存头与 history fallback 的完整配置,上线仍要用 nginx 或 CDN。
四、目录结构:每个文件负责什么
my-app/
├─ index.html # 唯一的 HTML 入口,里有 <div id="app">
├─ vite.config.ts # 构建与插件配置
├─ tsconfig.json # 类型检查配置(含路径别名)
├─ public/ # 原样拷贝的资源,不参与构建
└─ src/
├─ main.ts # 应用入口,createApp 并挂载
├─ App.vue # 根组件
├─ components/ # 可复用组件
├─ views/ # 路由页面级组件
├─ router/ # 路由表
├─ stores/ # Pinia
└─ assets/ # 参与构建的图片与样式
两个容易混的点:
public/里的文件原样输出,路径写/logo.svg;src/assets/里的文件参与构建,要用import或new URL(..., import.meta.url)引用,才会被哈希与压缩。index.html不在public/里,它是 Vite 的入口。Vue CLI 时代靠html-webpack-plugin生成 HTML,Vite 反过来以 HTML 为入口,这也是它冷启动快的原因之一。
五、SFC 三段式与第一个组件
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>
<template>
<button class="counter" @click="count++">{{ count }}</button>
</template>
<style scoped>
.counter {
padding: 6px 12px;
border-radius: 6px;
}
</style>
三个要点:
<script setup>是编译期语法糖,顶层变量自动暴露给模板,不需要return,也没有this。- Vue 3 的模板支持多根节点(Fragment),但多根组件在接收
attrs时需要显式指定落到哪个节点,初学建议先保持单根。 scoped会给元素加data-v-xxx属性并改写选择器,样式只作用于当前组件;要影响子组件内部,用:deep()。
入口文件只有三行:
import { createApp } from "vue";
import App from "./App.vue";
createApp(App).mount("#app");
六、路径别名:为什么每个项目都要配
深目录里写 ../../../components/Button.vue 既难读又易错。配一个 @ 指向 src:
// vite.config.ts
resolve: { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) } }
还要在 tsconfig.json 的 compilerOptions.paths 里同步一份 "@/*": ["./src/*"],否则编辑器和 vue-tsc 不认识别名。两处必须一致,只配一处是最常见的"运行正常但编辑器爆红"。
七、一次 dev 请求的完整链路
浏览器请求 /index.html
→ Vite 返回 HTML,注入 /src/main.ts
→ 浏览器以原生 ESM 请求 main.ts
→ 请求 App.vue,被 @vitejs/plugin-vue 拆成三块:
· 编译后的 JS(render 函数)
· 样式(注入 <style>)
· 类型/作用域信息
→ 浏览器执行并挂载
由此可以推出三件事:
- 为什么冷启动快:不打包整包,只编译被请求到的模块;依赖用 esbuild 预构建缓存。
- 为什么 HMR 快:只替换改动模块的边界,不刷新整页;但组件内的普通变量状态会丢,
ref状态通常能保留。 - 为什么生产必须 build:浏览器不认识
.vue,也不该在生产环境现场编译模板。
八、"改了没生效"的排查顺序
| 现象 | 常见原因 | 验证方式 |
|---|---|---|
| 改了代码页面没变 | 文件没保存;HMR 断开;改的是 dist/ 或另一个副本 | 看终端是否有 hmr update 日志 |
| 只有样式没变 | 样式被更高优先级覆盖;scoped 选择器没命中 | DevTools 看元素上的 data-v- 与最终样式 |
| 刷新后全白 | 运行时错误 | 看 Console 第一行报错,而不是先改代码 |
| 本地正常、构建后报错 | 用了 public 路径、大小写不一致、动态 import 路径拼错 | 跑 build + preview 复现 |
排查顺序固定为:先看终端 → 再看 Console → 最后看代码。跳过前两步,绝大多数时间会浪费在猜。
九、常见坑
- Node 版本过低,
vite直接启动失败,却以为是代码问题。 - 同时装着 Vetur 与 Volar,模板报一堆假错误。
index.html里手写了<script src="/src/main.ts">,又在main.ts里挂载一次,导致重复挂载。- 把
src/assets的图片写成绝对路径字符串,构建后 404。 - 直接双击打开
dist/index.html,file://协议禁止加载 ESM,页面全白。 - 配了别名只在
vite.config里,tsconfig没配,编辑器一片红。
十、动手任务:自写 → AI 写 → 对比 → 取舍
四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。
① 自己写
- 用脚手架起一个最小项目,删掉示例组件,只保留
App.vue与一个Counter.vue。 - 配好
@别名,并在App.vue里用它引入Counter。 - 在 dev 模式下分别改
script里的普通变量与template,观察 HMR 行为差异并记录。 - 跑一次
build与preview,确认产物可用;再故意把一张图片放进public与assets各引用一次,对比构建后的路径。
② 让 AI 写同一题
把第 2 题(组件 + 请求)交给 AI,补一句 Vue 3 + TS + Vite,用 <script setup>,不新增依赖。
③ 对比要盯的点
- 状态它是用
ref逐个声明,还是reactive包一个大对象?后者一解构就丢响应性。 - 列表渲染它有没有给
:key?给了的话用的是 id 还是 index? - 它给的目录结构能不能直接当项目起点,还是三分钟后就得分层?
④ 一句话取舍
它的版本能跑,但哪一处会在你解构 props 或解构 state 时静默失去响应性?
十一、自测
dev与build的本质区别是什么?- 为什么
public里的资源不需要 import? @别名要配哪两个地方?- HMR 为什么比整页刷新快,代价是什么?
- 构建后白屏,应该先看哪里?
参考答案要点:按需编译 vs 预编译产物;原样拷贝不参与构建;vite.config 与 tsconfig paths;只替换模块边界但会丢局部状态;Console 的第一个报错。
配套题目
★ 越多越该先背;点题目可看完整答案与深入版。
提示:读完可以顺手做几道对应题目,检验是否真的理解