楼层: 首页/ 软件技术/ 前端进阶/ 第 1.5 章 Vue 3 完整教程
V

第 1.5 章 Vue 3 完整教程

Vue 3 · Composition API · Pinia · Vue Router 4

Vue 是渐进式 JavaScript 框架:易学易用、响应式驱动、中国社区强大,文档中文质量极高。React 教你"用 JS 写 UI",Vue 教你"用增强版 HTML 写应用"。后端转前端想快速出活,Vue 3 + <script setup> 是最友好的一条路。

比Vue 2 vs Vue 3

Vue 3 四大升级:① Composition API(组合式)——按逻辑组织代码,告别 Options API 散在 data/methods/computed 里;② Proxy 响应式——替换 Vue 2 的 Object.defineProperty,性能更好、能检测数组索引和新增属性;③ 更小更快、对 TypeScript 支持更好;④ 新增 Fragments(多根节点)、Teleport(传送门)、Suspense(异步组件)。

Vue 3 推荐用 Composition API + <script setup>语法糖。Options API(选项式)仍兼容,老项目能跑。

模板语法:{{ }}、指令、事件、表单

插值 / 属性 / class / 条件 / 列表 / 事件 / 表单

<template> <p>{{ msg }}</p> <!-- 文本插值 --> <p v-html="rawHtml"></p> <!-- 原始 HTML(注意 XSS!) --> <a :href="url" :class="{ active: isOn }"></a> <!-- : 是 v-bind 缩写 --> <button v-if="role==='admin'">管理</button> <!-- v-if 不渲染;v-show 只是 display:none --> <li v-for="item in list" :key="item.id">{{ item.name }}</li> <button @click="count++">@ 是 v-on 缩写</button> <form @submit.prevent="onSubmit"> <!-- .prevent 阻止默认行为 --> <input v-model="name" /> <!-- 双向绑定 --> <input v-model.number="age" /> <!-- .number 转数字 --> <input v-model.lazy="bio" /> <!-- .lazy 改在 change 时同步 --> </form> </template>

v-if vs v-show:v-if 真正销毁/重建 DOM,切换开销大但初始不渲染;v-show 只是 CSS 显示切换,频繁切换用 v-show。事件修饰符 .stop/.prevent/.capture/.self/.once,按键修饰符 .enter/.delete,系统修饰符 .ctrl/.alt/.shift/.meta。

响应式核心:ref 与 reactive

<script setup> 中的响应式

<script setup> import { ref, reactive, computed, watch, watchEffect } from "vue"; const count = ref(0); // 基本类型用 ref,模板里直接 count.value? 不用!模板自动解包 const user = reactive({ name: "小明", age: 18 }); // 对象用 reactive const double = computed(() => count.value * 2); // 计算属性,有缓存 watch(count, (n, o) => console.log(`${o} -> ${n}`)); // 侦听特定源 watchEffect(() => console.log(count.value)); // 自动追踪用到的响应式源,立即执行一次 function inc() { count.value++; } // JS 里必须 .value </script>
reactive 解构丢失响应性

const { name, age } = user 会丢掉响应式!要保持响应式用 toRefs(user):const { name, age } = toRefs(user)。ref 在 JS 里必须 .value,模板里自动解包别加 .value——这是新手最常错的两点。

生命周期

<script setup> 钩子什么时候触发
onMounted组件挂载到 DOM 后(发请求、操作 DOM)。
onUpdated响应式状态变更、DOM 更新后。
onUnmounted组件卸载时(清定时器、解绑事件)。
onBeforeMount/Update/Unmount对应阶段"之前"。
onActivated/onDeactivated<keep-alive> 缓存组件激活/停用。
onErrorCaptured捕获子孙组件错误。

组件:Props / Emits / 插槽 / 依赖注入

父传子 props,子传父 emits

<!-- Child.vue --> <script setup> const props = defineProps({ title: { type: String, required: true }, size: { type: Number, default: 16 }, }); const emit = defineEmits(["update", "delete"]); </script> <template> <h3 @click="emit('update', { title })">{{ title }}</h3> <!-- 插槽:父组件往这里填内容 --> <slot /> <slot name="footer" /> <!-- 具名插槽,父用 #footer --> </template> <!-- 父组件 --> <Child :title="t" @update="onUpdate"> <p>默认插槽内容</p> <template #footer>页脚</template> <!-- # 是 v-slot: 的缩写 --> </Child>

provide/inject:祖先组件 provide('key', value),后代任意层级 inject('key'),跨层级传值不用 props drilling。组件通信全家桶:props 下传、emits 上抛、provide/inject 跨层、pinia 全局、插槽反向传内容。

组合式函数 Composables:Vue 的逻辑复用

Composable 就是一个 use 开头的函数,把响应式逻辑抽出来复用(等价 React Hooks)。比 Mixin 强:命名不冲突、来源清晰、类型友好。

useMouse / useLocalStorage 两个 Composable

// composables/useMouse.ts import { ref, onMounted, onUnmounted } from "vue"; export function useMouse() { const x = ref(0), y = ref(0); const move = (e: MouseEvent) => { x.value = e.clientX; y.value = e.clientY; }; onMounted(() => window.addEventListener("mousemove", move)); onUnmounted(() => window.removeEventListener("mousemove", move)); return { x, y }; } // 任何组件里:const { x, y } = useMouse(); 拿来即用 // composables/useLocalStorage.ts —— 自动持久化 export function useLocalStorage(key: string, defaultValue: string) { const data = ref(localStorage.getItem(key) ?? defaultValue); watch(data, (v) => localStorage.setItem(key, v)); return data; }

Vue Router 4 + Pinia

路由表 + 导航守卫

// router/index.ts import { createRouter, createWebHistory } from "vue-router"; const router = createRouter({ history: createWebHistory(), routes: [ { path: "/", component: Home }, { path: "/user/:id", component: () => import("@/views/User.vue") }, // 懒加载 { path: "/admin", component: Admin, meta: { requiresAuth: true } }, ], }); // 全局守卫:未登录拦去登录页 router.beforeEach((to) => { if (to.meta.requiresAuth && !isLoggedIn()) return { path: "/login" }; });

Pinia(Vue 官方推荐状态管理,替代 Vuex)

// stores/user.ts import { defineStore } from "pinia"; export const useUserStore = defineStore("user", { state: () => ({ name: "", token: "" }), getters: { isLogin: (s) => !!s.token }, // 计算属性 actions: { async login(account: string, pwd: string) { /* 调接口,赋值 this.name / this.token */ }, logout() { this.token = ""; this.name = ""; }, }, }); // 组件里:const u = useUserStore(); u.login(a,p); 模板用 u.isLogin // 解构保持响应式:import { storeToRefs } from 'pinia'; const { name, isLogin } = storeToRefs(u);

Pinia 对比 Vuex:更简单、TypeScript 友好、没有 mutations(直接在 actions 里改 state)、天然模块化。持久化用 pinia-plugin-persistedstate。

完整案例:Vue 3 任务管理应用

技术栈:Vue 3 + TypeScript + Vite + Vue Router 4 + Pinia + Element Plus。目录:src/views/(列表页/详情页)、src/stores/todo.ts(Pinia 存任务列表)、src/composables/useFilter.ts(过滤逻辑)。路由 / 任务列表、/todo/:id 详情;Pinia store 的 state 存 todos: Todo[],actions 写 add/toggle/remove,getters 写 doneCount;Element Plus 的 <el-input><el-button><el-checkbox><el-dialog> 直接搭界面。这套下来就是一个能交付的小工具。

Vue 3React 19
响应式Proxy 自动追踪,ref/reactive 声明即用手动 useState,不可变更新
模板HTML + 指令(v-if/v-for/v-model)JSX,一切都是 JS
状态管理Pinia(官方)Zustand/Redux Toolkit(生态选择)
学习曲线平缓,中文社区强陡,概念多但天花板高
生态企业中后台(Element Plus/Ant Design Vue)全球最大,跨端(React Native)
SSRNuxtNext.js
本章面试题

Q1. Vue 3 的 Proxy 响应式相比 Vue 2 的 Object.defineProperty 解决了什么问题?

看答案

Vue 2 无法检测对象属性新增/删除和数组下标赋值,要 Vue.set 补救;Proxy 能拦截整个对象,新增属性、数组操作都自动追踪,且性能更好。

Q2. ref 和 reactive 怎么选?解构 reactive 为什么丢响应性?

看答案

基本类型用 ref,对象用 reactive。解构 reactive 拿到的是普通变量副本,断了 Proxy 追踪,要用 toRefs 包装。

Q3. v-if 和 v-show 的区别?v-for 为什么不能和 v-if 同用?

看答案

v-if 销毁/重建 DOM,v-show 切 display。v-for 优先级高于 v-if,同用时每次循环都先判断,性能差且逻辑混乱,应改用 computed 先过滤再 v-for。

Q4. Pinia 相比 Vuex 砍掉了什么?

看答案

没有 mutations,state 直接在 actions 里改;模块化天然(每个 defineStore 独立);TS 推断更好。