学习指南:Vue 是什么、怎么学、学到什么
Vue 3 是当前最主流的渐进式 JavaScript 框架。它用声明式渲染 + 响应式数据 + 组件化三大特性,让你能用更少代码写出可维护的前端应用。
0.1 Vue 三大特性
💡 三件套
声明式渲染 · 用模板描述"长什么样" 响应式数据 · 数据变化自动更新界面 组件化 · 把页面拆成一个个可复用单元
0.2 Vue 2 vs Vue 3
| 维度 | Vue 2 | Vue 3 |
|---|---|---|
| 语法 | Options API | Composition API(推荐) |
| 响应式 | Object.defineProperty | Proxy(更强大) |
| TypeScript | 支持一般 | 原生友好 |
| 状态管理 | Vuex | Pinia(官方推荐) |
| 构建工具 | Vue CLI | Vite |
0.3 路线
- 模板语法 + 响应式数据 + 事件
- 组件化(props / emit / slot)
- 组合式 API(setup / ref / computed / watch)
- 生态:Vue Router + Pinia
- 工程化:Vite + 单文件组件 + 部署
前置要求:需要掌握 HTML + CSS + JavaScript(尤其是 ES6+ 语法)。
快速起步 ⭐⭐⭐
1.1 两种启动方式
| 方式 | 适用 | 命令 |
|---|---|---|
| CDN 引入 | 学习、demo | 直接在 HTML 加 <script> |
| Vite + npm | 真实项目 | npm create vue@latest |
1.2 CDN Hello World
HTML
<div id="app">{{ msg }}</div>
<script type="module">
import { createApp, ref } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
createApp({
setup() {
const msg = ref("你好,Vue 3!");
return { msg };
}
}).mount("#app");
</script>
1.3 用 Vite 脚手架
终端
npm create vue@latest
# 按提示选择 TypeScript / Router / Pinia / 测试
cd my-app
npm install
npm run dev # 启动 localhost:5173
模板语法 ⭐⭐⭐
2.1 插值
Vue 模板
<template>
<p>{{ msg }}</p>
<p>{{ n + 1 }}</p>
<p>{{ ok ? "YES" : "NO" }}</p>
<p>{{ formatDate(d) }}</p>
</template>
2.2 常见指令
| 指令 | 作用 |
|---|---|
v-text | 更新文本 |
v-html | 解析 HTML(注意 XSS) |
v-show | 切换 CSS display |
v-if / v-else | 条件渲染(条件为 false 时销毁) |
v-for | 列表渲染(必须加 :key) |
v-on / @ | 事件监听 |
v-bind / : | 属性绑定 |
v-model | 双向绑定(表单) |
Vue 模板
<a :href="url" :title="tip">链接</a>
<img :src="src" alt="">
<button @click="onClick">点击</button>
<input @keyup.enter="submit">
<p v-if="seen">看到了</p>
<p v-else>没看到</p>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
响应式数据 ref / reactive ⭐⭐⭐
3.1 ref:让任意类型变响应式
JavaScript (Vue)
import { ref } from "vue";
const count = ref(0);
console.log(count.value); // 0(脚本里要 .value)
count.value++; // 修改
// 模板里自动解包,不需要 .value
<template>
<button @click="count++">{{ count }}</button>
</template>
3.2 reactive:对象式响应式
JavaScript (Vue)
import { reactive } from "vue";
const state = reactive({
name: "张三",
age: 18,
hobbies: ["代码", "音乐"]
});
state.age = 19; // 直接改属性
state.hobbies.push("阅读"); // 数组方法也响应
3.3 ref vs reactive 之选
| ref | reactive |
|---|---|
| 任何值(基本/对象) | 只用于对象 |
| 用 .value 访问 | 直接访问属性 |
| 现代风格推荐 | 深度嵌套数据方便 |
推荐:默认用
ref,对象大且深嵌套时用 reactive。
计算属性 computed ⭐⭐
Vue
import { ref, computed } from "vue";
const firstName = ref("三");
const lastName = ref("张");
const fullName = computed(() => lastName.value + firstName.value);
// fullName 是只读的
<p>{{ fullName }}</p>
computed vs method
| computed | method |
|---|---|
| 有缓存(依赖不变不重算) | 每次重算 |
| 适合"由其他数据派生"的值 | 适合事件/动作 |
侦听器 watch / watchEffect ⭐⭐⭐
Vue
import { ref, watch, watchEffect } from "vue";
const id = ref(1);
// 监听一个 ref
watch(id, (newV, oldV) => {
console.log(`id 从 ${oldV} → ${newV}`);
});
// 立即执行 + 自动监听
watchEffect(() => {
console.log("id = ", id.value);
// 自动收集依赖
});
// 监听对象里的属性
const user = reactive({ name: "Alice", age: 18 });
watch(() => user.age, (n, o) => console.log(n));
watch 选项速查
| 选项 | 作用 |
|---|---|
immediate: true | 立即执行一次回调 |
deep: true | 深度监听对象内部 |
flush: "post" | DOM 更新后再触发 |
class 与 style 绑定 ⭐⭐
Vue 模板
<div :class="{ active: isActive, 'text-danger': hasError }">对象式</div>
<div :class="[a, b]">数组式</div>
<div :style="{ color: c, fontSize: size + 'px' }">行内</div>
条件渲染与列表渲染 ⭐⭐⭐
7.1 v-if vs v-show
| v-if | v-show |
|---|---|
| false 时不创建 | 始终创建,CSS 切换 |
| 首次渲染慢,切换开销小 | 首次渲染快,切换开销大 |
| 适合不常切换 | 适合频繁切换 |
7.2 v-for 必须 :key
Vue
<ul>
<li v-for="(user, i) in users"
:key="user.id">
{{ i }} - {{ user.name }}
</li>
</ul>
⚠️ key 别用 index
:key="i" 在数据顺序变化时会导致 DOM 错误复用,应使用稳定 id。
事件处理 ⭐⭐
Vue
<button @click="count++">+</button>
<button @click="say('hi')">问候</button>
// 事件修饰符
<form @submit.prevent="onSubmit"> // e.preventDefault()
<div @click.stop="onClick"> // e.stopPropagation()
<div @click.once="onClick"> // 只触发一次
<input @keyup.enter="submit"> // 回车键
<input @click.ctrl="onClick"> // Ctrl+点击
表单输入绑定 v-model ⭐⭐⭐
9.1 各种控件
Vue 模板
<input v-model="name" /> /* 文本 */
<input type="checkbox" v-model="ok"/> /* 复选框(boolean) */
<input type="checkbox" value="vue"
v-model="arr"/> /* 数组(多选) */
<input type="radio" v-model="pick" />/* 单选 */
<select v-model="city">...</select> /* 下拉 */
<textarea v-model="msg"></textarea> /* 多行 */
// 修饰符
<input v-model.lazy="name"> // change 时同步
<input v-model.number="age"> // 转数字
<input v-model.trim="name"> // 去空白
Vue 实例生命周期 ⭐⭐
Vue
import { onMounted, onUnmounted, ref } from "vue";
setup() {
const list = ref([]);
onMounted(async () => {
const r = await fetch("/api/list");
list.value = await r.json();
});
let timer;
onMounted(() => timer = setInterval(tick, 1000));
onUnmounted(() => clearInterval(timer));
return { list };
}
单文件组件 SFC ⭐⭐⭐
真实项目用 .vue 文件,每个组件内含三段:<template> + <script> + <style>。
App.vue
<template>
<div class="box">
<h1>{{ msg }}</h1>
<button @click="inc">{{ count }}</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const msg = ref("Hello Vue");
const count = ref(0);
const inc = () => count.value++;
</script>
<style scoped>
.box { padding: 20px; }
.box h1 { color: #42B883; }
</style>
scoped 让样式只作用于当前组件,避免样式污染。
组件基础 ⭐⭐⭐
12.1 注册与使用
MyButton.vue
<template>
<button :disabled="disabled" class="btn" @click="emit('click')">
<slot />
</button>
</template>
<script setup>
defineProps({ disabled: Boolean });
const emit = defineEmits(["click"]);
</script>
<style scoped>
.btn {
background: #42B883; color: #fff;
border: none; border-radius: 6px;
padding: 8px 16px;
}
</style>
12.2 在父组件使用
App.vue
<template>
<MyButton @click="onClick">点我</MyButton>
<MyButton disabled>禁用</MyButton>
</template>
<script setup>
import MyButton from "./components/MyButton.vue";
const onClick = () => alert("click");
</script>
12.3 全局注册
main.js
import { createApp } from "vue";
import MyButton from "./components/MyButton.vue";
const app = createApp({});
app.component("MyButton", MyButton);
app.mount("#app");
组件通信:props / emit / slot ⭐⭐⭐
13.1 父传子:defineProps
Child.vue
<script setup>
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 },
tags: { type: Array, default: () => [] }
});
</script>
<template>
<h2>{{ props.title }} ({{ props.count }})</h2>
</template>
13.2 子传父:defineEmits
Child.vue
<template>
<button @click="send">发给爸爸</button>
</template>
<script setup>
const emit = defineEmits(["greet"]);
const send = () => emit("greet", "hi from child");
</script>
// 父组件
<Child @greet="onGreet" />
const onGreet = (msg) => console.log(msg);
13.3 插槽 slot
Card.vue
<template>
<div class="card">
<div class="header"><slot name="header" /></div>
<div class="body"><slot /></div>
<div class="footer"><slot name="footer" /></div>
</div>
</template>
<Card>
<template #header>标题</template>
<p>默认插槽内容</p>
<template #footer><button>确定</button></template>
</Card>
组件通信高级:v-model / provide-inject ⭐⭐
14.1 父子 v-model
自定义 v-model 组件
<script setup>
defineProps(["modelValue"]);
const emit = defineEmits(["update:modelValue"]);
const input = (e) => emit("update:modelValue", e.target.value);
</script>
<template>
<input :value="modelValue" @input="input"/>
</template>
// 父组件
<MyInput v-model="text" />
14.2 provide / inject(跨层级)
祖
<script setup>
import { provide, ref } from "vue";
const user = ref({ name: "Alice" });
provide("user", user);
</script>
// 孙组件
<script setup>
import { inject } from "vue";
const user = inject("user");
</script>
组件设计原则
✅ 良好组件
- 职责单一(一个组件做一件事)
- 可独立复用,依赖低
- Props 通过
defineProps显式声明类型 - 状态提升到共同父组件
- 跨层级通信用 provide/inject 或 状态管理库
❌ 反模式
- 巨型组件(> 500 行):拆
- 深嵌套 props:考虑 provide/inject
- 业务逻辑混在模板里:抽出为 setup 函数
- 父子双向直接改 props:永远不允许
组合式 API 入门 setup ⭐⭐⭐
setup 是 Vue 3 推荐的写法。所有逻辑都在这里组织,而非 Options API 的 data/methods/computed 分块。这样更易抽离复用。
16.1 setup 两种风格
Vue SFC
<script setup>
// 顶层变量、函数自动暴露给模板
import { ref, computed } from "vue";
const n = ref(0);
const double = computed(() => n.value * 2);
function inc() { n.value++; }
</script>
<template>
<button @click="inc">{{ n }} × 2 = {{ double }}</button>
</template>
16.2 抽离逻辑到 composable
useMouse.js
import { ref, onMounted, onUnmounted } from "vue";
export function useMouse() {
const x = ref(0), y = ref(0);
function upd(e) {
x.value = e.clientX;
y.value = e.clientY;
}
onMounted(() => window.addEventListener("mousemove", upd));
onUnmounted(() => window.removeEventListener("mousemove", upd));
return { x, y };
}
// 任何组件都可以:
// import { useMouse } from "./useMouse";
// const { x, y } = useMouse();
深入:ref vs reactive 的内部原理
17.1 ref 的内部
示意
// ref 内部大致是:
class RefImpl {
constructor(value) {
this._value = toReactive(value);
}
get value() {
track(this); // 依赖收集
return this._value;
}
set value(v) {
this._value = toReactive(v);
trigger(this); // 通知更新
}
}
17.2 模板自动解包
<p>{{ count }}</p> 实际编译为 count.value,但在子组件或模板中会自动解包。
reactive 解包
const obj = reactive({ a: 1 });
const wrapped = { x: obj };
wrapped.x.a; // ❌ 不响应(被包了一层普通对象)
wrapped.x.a = 2; // ❌ 不会触发更新
// 解决:把 reactive 重新赋值给响应式
wrapped.x = reactive(wrapped.x);
wrapped.x.a = 2; // ✅ 响应
ref 在模板与 DOM 的用法 ⭐⭐
Vue SFC
<script setup>
import { ref } from "vue";
const inp = ref(null);
onMounted(() => inp.value.focus());
</script>
<template>
<input ref="inp" type="text">
</template>
Vue Router 路由 ⭐⭐⭐
19.1 安装与配置
终端
npm install vue-router@4
19.2 路由配置
router/index.js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import About from "../views/About.vue";
import NotFound from "../views/NotFound.vue";
export default createRouter({
history: createWebHistory(),
routes: [
{ path: "/", component: Home, name: "home" },
{ path: "/about", component: About, name: "about" },
{ path: "/user/:id", component: User, props: true },
{ path: "/:pathMatch(.*)*", component: NotFound }
]
});
19.3 在模板里使用
App.vue
<template>
<nav>
<RouterLink to="/">首页</RouterLink>
<RouterLink :to="{ name: 'about' }">关于</RouterLink>
</nav>
<main>
<RouterView />
</main>
</template>
<script setup>
import { useRouter, useRoute } from "vue-router";
const router = useRouter();
function go() {
router.push("/about");
}
</script>
19.4 路由参数
使用
<RouterLink :to="`/user/${user.id}`">{{ user.name }}</RouterLink>
// User.vue
import { useRoute } from "vue-router";
const route = useRoute();
console.log(route.params.id); // url 上的 id
console.log(route.query.tab); // ?tab=xxx
19.5 导航守卫
router
router.beforeEach((to, from) => {
if (to.requiresAuth && !isLoggedIn())
return { name: "login", query: { redirect: to.fullPath } };
});
Pinia 状态管理 ⭐⭐⭐
20.1 安装与定义 Store
stores/user.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useUserStore = defineStore("user", () => {
const name = ref("游客");
const token = ref("");
const isLogin = computed(() => !!token.value);
function login(u) {
name.value = u.username;
token.value = u.token;
}
function logout() {
name.value = "";
token.value = "";
}
return { name, token, isLogin, login, logout };
});
20.2 在组件中使用
任意组件
<script setup>
import { useUserStore } from "@/stores/user";
import { storeToRefs } from "pinia";
const userStore = useUserStore();
const { name, isLogin } = storeToRefs(userStore);
const login = userStore.login; // 方法直接拿
function doLogin() {
login({ username: "Alice", token: "abc" });
}
</script>
<template>
<p>欢迎 {{ name }} ({{ isLogin ? "在线" : "离线" }})</p>
<button @click="doLogin">登录</button>
</template>
对比 Vuex:Pinia 写法更简洁(TypeScript 友好),是官方推荐。
过渡与动画 ⭐⭐
Vue SFC
<template>
<button @click="show = !show">切换</button>
<Transition name="fade">
<p v-if="show">你好</p>
</Transition>
</template>
<style>
.fade-enter-active, .fade-leave-active { transition: opacity 0.5s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>
21.1 TransitionGroup 列表过渡
Vue
<TransitionGroup name="list" tag="ul">
<li v-for="t in todos" :key="t.id">{{ t.text }}</li>
</TransitionGroup>
.list-enter-from { opacity: 0; transform: translateY(-20px); }
.list-leave-to { opacity: 0; transform: translateX(20px); }
.list-move { transition: transform 0.5s; }
自定义指令 ⭐
Vue
// 全局
app.directive("focus", {
mounted(el) { el.focus(); }
});
// 局部
const vDemo = {
mounted(el, binding) {
el.style.color = binding.value;
}
};
<input v-focus>
<p v-demo="color">看我</p>
v-once / v-memo 等性能优化 ⭐
| 指令 | 作用 |
|---|---|
v-once | 只渲染一次(极致静态) |
v-memo | 依数组决定是否更新 |
v-pre | 跳过编译,原样显示 |
Vue
<div v-once>永远不再变</div>
<div v-pre>{{ 不会编译 }}</div>
// v-memo (3.2+)
<div v-memo="[item.id, item.text]">{{ item.text }}</div>
Vite 构建工具 ⭐⭐
24.1 核心特点
- 基于原生 ESM(浏览器直接 import)→ 启动极快
- HMR 热更新(毫秒级)
- 生产用 Rollup 打包
24.2 常用命令
Vite
npm run dev # 开发服务器
npm run build # 生产构建(dist/)
npm run preview # 预览构建结果
24.3 vite.config.js
vite.config.js
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { fileURLToPath, URL } from "node:url";
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url))
}
},
server: { port: 5173 }
});
插件与混入 ⭐
25.1 插件(plugin)
Vue
// 全局属性
app.config.globalProperties.$http = axios;
// install 风格
export default {
install(app, options) {
app.directive("demo", {...});
app.provide("key", options);
}
};
25.2 混入 mixin(不推荐)
mixin.js
export const mouseMixin = {
data() { return { x: 0, y: 0 }; },
mounted() { window.addEventListener("mousemove", this.upd); }
};
import { mouseMixin } from "./mixin.js";
export default { mixins: [mouseMixin] };
// 推荐改写为 composable(见 16 章 useMouse)
项目一:Todo 应用(Composition API)⭐⭐⭐
项目 1:Todo List(组合式 API)
useTodo.js
import { ref, computed } from "vue";
export function useTodo() {
const list = ref(JSON.parse(localStorage.getItem("todo") || "[]"));
const text = ref("");
const filter = ref("all");
function save() {
localStorage.setItem("todo", JSON.stringify(list.value));
}
function add() {
if (!text.value.trim()) return;
list.value.push({ id: Date.now(), text: text.value, done: false });
text.value = "";
save();
}
function toggle(id) {
const it = list.value.find(t => t.id === id);
if (it) it.done = !it.done;
save();
}
function remove(id) {
list.value = list.value.filter(t => t.id !== id);
save();
}
const view = computed(() =>
filter.value === "all" ? list.value :
filter.value === "active" ? list.value.filter(t => !t.done) :
list.value.filter(t => t.done)
);
return { list, text, filter, view, add, toggle, remove };
}
项目二:图书管理 SPA ⭐⭐⭐
项目 2:图书管理(Vue Router + Pinia)
27.1 store
stores/book.js
import { defineStore } from "pinia";
import { ref, computed } from "vue";
export const useBookStore = defineStore("book", () => {
const list = ref([
{ id: 1, title: "Vue 入门", author: "张三" },
{ id: 2, title: "JavaScript 高级", author: "李四" }
]);
const count = computed(() => list.value.length);
function add(book) { list.value.push({ id: Date.now(), ...book }); }
function del(id) { list.value = list.value.filter(b => b.id !== id); }
function find(id) { return list.value.find(b => b.id === Number(id)); }
return { list, count, add, del, find };
});
27.2 路由 + 视图
main.js
import { createApp } from "vue";
import { createPinia } from "pinia";
import router from "./router";
import App from "./App.vue";
createApp(App).use(createPinia()).use(router).mount("#app");
BookList.vue
<script setup>
import { useBookStore } from "@/stores/book";
import { storeToRefs } from "pinia";
import { useRouter } from "vue-router";
const store = useBookStore();
const { list, count } = storeToRefs(store);
const router = useRouter();
</script>
<template>
<h2>图书列表({{ count }})</h2>
<RouterLink to="/book/new">新增</RouterLink>
<ul>
<li v-for="b in list" :key="b.id">
<RouterLink :to=`/book/${b.id}`">{{ b.title }}</RouterLink>
<button @click="store.del(b.id)">删</button>
</li>
</ul>
</template>
项目三:电商商品列表 ⭐⭐⭐
项目 3:商品列表(Fetch + 过滤 + 详情页)
Products.vue
<script setup>
import { ref, computed, onMounted } from "vue";
import ProductCard from "@/components/ProductCard.vue";
const products = ref([]);
const keyword = ref("");
const category = ref("all");
onMounted(async () => {
const r = await fetch("https://fakestoreapi.com/products");
products.value = await r.json();
});
const list = computed(() => {
const kw = keyword.value.toLowerCase();
return products.value.filter(p =>
(category.value === "all" || p.category === category.value) &&
p.title.toLowerCase().includes(kw)
);
});
</script>
<template>
<input v-model="keyword" placeholder="搜索">
<select v-model="category">
<option value="all">全部</option>
<option value="electronics">电子</option>
<option value="jewelery">珠宝</option>
</select>
<div class="grid">
<ProductCard
v-for="p in list" :key="p.id" :product="p" />
</div>
</template>
<style scoped>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; }
</style>
Vue 速查手册 + 课堂训练
29.1 API 速查
响应式
ref(value)reactive(obj)computed(fn)watch(src, cb)watchEffect(fn)
生命周期
onMountedonUpdatedonUnmountedonBeforeMountonErrorCaptured
组件
defineProps()defineEmits()defineExpose()<slot>useSlots/useAttrs
指令
v-if / v-showv-for + :keyv-model.lazy/number/trimv-on @ + 修饰符v-bind :v-once / v-memo / v-pre
Router
createRouter()<RouterLink /><RouterView />useRouter()useRoute()router.beforeEach
Pinia
defineStore(id, setup)storeToRefs(store)$state / $patch$subscribe / $onAction
依赖注入
provide(key, value)inject(key, default)
工具
nextTick()useTemplateRef()app.use(plugin)app.component()app.directive()
29.2 课堂训练(12 题)
Q1. 在 setup 中,ref 与 reactive 的核心区别是?
- A. ref 只能装基本类型,reactive 只能装对象
- B. ref 用 .value 取值;reactive 直接拿属性
- C. 两者完全一样
- D. reactive 是 ES6 proxy 实现
答案:B。ref 任何值通用,模板自动解包;reactive 仅对象,直接拿属性。
Q2. v-if 与 v-show 的区别?
- A. 完全相同
- B. v-show 不支持 boolean
- C. v-if 在 false 时不创建元素;v-show 始终在,CSS 切换
- D. v-show 性能始终更好
答案:C。v-if 是真渲染/销毁,v-show 是切换 display。
Q3. v-for 列表必须加 :key,为什么?
- A. 帮助 Vue 高效复用和重排 DOM
- B. 必需否则报错
- C. 纯属风格问题
- D. 必须用 index
答案:A。key 是 diff 算法的标识,最好用稳定 id,不要用 index。
Q4. 父 → 子传值用?子 → 父传值用?
- A. props / ref
- B. slot / slot
- C. attrs / props
- D. props / emit
答案:D。这是 Vue 组件通信的基础模式。
Q5. computed 与 method 的差别?
- A. 性能完全相同
- B. computed 有缓存,依赖不变不重算
- C. method 有缓存,computed 没有
- D. computed 不能传参
答案:B。这是 computed 作为"派生数据"的核心优势。
Q6. onMounted 里适合做什么?
- A. 修改 props
- B. 写响应式数据声明
- C. 发请求、操作 DOM、绑定事件
- D. 都不合适
答案:C。DOM 已渲染好,可以与外部交互。
Q7. <RouterView> 与 <RouterLink> 的区别?
- A. RouterLink 是路由链接,RouterView 渲染当前路由对应的组件
- B. RouterView 生成链接
- C. 两者一样
- D. RouterLink 是 v-if 的另一种写法
答案:A。记住:Link = a,View = outlet。
Q8. Pinia 相较 Vuex 的最大好处?
- A. 支持异步
- B. 更稳定
- C. 体积更小
- D. 写法更简洁,TS 友好,无需 mutations
答案:D。官方推荐 Pinia 替代 Vuex。
Q9. ref 在模板中会自动 .value 解包,例外是?
- A. v-for 中不会解包
- B. 当 ref 是对象/数组的 reactive 属性时不会解包
- C. 永远不会解包
- D. 在 setup 中不会解包
答案:B。如果 ref 被嵌套为
reactive 对象的属性,将保持 .value 形态。Q10. 想让多个组件共享一份响应式数据,应该?
- A. 每个组件都自己 ref 一次
- B. 用 mixin
- C. 用 Pinia / provide-inject
- D. window 全局变量
答案:C。Pinia 是跨组件/页面共享的最佳答案。
Q11. <script setup> 的最大优势?
- A. 提升运行时性能
- B. 顶层变量自动暴露,少写 return,TS 更友好
- C. 兼容老 IE
- D. Vue2 不能用
答案:B。setup 语法糖是 Composition API 的最佳实践。
Q12. Vite 相比 webpack 的关键优势?
- A. 基于原生 ESM 启动秒级,开发体验极快
- B. 体积更小
- C. SEO 更好
- D. 服务端渲染更好
答案:A。Vite 已成 Vue 默认工具链。
写在最后:Vue 是现代化前端的入口
🎓 与本系列衔接
HTML → 网页骨架 CSS → 网页样式 JavaScript→ 网页交互 Vue → 组件化 + 响应式前端框架
学完 Vue 之后,下一步可往 Nuxt 3 SSR、Element Plus / Naive UI、Pinia 高级用法、TypeScript 强化 继续深入。
推荐资源:Vue 官方文档(vuejs.org)、Vue Mastery、Vue School、Vite 官方文档。