9.7 KiB
Week 6:Vue 3 前端框架入门
目标:掌握 Vue 3 Composition API + Vite + Vue Router + Pinia,能独立开发 SPA 应用。
前置:已完成 Week 5 Spring Boot 后端,有 HTML/CSS/JS 基础。
本周产出:一个独立的 Todo SPA 应用,包含计数器、动态表单、组件拆分、路由导航、状态管理和 HTTP 封装。
启动方式:
cd week6
npm install # 首次运行
npm run dev # 启动开发服务器 → http://localhost:5173
Day 1:Vue 3 介绍、Vite 脚手架、组件基础
知识点:
- Vue 3 是什么:渐进式前端框架,核心是"响应式数据 + 声明式渲染"
- SFC(Single File Component):
.vue文件 =<template>+<script setup>+<style scoped> - Composition API vs Options API:为什么选 Composition API
- Vite:新一代构建工具,冷启动快(ES Module)、HMR 极速
- 项目结构:
main.js(入口)、App.vue(根组件)、components/、views/
动手 → 理解:
- 打开
src/main.js:理解createApp(App).mount('#app')的过程 - 打开
src/App.vue:观察<script setup>语法糖 - 修改
index.html中的<title>,观察浏览器标签变化 - 在
HomeView.vue的<template>中加一行<p>Hello Vue!</p>,观察热更新 - 把
style scoped去掉,观察样式是否泄漏到其他组件
核心文件:
index.html— 入口 HTML,<div id="app">挂载点src/main.js— 应用初始化src/App.vue— 根组件vite.config.js— Vite 配置(插件、代理、端口)
思考题:<script setup> 和普通的 <script> 有什么区别?为什么 Vue 3 推荐 setup 语法糖?
Day 2:响应式数据(ref/reactive)、计算属性
知识点:
ref():包装基本类型(number、string、boolean),通过.value访问reactive():包装对象/数组,直接访问属性(不需要.value)computed():计算属性,依赖变化时自动重新计算,带缓存watch()/watchEffect():侦听数据变化执行副作用- 响应式的本质:Proxy 拦截 getter/setter,数据变 → 视图自动变
- 模板中 ref 自动解包(不需要写
.value)
动手 → 理解:
- 打开
CounterView.vue,在模板中写{{ localCount }},不需要.value - 在
<script>中用console.log(localCount.value)打印当前值 - 修改
computed的计算逻辑,改为判断"是否大于 10" - 添加一个
watch,在localCount变化时console.log('变了:', newVal) - 对比:把
reactive改为ref写一个对象,体会.value的区别
核心文件:
views/CounterView.vue— ref + computed 演示stores/counter.js— Pinia store(第 6 天预热)
思考题:为什么模板中 ref 自动解包,但 <script> 里必须用 .value?
Day 3:指令系统(v-if/v-for/v-model)
知识点:
v-if/v-else-if/v-else:条件渲染(销毁/重建 DOM)v-show:条件显示(仅切换display)—— 适合频繁切换v-for:列表渲染,必须有:key唯一标识v-bind(缩写:):动态绑定属性:class、:style、:hrefv-model:双向绑定,表单输入 ↔ 数据自动同步v-on(缩写@):事件绑定@click、@submit、@keyupv-model修饰符:.trim、.number、.lazy
动手 → 理解:
- 打开
FormDemoView.vue,在输入框中输入,观察下方实时预览 - 用 DevTools 检查:勾选"显示详细信息"时,
v-if的元素是否在 DOM 中? - 把
v-if换成v-show,再次检查 DOM 差异 - 在
v-for的<li>中故意不写:key,观察控制台警告 - 尝试加一个
v-model.lazy的输入框,对比input事件和change事件的区别
核心文件:
views/FormDemoView.vue— 指令综合演示
思考题:什么时候用 v-if,什么时候用 v-show?为什么 v-for 和 v-if 不建议同时使用?
Day 4:组件通信(props/emits)、插槽
知识点:
defineProps():父组件向子组件传递数据(单向数据流)defineEmits():子组件向父组件发送事件(回调模式)- Prop 校验:
type、required、default、validator - 单向数据流原则:子组件不能直接修改 props
v-model组件版:modelValue+update:modelValue<slot>插槽:父组件向子组件传递模板内容- 命名插槽、作用域插槽(高级)
动手 → 理解:
- 打开
TodoForm.vue和TodoItem.vue,对照看 props 和 emits - 在
TodoItem中尝试直接todo.done = true(不通过 emit),观察 Pinia 是否感知 - 给
TodoItem的 props 加一个validator,限制todo.text不能为空 - 练习:新建一个
<Card>组件,用<slot>让父组件填充内容 - 尝试用
v-model模式改写TodoForm(父组件用 v-model 双向绑定输入值)
核心文件:
components/TodoForm.vue— props 接收 / emits 发送components/TodoItem.vue— props 接收 / emits 发送views/TodoView.vue— 父组件,组装 TodoForm + TodoItem
思考题:为什么不直接在子组件中操作 Pinia store,而是通过 props/emits 层层传递?
Day 5:Vue Router 路由
知识点:
vue-router:前端路由,URL 变化时不刷新页面createRouter({ history: createWebHistory() }):HTML5 History 模式<router-link>:导航链接(渲染为<a>标签)<router-view>:路由出口(匹配的组件渲染在这里)- 路由懒加载:
() => import('../views/XXX.vue') meta:路由元信息(标题、权限等)- 导航守卫:
beforeEach、afterEach - 动态路由:
/user/:id
动手 → 理解:
- 点击导航栏各链接,观察 URL 变化和页面内容,按 F12 Network 面板看是否有网络请求
- 在
router/index.js中临时把createWebHistory改为createWebHashHistory,观察 URL 变化(#号出现) - 修改某个路由的
meta.title,观察浏览器标签变化 - 在导航守卫中加
console.log('从', from.path, '到', to.path) - 添加一个
/user/:name动态路由页面
核心文件:
router/index.js— 路由配置 + 导航守卫components/NavBar.vue—<router-link>导航
思考题:为什么前端路由要用 History 模式而不是 Hash 模式?History 模式上线需要注意什么(Nginx 配置)?
Day 6:Pinia 状态管理
知识点:
- 为什么需要状态管理:跨组件/跨页面共享数据
defineStore('name', () => { ... }):Setup Store 语法(与 Composition API 一致)- State:
ref()/reactive()定义状态 - Getters:
computed()定义派生状态 - Actions:普通函数,可异步
storeToRefs():解构时保持响应性- Store 在
main.js中通过app.use(createPinia())注入 - 组件中直接
const store = useXxxStore()使用
动手 → 理解:
- 打开
stores/todo.js,观察 state/getters/actions 的定义方式 - 在浏览器中操作 Todo,打开 Vue DevTools 的 Pinia 面板查看状态变化
- 在
CounterView.vue中对比:Pinia store 的 counter 和本地 ref 的 counter - 用
storeToRefs(todoStore)解构todos,验证响应性 - 练习:新建一个
themestore,管理暗色/亮色主题切换
核心文件:
stores/counter.js— 简单 store 示例stores/todo.js— 完整 CRUD storestores/auth.js— 认证 Token storemain.js—app.use(createPinia())
思考题:Pinia 和 Vuex 有什么区别?为什么 Vue 3 官方推荐 Pinia?
Day 7:Axios 封装与请求拦截
知识点:
axios.create():创建实例,配置 baseURL、timeout- 请求拦截器:自动附加 Token、设置 Content-Type
- 响应拦截器:统一处理 401/403/500
Authorization: Bearer <token>:JWT 标准格式- Vite proxy 配置:开发环境代理
/api到 Spring Boot .env环境变量:VITE_API_BASE_URL
动手 → 理解:
- 打开
api/http.js,对照 Week 5 的app.js中api()函数,对比封装方式 - 用 DevTools Network 面板观察请求头中是否有
Authorization - 故意把 token 改错,观察响应拦截器的 401 处理
- 检查
vite.config.js的 proxy 配置,理解开发环境代理原理 - 添加
VITE_API_BASE_URL环境变量,修改baseURL读取环境变量
核心文件:
api/http.js— Axios 实例 + 拦截器vite.config.js→server.proxy— 开发代理stores/auth.js— Token 存储与模拟登录
思考题:为什么开发环境需要 Vite proxy,而生产环境用 Nginx 反向代理?
Week 6 总结
| 维度 | 掌握内容 |
|---|---|
| 脚手架 | Vite 创建项目、目录结构、HMR 热更新 |
| 响应式 | ref / reactive / computed / watch |
| 指令 | v-if / v-show / v-for / v-model / v-bind / v-on |
| 组件 | SFC、props / emits、slot、scoped style |
| 路由 | Vue Router、懒加载、导航守卫 |
| 状态 | Pinia Setup Store、storeToRefs |
| 网络 | Axios 封装、拦截器、Vite proxy |
与 Week 5 的对比:
| Week 5 前端 | Week 6 前端 | |
|---|---|---|
| 框架 | 原生 HTML + 原生 JS | Vue 3 + Vite |
| 状态管理 | localStorage + DOM 操作 | Pinia 响应式 store |
| 路由 | 无(单页面切换 display) | Vue Router History 模式 |
| HTTP | fetch 手写 | Axios 拦截器封装 |
| 组件化 | 无 | SFC + props/emits |
| 开发体验 | 手动刷新 | HMR 热更新 |
预习 Week 7:将本项目的 Vue 前端与 Week 5 的 Spring Boot 后端对接,用 Axios 替换模拟登录,实现前后端分离的学生管理系统 v3。