第 1 章 · Vue 3 入门:组合式 API 与响应式
本章目标:在 frontend-web ch01~ch13 与 ch16 毕业项目 基础上,用 Vue 3 组合式 API 重建 academy-demo 的课程列表核心交互;掌握 script setup、ref/reactive、computed、watch 与模板语法。
学时建议:3~4 小时(含 60 分钟跟练)
前置:完成 frontend-web 模块 ch01~ch13;熟悉 ES6 模块、Fetch、Vite 基础。
1.1 为什么从原生 JS 进入 Vue 3
| 维度 | 原生 JS(ch16) | Vue 3 |
|---|---|---|
| 状态管理 | 手动维护 state 对象 + DOM 更新 | 响应式自动追踪依赖 |
| 组件复用 | 工厂函数 createCourseCard() | 单文件组件 .vue |
| 模板 | innerHTML / createElement | 声明式模板 + 编译优化 |
| 工程心智 | 轻量、零依赖 | 约定目录 + 生态(Router/Pinia) |
行业案例 · 贤紫优选商城后台:2024 年运营后台从 jQuery 片段迁移至 Vue 3 + Element Plus,表单页开发效率提升约 40%,同一套组件在 H5 活动页通过 Vite 多入口复用,大促配置页上线周期从 5 天缩短至 2 天。
本章范围:单页课程列表 Demo;路由与全局状态留待 ch02~ch03。
1.2 用 Vite 创建 Vue 项目
# 在 academy 工作区根目录执行
npm create vite@latest academy-demo-vue -- --template vue
cd academy-demo-vue
npm install
npm run dev
默认打开 http://localhost:5173,看到 Vite + Vue 欢迎页即成功。
1.2.1 推荐目录(本章阶段)
academy-demo-vue/
├── index.html
├── package.json
├── vite.config.js
├── public/
│ └── mock/
│ └── courses.json # 从 ch16 复制
├── src/
│ ├── main.js
│ ├── App.vue
│ ├── assets/
│ ├── components/ # ch02 起扩展
│ └── data/
│ └── courses.js # 本地兜底数据
└── README.md
| 文件 | 职责 |
|---|---|
main.js | 创建应用实例、挂载 #app |
App.vue | 根组件,本章承载课程列表 |
public/mock/ | 静态 Mock,与 ch16 保持一致 |
1.2.2 入口文件
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import './assets/main.css'
createApp(App).mount('#app')
1.3 组合式 API 与 <script setup>
Vue 3 推荐 组合式 API(Composition API):按逻辑功能组织代码,而非按 data/methods 选项拆分。
<!-- src/App.vue — 最小骨架 -->
<script setup>
import { ref } from 'vue'
const title = ref('贤紫技术学院')
</script>
<template>
<h1>{{ title }}</h1>
</template>
| 概念 | 说明 |
|---|---|
<script setup> | 编译期语法糖,顶层绑定自动暴露给模板 |
ref() | 包装基本类型或对象引用,通过 .value 读写(模板中自动解包) |
reactive() | 将对象转为深层响应式代理,直接改属性即可 |
<script setup>
import { ref, reactive } from 'vue'
// 基本类型、需要整体替换的用 ref
const keyword = ref('')
const loading = ref(false)
// 聚合状态对象用 reactive
const filters = reactive({
tag: 'all',
level: 'all',
})
</script>
选用原则:需要整体替换的变量用 ref;固定结构的对象用 reactive;勿解构 reactive 对象(会丢失响应式)。
1.4 响应式核心:ref、reactive、computed、watch
1.4.1 computed 派生状态
<script setup>
import { ref, computed } from 'vue'
const courses = ref([
{ id: 'fe-01', title: 'HTML 文档结构', tags: ['html'], level: 'beginner', hours: 2 },
{ id: 'fe-09', title: 'DOM 操作与事件', tags: ['js'], level: 'beginner', hours: 3 },
])
const keyword = ref('')
const activeTag = ref('all')
const filteredCourses = computed(() => {
const q = keyword.value.trim().toLowerCase()
return courses.value.filter((c) => {
const matchQ = !q || c.title.toLowerCase().includes(q) || c.id.includes(q)
const matchTag = activeTag.value === 'all' || c.tags.includes(activeTag.value)
return matchQ && matchTag
})
})
const courseCount = computed(() => filteredCourses.value.length)
</script>
computed 会缓存结果,仅当依赖变化时重新计算——对应 ch16 中 applyFilter() 的声明式写法。
1.4.2 watch 副作用
<script setup>
import { ref, watch } from 'vue'
const keyword = ref('')
const searchLog = ref([])
// 监听 ref
watch(keyword, (newVal, oldVal) => {
if (newVal.trim()) {
searchLog.value.push({ q: newVal, at: Date.now() })
}
})
// 立即执行 + 深度监听(对象)
const prefs = ref({ dark: false })
watch(prefs, (val) => {
localStorage.setItem('academy-prefs', JSON.stringify(val))
}, { deep: true, immediate: true })
</script>
| API | 用途 | 对比 ch16 |
|---|---|---|
computed | 派生、只读(默认) | 替代手动 filter 后赋值 |
watch | 异步副作用、打日志、持久化 | 替代 debounce 回调内的额外逻辑 |
watchEffect | 自动收集依赖的副作用 | 了解即可,本章不展开 |
生产环境搜索防抖可在 ch03 结合自定义 composable 实现;本章先用 v-model 即时过滤理解数据流。