认识 Vue
Vue.js 是一款渐进式前端框架,核心信息如下:
- 诞生:2014 年由尤雨溪创造。
- 核心聚焦:专注于视图层,以简洁方式构建用户界面。
- 核心特点:
- 轻量级,API 简洁易懂;
- 支持双向数据绑定,实现数据与视图联动;
- 组件化开发,提升代码复用性;
- 生态丰富,可灵活与其他库或现有项目集成;
- 渐进式特性:可根据需求逐步引入功能,从简单应用扩展到复杂项目。
Vue 就像一个 "智能管家",专门帮你处理网页的交互逻辑。如果说原生 JS 操作 DOM 是 "自己动手做饭"(需要手动切菜、开火、调味,步骤繁琐),那 Vue 就是 "点外卖"------ 你只需要告诉它 "要什么"(数据和逻辑),它会自动帮你处理 "怎么做"(DOM 操作、更新视图),大大简化开发流程。
第一部分:Vue.js 入门基础
1.1 Vue.js 简介
什么是 Vue.js
Vue.js 是一套渐进式 JavaScript 框架,核心用于构建用户界面。所谓渐进式,即框架的能力可以按需分层引入:你可以仅用核心响应式能力开发简单页面,也可以逐步接入组件系统、路由、状态管理等全家桶能力,适配从简单交互页到大型单页应用(SPA)的全场景开发。
核心特点
- 易用性:HTML/CSS/JavaScript 基础即可上手,文档友好,API 简洁直观,学习曲线平缓。
- 灵活性:既可以嵌入现有页面增强交互,也可以基于全家桶构建完整的单页应用。
- 高效性:基于虚拟 DOM 与差异化更新,最小化 DOM 操作,保证渲染性能。
- 响应式数据绑定:数据与视图双向关联,数据变更自动触发视图更新,无需手动操作 DOM。
- 组件化开发:将页面拆分为独立、可复用的组件,实现高内聚、低耦合的代码设计。
适用场景
- 传统网页的交互增强、动态内容渲染
- 中后台管理系统、数据可视化平台
- 单页面应用(SPA)、移动端 H5 页面
- 跨端应用(小程序、原生 App、桌面端应用)
Vue.js 与其他框架的简要比较
| 框架 | 核心特点 | 学习曲线 | 生态 | 适用场景 |
|---|---|---|---|---|
| Vue.js | 渐进式、模板与 JSX 双支持、响应式开箱即用 | 平缓 | 官方维护核心生态(路由 / 状态管理),社区丰富 | 中小项目到大型企业级项目,兼顾开发效率与灵活性 |
| React | 函数式编程、JSX 优先、单向数据流、灵活度极高 | 中等 | 社区生态极其庞大,方案多样 | 大型复杂前端项目,跨端全栈场景 |
| Angular | 大而全的全家桶方案、TypeScript 原生支持、强约束 | 陡峭 | 官方提供完整能力,企业级生态完善 | 大型企业级中后台项目,强规范的团队协作场景 |
1.2 环境搭建与第一个应用
安装方式
-
CDN 引入(入门首选) 适合快速体验、小型项目或传统页面嵌入,直接在 HTML 中引入脚本:
html<!-- Vue 3 开发版(含完整警告和调试信息) --> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <!-- Vue 2 开发版 --> <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> -
包管理器安装(工程化项目) 适合大型项目开发,配合构建工具使用:
# npm 安装 Vue 3 npm install vue@3 # yarn 安装 yarn add vue@3 # pnpm 安装(推荐,性能更优) pnpm add vue@3
- 安装 Node.js:提供 JavaScript 运行时环境,是 Vue 开发的基础;
- 安装 npm 或 yarn:Node.js 自带的包管理工具(npm)或替代工具(yarn),用于管理项目依赖(如 Vue 库、插件等)。
项目脚手架搭建
-
Vite(Vue 3 官方推荐) 基于原生 ES 模块的新一代构建工具,冷启动快、热更新性能优异,是 Vue 3 项目的首选:
# 创建项目 npm create vite@latest vue-project -- --template vue # 进入项目目录 cd vue-project # 安装依赖 npm install # 启动开发服务 npm run dev -
Vue CLI(Vue 2 适配) Vue 官方旧版脚手架,基于 Webpack,适配 Vue 2 项目,Vue 3 项目已不推荐:
# 全局安装CLI npm install -g @vue/cli # 创建项目 vue create vue-project
第一个 Vue 应用
Vue 3 示例(CDN 版)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Hello Vue 3</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<!-- 挂载容器 -->
<div id="app">{{ message }}</div>
<script>
// 创建Vue应用实例
const { createApp } = Vue
createApp({
// 响应式数据
data() {
return {
message: 'Hello Vue!'
}
}
}).mount('#app') // 挂载到DOM节点
</script>
</body>
</html>
Vue 2 示例(CDN 版)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Hello Vue 2</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="app">{{ message }}</div>
<script>
// 创建Vue实例
new Vue({
el: '#app', // 挂载选项
data() {
return {
message: 'Hello Vue!'
}
}
})
</script>
</body>
</html>
核心说明:
- Vue 3 采用
createApp()创建应用实例,通过.mount()方法挂载到 DOM;Vue 2 采用new Vue()创建实例,通过el选项或$mount()挂载。 data选项用于定义响应式数据,模板中通过双大括号插值直接访问。
各部分介绍
html
<!-- 视图:显示数据 -->
<div id="app">
<p>{{ message }}</p> <!-- 双大括号是Vue的模板语法,用于显示数据 -->
<button @click="changeMessage">点我改内容</button>
</div>
<!-- 引入Vue -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
// Vue实例:管理数据和逻辑
const { createApp } = Vue;
createApp({
data() { // 存放数据(相当于温度计的"温度")
return {
message: "Hello Vue!"
}
},
methods: { // 存放方法(操作数据的逻辑)
changeMessage() {
this.message = "Vue 真简单!"; // 修改数据
// 不需要手动改DOM,视图会自动更新
}
}
}).mount('#app'); // 把Vue实例挂载到id为app的元素上
</script>
- 核心关系与原理:
- 实例与容器对应:一个 Vue 实例通常绑定一个 DOM 容器(通过
el配置),负责管理该容器内的视图; - 数据绑定:Vue 的核心特性,实现数据与视图的双向同步 ------ 数据变化时视图自动更新,视图操作(如表单输入)时数据同步变化。在 Vue 中,你只需要关注 "数据",视图会自动跟随数据变化。比如:
- 实例与容器对应:一个 Vue 实例通常绑定一个 DOM 容器(通过
1.3 模板语法
模板语法是框架连接数据与视图的核心桥梁,分为插值语法与指令语法 两类,前者负责数据渲染,后者处理 DOM 行为控制。模板语法可以理解为 "带动态功能的 HTML 模板",核心是用声明式的方式把数据和 DOM 关联起来,让开发者不用手动操作 DOM,只需关注 "数据是什么" 和 "页面该怎么展示"。
插值语法
- 语法:使用双大括号
{``{ }}(Mustache 语法) - 功能:将数据值渲染到页面,当数据发生变化时,页面显示会同步更新(依赖框架的响应式系统)
html
<!-- 文本插值 -->
<p>{{ message }}</p>
<!-- 支持JavaScript表达式 -->
<p>{{ number + 1 }}</p>
<p>{{ isOk ? '已完成' : '未完成' }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
<!-- 一次性插值,数据变化不更新 -->
<p v-once>{{ message }}</p>
注意:双大括号内仅支持单个 JavaScript 表达式,不支持语句、循环、条件判断等复杂代码。
核心指令
指令是带有 v- 前缀的特殊属性,用于响应式地作用于 DOM,当表达式的值变化时,指令会更新 DOM。
| 指令 | 作用 | 核心用法 |
|---|---|---|
v-bind |
动态绑定 HTML 属性、组件 props | <img :src="imgUrl" :title="imgTitle"> |
v-model |
表单元素双向数据绑定 | <input v-model="inputValue"> |
v-for |
列表渲染,循环遍历数组 / 对象 | <li v-for="(item, index) in list" :key="index">{``{ item }}</li> |
v-if / v-else / v-else-if |
条件渲染,满足条件才渲染节点,不满足则销毁 / 不创建 | <div v-if="type === 1">类型1</div><div v-else-if="type === 2">类型2</div><div v-else>其他类型</div> |
v-show |
条件展示,始终渲染节点,通过 display 控制显隐 |
<div v-show="isShow">展示内容</div> |
v-on |
事件监听,绑定 DOM 事件 | <button @click="handleClick">点击按钮</button> |
指令缩写
为简化开发,Vue 为高频指令提供了缩写:
v-bind:缩写为:v-on:缩写为@v-slot:缩写为#(插槽相关,后续详解)
具体介绍
-
v-bind:用于绑定 HTML 属性,简化属性绑定操作-
基础语法:v-bind:属性名="表达式",简写为 :属性名="表达式"
-
示例:普通属性:图片路径、链接地址等
html<!--普通属性:图片路径、链接地址等--> <img :src="productImg" :alt="productName"> <a :href="detailUrl" :title="productDesc">查看详情</a>CSS 类名:支持对象语法(条件绑定)和数组语法(多类名)
html<!-- 对象语法:isActive为true时添加active类 --> <div :class="{ active: isActive, disabled: isDisabled }"></div> <!-- 数组语法:绑定多个固定类名 --> <div :class="[baseClass, themeClass]"></div>内联样式:支持对象语法和数组语法
html<div :style="{ fontSize: '16px', color: textColor }"></div>
-
-
v-model:实现双向数据绑定,主要用于表单元素-
功能:表单元素值与数据双向同步(输入框内容变化时数据更新,数据变化时输入框内容同步更新)本质是 v-bind:value + v-on:input
-
示例:支持元素 :input(文本 / 复选 / 单选)、textarea、select
html<!-- 文本输入框 --> <input v-model="username" type="text"> <!-- 复选框 --> <input v-model="isAgree" type="checkbox"> <!-- 下拉选择框 --> <select v-model="selectedCity"> <option value="beijing">北京</option> <option value="shanghai">上海</option> </select>常用修饰符:
.lazy:失去焦点后同步数据(默认实时同步)
html<input v-model.lazy="username">number:自动转换为数字类型(避免输入框返回字符串)
html<input v-model.number="age" type="number">.trim:自动去除首尾空格
html<input v-model.trim="searchKey">
-
-
v-if,v-show用于条件渲染-
- 示例:
<p v-if="isShow">显示内容</p>(isShow为true时,<p>标签及内容被渲染)
- 示例:
-
-
v-for:用于列表渲染,可遍历数组、对象、字符串等-
语法:
-
遍历数组:v-for="(item, index) in array" :key="uniqueKey"
-
遍历对象:v-for="(value, key, index) in object" :key="key"
-
遍历数字:v-for="n in 5" :key="n"(生成 1-5 的序列)
-
-
关键规则:key 属性
-
key 用于标识唯一元素,帮助框架高效更新 DOM,需遵循以下原则:
-
优先使用数据唯一标识(如 item.id),避免用 index(数组排序 / 删除时会导致 DOM 复用错误)
html<!-- ❌ 不推荐:index会随数组变化 --> <li v-for="(item, index) in list" :key="index">{{ item.name }}</li> <!-- ✅ 推荐:id唯一且稳定 --> <li v-for="item in list" :key="item.id">{{ item.name }}</li> -
template 标签不能绑定 key,需绑定到实际 DOM 元素上
html<!-- ❌ 错误:key不能绑定在template上 --> <template v-for="item in list" :key="item.id"> <div>{{ item.name }}</div> </template> <!-- ✅ 正确:key绑定到实际元素 --> <template v-for="item in list"> <div :key="item.id">{{ item.name }}</div> </template> -
嵌套循环示例:
html<!-- 遍历二维数组:省份->城市 --> <div v-for="province in provinces" :key="province.id"> <h3>{{ province.name }}</h3> <ul> <li v-for="city in province.cities" :key="city.id"> {{ city.name }} </li> </ul> </div>
-
计算属性 computed
用于处理复杂的响应式计算逻辑,基于依赖缓存,只有依赖的响应式数据变化时才会重新计算,否则直接返回缓存结果。
- 定义:通过 computed 对象定义的派生属性,依赖其他响应式数据生成新值
- 特性:
- 缓存机制:仅当依赖的响应式数据变化时才重新计算,重复访问直接返回缓存结果(性能优化核心)
- 默认只读:默认只有 getter 方法,如需修改需显式定义 setter
- 依赖追踪:自动追踪所有依赖的响应式数据,无需手动配置
- 依赖驱动 :只依赖声明的数据源(如
data中的属性),当依赖变化时自动重新计算; - 无副作用:只做 "计算"(纯逻辑),不应该有异步请求、修改 DOM 等操作。
javascript
// Vue 3 选项式API示例
createApp({
data() {
return {
firstName: '张',
lastName: '三'
}
},
computed: {
// 计算属性的getter
fullName() {
return this.firstName + this.lastName
}
}
})
html
<!-- 模板中直接使用 -->
<p>全名:{{ fullName }}</p>
- 当
firstName或lastName变化时,fullName会自动更新; - 与
methods的核心区别:computed有依赖缓存,多次调用仅执行一次;methods每次调用都会重新执行。即连续多次访问fullName(如在模板中多次使用{``{ fullName }}),只要依赖没变,只会计算一次(控制台只打印一次)。
侦听器 watch
用于监听响应式数据的变化,当数据变化时执行异步操作、复杂逻辑或副作用代码。
-
定义:通过
watch选项定义,用于监听数据变化 -
特点:
- 目标明确 :只监听指定的数据(可以是
data、computed甚至嵌套属性); - 副作用优先:适合执行 "非计算" 操作(如异步请求、打印日志、修改其他数据等);
- 无缓存:每次监听的数据变化时,都会执行回调函数(哪怕变化后的值和之前一样)。
- 目标明确 :只监听指定的数据(可以是
javascript
new Vue({
data() {
return {
userId: 1,
userInfo: null
}
},
watch: {
// 监听userId的变化
userId(newVal, oldVal) {
console.log(`userId从${oldVal}变成了${newVal}`);
// 副作用操作:发请求(异步)
fetch(`/api/user/${newVal}`)
.then(res => res.json())
.then(data => this.userInfo = data);
}
}
})
- 当
userId从 1 变成 2 时,会自动执行回调,打印日志并请求新用户的数据; - 即使
userId被重复赋值为 1(比如this.userId = 1),只要发生了 "赋值" 操作,回调依然会执行(无缓存)。 - 与
computed的适用场景:computed用于数据派生、有返回值的同步计算;watch用于数据变化触发的副作用、异步操作,无强制返回值要求。
计算属性 vs 侦听器对比:
| 维度 | 计算属性(computed) |
侦听器(watch) |
|---|---|---|
| 核心作用 | 派生新数据(如拼接、过滤、计算) | 执行副作用(如异步请求、日志、DOM 操作) |
| 依赖处理 | 自动追踪所有依赖(无需手动指定) | 需手动指定监听的目标数据 |
| 缓存 | 有(依赖不变则不重新计算) | 无(数据变化就执行) |
| 返回值 | 必须有返回值(用于模板渲染或其他逻辑) | 无需返回值(专注于过程) |
| 使用场景 | 当你需要 "从现有数据算出一个新数据"(如fullName、filteredList),用computed; |
当你需要 "数据变了之后做一件事"(如发请求、存本地存储),用watch。 |
1.4 事件处理
Vue 中的事件处理,本质是让页面元素(如按钮、输入框)在发生特定动作(点击、输入、滚动等)时,能精准触发 Vue 实例中的逻辑(方法),实现 "用户操作→程序响应" 的交互闭环。它就像给元素装了个 "智能感应器":当用户做了预设动作(比如点按钮),感应器就会 "通知" 对应的函数执行操作(比如修改数据、发请求)。
基础事件绑定 (v-on指令)
框架通过 v-on 指令统一管理 DOM 事件,支持事件绑定、修饰符、参数传递等功能。
核心语法 :v-on:事件名="处理函数",简写为 @事件名="处理函数"。
html
<!-- 完整写法 -->
<button v-on:click="handleClick">点我</button>
<!-- 缩写(推荐) -->
<button @click="handleClick">点我</button>
绑定方式:
-
直接绑定
methods方法(推荐) -
内联事件表达式(适用于简单逻辑
-
示例:
javascriptnew Vue({ methods: { handleClick() { console.log('按钮被点击了!'); // 可以在这里修改数据(响应式更新) this.message = '按钮被点了~'; } } })
传递参数
如果需要在事件触发时传递额外数据(比如列表项的 ID),可以直接在方法后加参数:
html
<!-- 传递静态值 -->
<button @click="sayHello('小明')">问候小明</button>
<!-- 传递动态数据(来自data) -->
<ul>
<li v-for="item in list" :key="item.id">
<button @click="deleteItem(item.id)">删除</button>
{{ item.name }}
</li>
</ul>
方法接收参数:
javascript
methods: {
sayHello(name) {
console.log(`你好,${name}!`); // 点击后打印"你好,小明!"
},
deleteItem(id) {
console.log(`要删除的ID是:${id}`);
// 实际逻辑:从list中删除对应项
this.list = this.list.filter(item => item.id !== id);
}
}
新手坑 :如果需要同时传递参数和事件对象(event),要手动传入$event(Vue 提供的特殊变量,代表原生事件对象):
html
<!-- 正确:同时传参数和事件对象 -->
<button @click="handleClick('参数', $event)">点击</button>
javascript
handleClick(arg, e) {
console.log(arg); // 打印"参数"
console.log(e.target); // 打印触发事件的按钮元素(原生event属性)
}
事件修饰符
有时需要对事件做额外处理(比如阻止冒泡、阻止默认行为),Vue 提供了事件修饰符 (以.开头),直接在事件后添加即可,无需在方法里写e.preventDefault()或e.stopPropagation()。
| 修饰符 | 功能说明 | 示例 |
|---|---|---|
| .stop | 阻止事件冒泡 | @click.stop="handleClick" |
| .prevent | 阻止默认行为(如表单提交、链接跳转) | @submit.prevent="handleSubmit" |
| .self | 仅元素自身触发时执行(排除冒泡 / 捕获事件) | @click.self="handleClick" |
| .once | 事件仅触发一次 | @click.once="handleClick" |
| .passive | 优化滚动性能(不阻止默认滚动) | @scroll.passive="handleScroll" |
| .native | 监听自定义组件的原生事件 | <MyBtn @click.native="handleClick"> |
组合使用示例:
html
<!-- 同时阻止冒泡和默认行为 -->
<a @click.stop.prevent="handleCustomClick" href="/detail">自定义链接</a>
按键修饰符
对于键盘事件(keyup、keydown),可以用按键修饰符指定 "只有按特定键时才触发",比如只响应回车键、ESC 键。
| 修饰符 | 对应按键 |
|---|---|
| .enter | 回车键 |
| .esc | ESC 键 |
| .tab | Tab 键 |
| .space | 空格键 |
| .up/.down/.left/.right | 方向键 |
示例:
html
<!-- 输入框按回车触发搜索 -->
<input @keyup.enter="handleSearch" v-model="searchQuery">
<!-- 按ESC关闭弹窗 -->
<div @keyup.esc="closeModal" class="modal"></div>
1.5 组件基础
组件概念
组件是可复用的 Vue 实例,将页面的独立模块(按钮、弹窗、表单、页面)封装为独立单元,每个组件包含自己的模板、逻辑、样式,实现代码复用与解耦。
一个完整的 Vue 应用,本质是一棵由嵌套组件构成的组件树。就像 "拼乐高"------ 把复杂的网页拆成一个个独立的 "小模块"(组件),每个模块有自己的结构、样式和功能,就像乐高的 "轮子""车身""车窗",可以单独设计、重复使用,最后拼出完整的 "汽车"(网页)。
这种思路能解决两大问题:
- 大型页面代码混乱(拆成小模块,每个模块逻辑清晰);
- 重复功能反复开发(一个组件写一次,到处能用)。
什么是 "组件"?
组件是 "独立的、可复用的代码片段",包含 3 部分核心内容(类比一个 "小网页"):
- 结构(HTML):组件的外观(比如按钮的形状、文字);
- 样式(CSS):组件的样式(比如按钮的颜色、大小);
- 逻辑(JS):组件的功能(比如按钮点击后做什么)。
举个例子:一个 "商品卡片" 组件,可能包含:
- 结构:图片、标题、价格、购买按钮;
- 样式:卡片边框、阴影、文字大小;
- 逻辑:点击购买按钮弹出提示。
组件化的核心好处(为什么要用组件?)
-
复用性:一个组件可以在多个地方重复使用。比如电商网站的 "商品卡片",首页、列表页、推荐页都能用,不用写 3 次代码。
-
维护性:修改一个组件,所有使用它的地方都会同步更新。比如想把所有商品卡片的边框颜色从灰色改成蓝色,只需要改一次组件代码,不用逐个页面修改。
-
分工明确:多人开发时,可按组件分工(A 写头部组件,B 写列表组件),互不干扰。
总结
组件化的核心是 "拆分与复用":把页面拆成独立的小模块,每个模块封装自己的结构、样式和逻辑,通过 "父子通信" 协同工作。就像工厂生产手机:屏幕、电池、摄像头都是标准化组件,单独生产、测试,最后组装成整机。这种模式让开发更高效、维护更简单,尤其适合大型项目。入门时,先学会定义简单组件、复用组件、处理父子通信,再逐步深入复杂组件和状态管理(如 Vuex)。
Vue 中的组件基础(以 Vue 为例,最常用)
Vue 是组件化的典型代表,它的组件系统让 "拆模块" 变得简单。下面用具体例子说明。
1. 定义一个简单组件(以 Vue 3 为例)
一个组件通常是一个.vue文件(单文件组件),包含 3 个部分:<template>(结构)、<script>(逻辑)、<style>(样式)。
比如定义一个Button.vue组件(自定义按钮):
html
<!-- Button.vue -->
<template>
<!-- 结构:按钮的HTML -->
<button class="my-btn" @click="handleClick">
{{ text }} <!-- 显示传入的文字 -->
</button>
</template>
<script>
// 逻辑:组件的数据和方法
export default {
// props:父组件传给子组件的数据(类似"参数")
props: {
text: {
type: String, // 类型是字符串
default: "按钮" // 默认文字
}
},
methods: {
handleClick() {
// 子组件触发事件,通知父组件"被点击了"
this.$emit("btn-click");
}
}
};
</script>
<style scoped>
/* 样式:只作用于当前组件(scoped确保不污染其他组件) */
.my-btn {
padding: 8px 16px;
background: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
2. 使用组件(复用组件)
定义好的组件需要 "引入" 到其他组件(或页面)中才能使用,就像把乐高零件拼到主体上。
比如在App.vue(根组件)中使用Button组件:
html
<!-- App.vue -->
<template>
<div>
<!-- 使用Button组件,传入text属性,监听btn-click事件 -->
<Button text="登录" @btn-click="login" />
<Button text="注册" @btn-click="register" />
<!-- 不传入text,使用默认值"按钮" -->
<Button @btn-click="other" />
</div>
</template>
<script>
// 1. 引入Button组件
import Button from "./Button.vue";
export default {
// 2. 注册组件(声明要使用的组件)
components: {
Button // 键和值相同,可简写
},
methods: {
login() {
alert("登录按钮被点击");
},
register() {
alert("注册按钮被点击");
},
other() {
alert("默认按钮被点击");
}
}
};
</script>
效果 :页面会显示 3 个按钮,点击分别触发不同的提示 ------ 这就是组件的 "复用性":写一次Button.vue,能在多个地方用,且各自的文字和点击逻辑可定制。
3. 组件的 "父子关系" 和通信
组件之间通常有层级关系(比如App是父组件,Button是子组件),就像 "大盒子套小盒子"。父子组件需要传递数据或事件,这就是 "组件通信"。
-
父传子(父给子数据) :通过
props(就像给子组件传 "参数")。比如上面的Button组件通过props: { text }接收父组件传入的text属性。 -
子传父(子通知父发生了什么) :通过
$emit触发事件(就像子组件给父组件 "发消息")。比如Button组件用this.$emit("btn-click")告诉父组件 "我被点击了",父组件通过@btn-click监听并处理。
组件注册
组件分为全局注册和局部注册,区别在于作用域不同。引入组件后,必须在components中注册,否则会报错 "组件未定义"。
-
全局注册:组件在整个项目中都能直接使用(不用每次引入)。适合高频使用的组件(如按钮、输入框):
javascript// Vue 3 全局注册 const app = createApp(App) app.component('MyButton', { template: `<button class="my-btn">全局按钮</button>` }) app.mount('#app') // Vue 2 全局注册 Vue.component('MyButton', { template: `<button class="my-btn">全局按钮</button>` }) -
局部注册:组件只在引入它的父组件中可用。优点:避免不必要的资源加载(不用的组件不加载)。
javascript// 单文件组件中局部注册 import MyButton from './MyButton.vue' export default { components: { MyButton // 注册后,模板中可使用 <MyButton /> } }
组件的 data 选项
组件的 data 必须是一个函数,且返回一个对象。
javascript
export default {
data() {
return {
count: 0,
btnText: '点击计数'
}
}
}
核心原因:每个组件实例需要独立的响应式数据对象。如果
data是一个普通对象,所有组件实例会共享同一个对象,导致一个实例修改数据,其他实例全部受影响;使用函数返回对象,每个实例都会创建独立的对象,实现数据隔离。
第二部分:核心概念深入
2.1 组件化开发进阶
Props 详解
props 支持完整的类型校验、默认值、必填校验、自定义验证,提升组件的健壮性。
javascript
export default {
props: {
// 基础类型校验
title: String,
// 多种类型支持
count: [Number, String],
// 必填项校验
userId: {
type: Number,
required: true
},
// 带默认值
status: {
type: String,
default: 'normal'
},
// 对象/数组的默认值必须用函数返回
userInfo: {
type: Object,
default: () => {
return { name: '未知', age: 0 }
}
},
list: {
type: Array,
default: () => []
},
// 自定义验证函数
type: {
type: String,
validator: (value) => {
// 必须是以下值中的一个
return ['primary', 'warning', 'danger'].includes(value)
}
}
}
}
单向数据流
Vue 严格遵循单向数据流原则:
- 父组件的 props 更新会向下流动到子组件,子组件会自动更新
- 子组件绝对不能直接修改 props,否则会触发控制台警告
- 若子组件需要修改 props 传递的值,有两种合法方案:
- 将 props 赋值给本地 data ,作为初始值,后续修改本地 data
- 通过计算属性,基于 props 派生新值
自定义事件 $emit:子传父
子组件向父组件传递数据,通过 $emit 触发自定义事件,父组件通过 v-on/@ 监听事件并接收数据。
-
子组件触发事件
html<!-- Child.vue --> <template> <button @click="handleSend">向父组件传值</button> </template> <script> export default { data() { return { childMsg: '来自子组件的消息' } }, methods: { handleSend() { // 触发自定义事件,第一个参数是事件名,后续参数是传递的数据 this.$emit('send-message', this.childMsg, 200) } } } </script> -
父组件监听事件
html<!-- Parent.vue --> <template> <Child @send-message="handleReceive" /> </template> <script> import Child from './Child.vue' export default { components: { Child }, methods: { handleReceive(msg, code) { console.log(msg, code) // 输出:来自子组件的消息 200 } } } </script>
插槽 slot:内容分发
插槽用于实现组件的内容分发,父组件可以向子组件的模板中传递 HTML 内容,子组件通过 <slot> 标签作为内容的占位符。
-
默认插
html<!-- MyCard.vue 子组件 --> <template> <div class="card"> <div class="card-header">卡片标题</div> <!-- 插槽占位符,父组件传递的内容会渲染在这里 --> <slot></slot> <div class="card-footer">卡片底部</div> </div> </template>html<!-- 父组件使用 --> <MyCard> <p>这是卡片的主体内容</p> <p>可以是任意HTML结构</p> </MyCard> -
具名插槽 当组件需要多个插槽时,通过
name属性给插槽命名,父组件通过v-slot:name对应传递内容。html<!-- MyLayout.vue 子组件 --> <template> <div class="layout"> <header><slot name="header"></slot></header> <main><slot></slot></main> <!-- 不带name的是默认插槽,name为default --> <footer><slot name="footer"></slot></footer> </div> </template>html<!-- 父组件使用,缩写 # 代替 v-slot: --> <MyLayout> <template #header> <h1>页面标题</h1> </template> <template #default> <p>页面主体内容</p> </template> <template #footer> <p>页面版权信息</p> </template> </MyLayout> -
作用域插槽让父组件的插槽内容可以访问子组件内部的数据,实现子组件向父组件插槽传递数据。
html<!-- MyList.vue 子组件 --> <template> <ul> <li v-for="item in list" :key="item.id"> <!-- 向插槽传递item数据 --> <slot :item="item" :index="item.id"></slot> </li> </ul> </template> <script> export default { data() { return { list: [ { id: 1, name: 'Vue' }, { id: 2, name: 'React' }, { id: 3, name: 'Angular' } ] } } } </script>html<!-- 父组件使用,接收子组件传递的数据 --> <MyList> <template #default="{ item, index }"> <p>{{ index }} - {{ item.name }}</p> </template> </MyList>
动态组件与 keep-alive
动态组件用于在多个组件之间动态切换,通过 <component> 标签的 :is 属性指定要渲染的组件。
html
<template>
<div>
<button @click="currentTab = 'Home'">首页</button>
<button @click="currentTab = 'About'">关于</button>
<!-- 动态渲染组件 -->
<component :is="currentTab"></component>
</div>
</template>
<script>
import Home from './Home.vue'
import About from './About.vue'
export default {
components: { Home, About },
data() {
return {
currentTab: 'Home'
}
}
}
</script>
默认情况下,动态组件切换时,被隐藏的组件会被销毁,再次切换会重新创建,导致状态丢失、性能损耗。此时可以用 <keep-alive> 包裹动态组件,缓存不活动的组件实例,避免重复销毁和创建。
html
<!-- 缓存动态组件 -->
<keep-alive>
<component :is="currentTab"></component>
</keep-alive>
<keep-alive> 支持三个核心属性:
include:字符串 / 正则 / 数组,只有名称匹配的组件会被缓存exclude:字符串 / 正则 / 数组,名称匹配的组件不会被缓存max:数字,最多缓存的组件实例数量,超出会采用 LRU 策略销毁最久未使用的实例
2.2 深入响应式原理
响应式是 Vue 的核心特性,实现了数据与视图的自动同步,Vue 2 与 Vue 3 采用了完全不同的底层实现方案。
Vue 2:Object.defineProperty 数据劫持
Vue 2 的响应式核心是 Object.defineProperty(),通过劫持对象属性的 getter 和 setter 方法,在属性被访问时收集依赖,属性被修改时派发更新,通知视图更新。
核心实现逻辑:
- 遍历
data中的所有属性,通过Object.defineProperty()为每个属性添加getter和setter - 当组件渲染时,会访问属性触发
getter,将当前组件的渲染 Watcher 收集为依赖 - 当属性被修改时,触发
setter,通知所有收集的 Watcher,触发组件重新渲染
核心缺陷:
- 无法监听对象新增 / 删除的属性,只能劫持初始化时已存在的属性
- 无法监听数组通过下标修改元素 、修改 length的操作
- 无法原生支持 Map、Set、WeakMap、WeakSet 等数据结构
为解决这些问题,Vue 2 提供了 Vue.set / this.$set 方法,用于给对象新增响应式属性、修改数组元素。
javascript
运行
// 给对象新增属性
this.$set(this.userInfo, 'age', 18)
// 按下标修改数组元素
this.$set(this.list, 0, '新内容')
Vue 3:Proxy 代理
Vue 3 重构了响应式系统,使用 ES6 的 Proxy 代理整个对象,而非单个属性,彻底解决了 Vue 2 响应式的缺陷。
核心优势:
- 可以监听对象新增 / 删除的属性,无需提前遍历
- 可以监听数组的下标修改 、length 修改,无需重写数组方法
- 原生支持 Map、Set、WeakMap、WeakSet 等数据结构
- 性能更优,初始化时无需遍历所有属性,懒执行依赖收集
- 支持嵌套对象的深层代理,可按需递归
依赖收集与派发更新
完整的响应式流程分为两个核心阶段:
- 依赖收集(track):当响应式数据被访问时,记录哪些组件 / 函数依赖了这个数据
- 派发更新(trigger):当响应式数据被修改时,通知所有依赖该数据的组件 / 函数执行更新
整个流程的核心角色:
- 响应式数据:被 Proxy/Object.defineProperty 劫持的对象 / 属性
- 副作用函数(Effect):组件的渲染函数、watch、computed 等,依赖响应式数据的函数
- 依赖集合(Dep):管理每个响应式数据对应的所有副作用函数
响应式数据的注意事项
-
Vue 2 注意事项
- 新增对象属性必须使用
$set,删除属性使用$delete - 数组修改优先使用
push/pop/shift/unshift/splice/sort/reverse7 个变异方法,下标修改使用$set - 初始化时必须在
data中声明所有需要响应式的属性,避免后续新增属性无响应式
- 新增对象属性必须使用
-
Vue 3 注意事项
reactive仅对引用类型(对象 / 数组 / Map 等)生效,对基本类型(字符串 / 数字 / 布尔值)无效,基本类型请使用ref- 直接解构
reactive对象会丢失响应式,需使用toRefs/toRef转换后再解构 - 给
reactive对象直接赋值一个新对象,会丢失原对象的响应式代理,需使用Object.assign或修改属性值
2.3 生命周期钩子
Vue 组件从创建到销毁的整个过程,称为组件的生命周期。Vue 在生命周期的关键节点,提供了生命周期钩子函数,允许开发者在特定阶段执行自定义代码。
生命周期整体流程
组件生命周期分为 4 个核心阶段:
- 创建阶段:组件实例初始化,数据、事件初始化,未挂载到 DOM
- 挂载阶段:组件模板编译、DOM 渲染完成,挂载到页面
- 更新阶段:组件响应式数据变化,触发视图重新渲染
- 销毁阶段:组件实例销毁,清理事件监听、定时器、副作用
Vue 2 与 Vue 3 生命周期钩子对应表
表格
| Vue 2 选项式钩子 | Vue 3 选项式钩子 | Vue 3 setup 组合式钩子 | 执行时机 | 核心使用场景 |
|---|---|---|---|---|
| beforeCreate | beforeCreate | 无需对应,setup 替代 | 实例初始化之后,data/methods 等配置未初始化 | 几乎不用,Vue3 中 setup 执行时机早于该钩子 |
| created | created | 无需对应,setup 替代 | 实例创建完成,data/methods 已初始化,未挂载 DOM | 数据初始化、异步接口请求、全局事件监听注册 |
| beforeMount | beforeMount | onBeforeMount | 挂载开始前,模板编译完成,DOM 未生成 | 挂载前的最后数据修改,不会触发更新 |
| mounted | mounted | onMounted | 组件挂载完成,DOM 已渲染到页面 | DOM 操作、第三方库初始化、异步接口请求、获取 DOM 元素 |
| beforeUpdate | beforeUpdate | onBeforeUpdate | 数据变化,视图更新前触发 | 获取更新前的 DOM 状态、更新前的数据备份 |
| updated | updated | onUpdated | 数据变化,视图重新渲染完成 | 操作更新后的 DOM、更新后的状态同步 |
| beforeDestroy | beforeUnmount | onBeforeUnmount | 组件销毁前触发,实例仍完全可用 | 清除定时器、取消事件监听、取消接口请求、解绑第三方库 |
| destroyed | unmounted | onUnmounted | 组件销毁完成,实例、DOM 已完全卸载 | 销毁后的收尾操作、日志上报 |
核心说明:Vue 3 的
setup函数执行时机在beforeCreate之前,因此组合式 API 中无需beforeCreate和created钩子,初始化逻辑直接写在setup中即可。
生命周期使用示例(Vue 3 组合式 API)
html
<template>
<div id="box">{{ count }}</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const count = ref(0)
let timer = null
// 挂载完成后执行
onMounted(() => {
// 操作DOM
const box = document.getElementById('box')
console.log(box)
// 启动定时器
timer = setInterval(() => {
count.value++
}, 1000)
})
// 组件销毁前执行
onBeforeUnmount(() => {
// 清除定时器,避免内存泄漏
clearInterval(timer)
})
</script>
2.4 路由管理 (Vue Router)
Vue Router 是 Vue 官方的路由插件,核心作用是 在单页应用(SPA)中,实现 URL 与组件的精准映射,并且做到 "无刷新切换页面"。
通俗比喻:把 SPA 当成 "大型商场"
- 整个 Vue 应用 = 一座商场(只有 1 个 "大门",对应 1 个 HTML 文件,不会重新开门);
- 每个组件 = 商场里的 "店铺"(比如 "首页店""商品详情店""我的订单店");
- Vue Router = 商场的 "导航系统"(导览图 + 指路牌 + 电梯);
- URL 地址 = 店铺的 "门牌号"(比如
/home是首页店,/goods/123是 123 号商品店); - 路由切换 = 顾客在商场内走电梯 / 指路牌找店铺(不用出商场大门,直接到达目标店铺,无刷新 = 不用重新进商场)。
没有 Vue Router 的 SPA,就像商场没有导航:顾客(用户)不知道怎么找店铺,只能在 "大厅"(根组件)里打转;有了 Vue Router,用户只需输入 "门牌号"(URL)或点击 "指路牌"(导航按钮),就能直接切换到对应 "店铺"(组件)。
当前版本对应关系:
- Vue 3 → Vue Router 4
- Vue 2 → Vue Router 3
安装与基本配置
-
安装依赖
# Vue 3 安装 npm install vue-router@4 # Vue 2 安装 npm install vue-router@3 -
基础配置(Vue 3 + Vite)新建
src/router/index.js路由配置文件:javascript// 从vue-router导入核心方法 import { createRouter, createWebHistory } from 'vue-router' // 导入页面组件 import Home from '@/views/Home.vue' import About from '@/views/About.vue' // 定义路由规则 const routes = [ { path: '/', // 路由路径 name: 'Home', // 路由名称(可选) component: Home, // 对应的组件 meta: { title: '首页' } // 路由元信息(可选,存储自定义数据) }, { path: '/about', name: 'About', component: () => import('@/views/About.vue') // 路由懒加载 } ] // 创建路由实例 const router = createRouter({ // 路由模式 history: createWebHistory(import.meta.env.BASE_URL), // 路由规则 routes }) // 导出路由实例 export default router -
全局挂载路由在
src/main.js中挂载路由到 Vue 应用:javascriptimport { createApp } from 'vue' import App from './App.vue' import router from './router' // 导入路由实例 // 挂载路由 createApp(App).use(router).mount('#app') -
路由视图与导航链接在根组件
App.vue中添加路由视图和导航:html<template> <div id="app"> <!-- 导航链接,替代a标签,避免页面刷新 --> <router-link to="/">首页</router-link> <router-link to="/about">关于</router-link> <!-- 路由视图:匹配到的路由组件会渲染在这里 --> <router-view></router-view> </div> </template>
路由模式
Vue Router 提供两种核心路由模式,区别在于 URL 表现、实现原理、后端配置要求:
| 模式 | 实现原理 | URL 表现 | 兼容性 | 后端配置 |
|---|---|---|---|---|
| hash 模式 | 基于 URL 的 hash 值(#),hash 变化不会触发页面刷新,通过 hashchange 事件监听 |
http://localhost:5173/#/about |
兼容所有浏览器,包括低版本 IE | 无需后端配置,刷新不会 404 |
| history 模式 | 基于 HTML5 History API,实现 URL 无刷新跳转 | http://localhost:5173/about |
兼容现代浏览器,IE10+ | 必须配置后端,将所有路由请求 fallback 到 index.html,否则刷新页面会 404 |
核心路由能力
动态路由参数
用于匹配动态路径,比如详情页**/detail/1001,** 通过**:参数名**定义:
核心本质
URL 中携带动态参数(如商品 ID、用户 ID),让同一个组件适配不同 "参数场景"(无需为每个场景创建单独组件)。
通俗比喻
商场里的 "商品详情店":所有商品共用一个 "详情店铺",门牌号用 "商品 ID" 区分(比如 /goods/123 是 123 号商品,/goods/456 是 456 号商品),进店后根据门牌号展示对应商品信息。
javascript
// 路由配置
const routes = [
{
path: '/detail/:id',
name: 'Detail',
component: () => import('@/views/Detail.vue')
}
]
组件中获取参数:
javascript
// 选项式API
this.$route.params.id
// 组合式API
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id)
新手容易误解的点
- 坑 1:动态参数名写错(如路由配置是
:goodsId,组件内写$route.params.id),导致参数获取为undefined; - 坑 2:动态路由的参数在刷新页面后仍存在(hash/history 模式均支持),但如果用
params传递非动态参数(如push({ name: 'goods', params: { name: '手机' } })),history 模式下刷新会丢失; - 坑 3:子路由的动态参数路径带
/,比如父路由path: '/user',子路由写path: '/:id',会导致 URL 变成/id(而非/user/id),正确写法是子路由path: ':id'(不带/)
嵌套路由
用于实现页面的嵌套布局,比如后台管理系统的侧边栏 + 主体内容,通过 children 属性配置子路由:
核心本质
路由的层级结构对应组件的嵌套结构,实现 "父页面包含子页面" 的效果(如 "我的订单" 页面包含 "待付款""已完成" 子页面)。
通俗比喻
商场里的 "我的订单店"(父店铺),里面分 "待付款分区""已完成分区"(子店铺):用户进入 "我的订单店" 后,可在店内切换不同分区,无需离开父店铺(URL 也会变成 parent/child 层级形式)。
javascript
const routes = [
{
path: '/admin',
component: () => import('@/views/AdminLayout.vue'),
children: [
// 子路由path不以/开头,会拼接父路由path,最终路径 /admin/dashboard
{
path: 'dashboard',
component: () => import('@/views/admin/Dashboard.vue')
},
{
path: 'user',
component: () => import('@/views/admin/User.vue')
},
// 空路径,匹配父路由 /admin 时默认渲染
{
path: '',
redirect: '/admin/dashboard'
}
]
}
]
父组件 AdminLayout.vue 中需要添加 <router-view> 作为子路由的渲染出口。
编程式导航 除了 <router-link> 声明式导航,Vue Router 提供了编程式导航 API,用于在 JS 代码中控制页面跳转:
javascript
// 选项式API
// 跳转到指定路径,新增历史记录
this.$router.push('/about')
this.$router.push({ path: '/detail', query: { id: 1001 } })
this.$router.push({ name: 'Detail', params: { id: 1001 } })
// 替换当前历史记录,不新增,点击后退不会回到该页面
this.$router.replace('/login')
// 前进/后退历史记录
this.$router.go(-1) // 后退一页
this.$router.go(1) // 前进一页
// 组合式API
import { useRouter } from 'vue-router'
const router = useRouter()
router.push('/about')
新手容易误解的点
- 坑 1:父组件忘记写
<router-view>,导致子组件无法渲染(最常见); - 坑 2:子路由
path带/,变成绝对路径(如子路由path: '/unpaid',URL 直接是/unpaid,而非/order/unpaid); - 坑 3:嵌套路由的导航路径必须写完整路径(如
to="/order/unpaid"),不能只写to="unpaid"(除非用相对路径,但新手不推荐)。
编程式导航
核心本质
通过 JavaScript 代码控制路由跳转(替代 <router-link> 标签),适用于 "逻辑触发后跳转" 场景(如登录成功、表单提交后)。
通俗比喻
顾客在商场里,不是通过 "指路牌"(router-link)找店铺,而是通过 "导购员"(代码)直接带到目标店铺(如登录成功后,导购员直接把顾客带到首页)。
核心方法($router 实例的 3 个核心方法)
| 方法 | 作用 | 类比场景 |
|---|---|---|
push(path/obj) |
跳转到目标路由,添加历史记录 | 正常走路到店铺,可后退 |
replace(path/obj) |
跳转到目标路由,替换历史记录 | 坐电梯到店铺,不可后退 |
go(n) |
前进 / 后退 n 步(n 为数字) | 按浏览器前进 / 后退按钮 |
代码示例
html
<template>
<button @click="handleLogin">登录</button>
<button @click="goBack">返回上一页</button>
</template>
<script>
export default {
methods: {
handleLogin() {
// 模拟登录成功
const isLogin = true;
if (isLogin) {
// 1. 跳转到首页(简单路径)
this.$router.push('/home');
// 2. 跳转到动态路由(带参数)
this.$router.push({ path: '/goods', params: { id: 123 } });
// 3. 跳转到带查询参数的路由(?name=xxx)
this.$router.push({ path: '/search', query: { keyword: '手机' } });
// 4. 替换历史记录(登录后不能后退到登录页)
this.$router.replace('/home');
}
},
goBack() {
// 后退 1 步(相当于浏览器后退)
this.$router.go(-1);
// 前进 1 步:this.$router.go(1)
}
}
}
</script>
新手容易误解的点
- 坑 1:混淆
$route和$router:用this.$route.push(...)报错(正确是$router,$route是当前路由信息对象,没有导航方法); - 坑 2:
push传递params时用path字段(正确:用name或直接拼接路径,如push({ name: 'goods', params: { id: 123 } }),或push('/goods/123')); - 坑 3:
go(-1)在没有历史记录时会报错(可先判断history.length,如if (window.history.length > 1) this.$router.go(-1))。
路由守卫
路由守卫用于拦截导航过程,实现权限控制、登录拦截、页面标题修改、跳转确认等功能,分为三类:全局守卫、路由独享守卫、组件内守卫。
通俗比喻
顾客进入 "VIP 店铺"(需要权限的路由)前,安检员(路由守卫)检查是否有 VIP 卡(登录状态):有则放行,无则引导到办卡处(登录页);或进入店铺后,通知店员准备服务(路由进入后执行逻辑)。
-
全局守卫对所有路由生效,在路由实例上配置:
javascript// 全局前置守卫:每次导航跳转前执行,最常用,用于登录拦截 router.beforeEach((to, from, next) => { // to:即将进入的目标路由对象 // from:当前导航正要离开的路由对象 // next:放行函数,必须调用一次才能完成导航 // 登录拦截逻辑 const token = localStorage.getItem('token') // 目标路由需要登录权限 if (to.meta.requiresAuth && !token) { // 跳转到登录页 next('/login') } else { // 放行 next() } }) // 全局后置钩子:导航完成后执行,无next函数,不改变导航 router.afterEach((to, from) => { // 修改页面标题 document.title = to.meta.title || 'Vue应用' }) -
路由独享守卫仅对单个路由生效,写在路由配置中:
javascriptconst routes = [ { path: '/admin', component: () => import('@/views/Admin.vue'), beforeEnter: (to, from, next) => { // 管理员权限校验 const isAdmin = localStorage.getItem('isAdmin') if (!isAdmin) { next('/403') } else { next() } } } ] -
组件内守卫在组件内部定义,仅对当前组件生效:
javascript// 选项式API export default { // 进入组件前执行,此时组件实例还未创建,无法访问this beforeRouteEnter(to, from, next) { next(vm => { // 回调中可访问组件实例vm }) }, // 路由参数变化,组件复用时执行(比如 /detail/1001 → /detail/1002) beforeRouteUpdate(to, from, next) { // 可访问this,重新获取数据 this.fetchData(to.params.id) next() }, // 离开组件前执行,用于阻止未保存的表单页面离开 beforeRouteLeave(to, from, next) { if (this.isFormChanged) { const isConfirm = window.confirm('内容未保存,确定要离开吗?') next(isConfirm) } else { next() } } }
新手容易误解的点
- 坑 1:忘记调用
next(),导致路由切换卡住(页面无响应); - 坑 2:
next()调用多次(如if (a) next(); next();),导致报错; - 坑 3:
beforeRouteEnter中直接用this(此时组件未实例化,this为undefined,需用next(vm => { ... })); - 坑 4:全局守卫中判断路径时用
to.path === '/order'(如果路由有子路由,如/order/unpaid,会判断失败,推荐用to.path.startsWith('/order'))。
路由参数传递
核心本质
通过路由 URL 传递数据,实现 "无父子关系" 的组件通信(常用两种方式:params 和 query)。
通俗比喻
params:相当于 "店铺门牌号的一部分"(如/goods/123中的 123),隐藏在路径中,更优雅;query:相当于 "店铺门口贴的通知"(如/search?keyword=手机),显式拼接在 URL 后,支持多参数。
两种参数对比与代码示例
| 特性 | params(动态参数) |
query(查询参数) |
|---|---|---|
| URL 表现 | /goods/123 |
/search?keyword=手机&price=5000 |
| 配置方式 | 路由路径需定义 :paramName |
无需配置路由,直接拼接 |
| 获取方式 | $route.params.paramName |
$route.query.queryName |
| 刷新后是否丢失 | 不丢失(动态路由)/ 丢失(非动态) | 不丢失(URL 中可见) |
| 适用场景 | 唯一标识(商品 ID、用户 ID) | 搜索条件、筛选参数(可分享 URL) |
代码示例
javascript
// 1. 传递 params(动态路由)
this.$router.push('/goods/123'); // 直接拼接
this.$router.push({ name: 'goods', params: { id: 123 } }); // 命名路由方式(推荐)
// 2. 传递 query
this.$router.push({ path: '/search', query: { keyword: '手机', price: 5000 } });
// 3. 组件内获取
console.log(this.$route.params.id); // 123
console.log(this.$route.query.keyword); // 手机
console.log(this.$route.query.price); // 5000
新手容易误解的点
- 坑 1:用
path传递params(如push({ path: '/goods', params: { id: 123 } })),params会丢失,正确用name或直接拼接路径; - 坑 2:
query参数是字符串类型(如price=5000实际是'5000'),需要手动转成数字类型; - 坑 3:
params传递非动态参数(如push({ name: 'goods', params: { name: '手机' } })),history 模式下刷新会丢失,需用query或本地存储替代。
重定向与别名
核心本质
- 重定向(
redirect):访问 A 路由时,自动跳转到 B 路由(如访问/跳转到/home); - 别名(
alias):给路由起 "小名",访问小名和原名都能指向同一个组件(如/main是/home的别名,访问/main也能看到首页)。
通俗比喻
- 重定向:商场 "大门"(
/)直接通向 "首页店"(/home),顾客不用手动找; - 别名:"首页店" 的门牌号除了
/home,还有/main,两个门牌号都能找到同一店铺。
代码示例
javascript
// src/router/index.js
const routes = [
// 1. 重定向:访问 / 跳转到 /home
{ path: '/', redirect: '/home' },
// 重定向到动态路由(如访问 /product/123 跳转到 /goods/123)
{ path: '/product/:id', redirect: '/goods/:id' },
// 2. 别名:/main 是 /home 的别名,访问 /home 或 /main 都渲染 Home 组件
{ path: '/home', component: () => import('@/views/Home.vue'), alias: '/main' }
]
新手容易误解的点
-
坑 1:重定向用
component替代redirect(如{ path: '/', component: '/home' }),导致报错,正确用redirect; -
坑 2:别名
alias写绝对路径时,要和父路由层级匹配(如嵌套路由path: 'unpaid',别名不能写/unpaid,要写'unpaid-alias'); -
坑 3:重定向和别名混淆:重定向会改变 URL(如访问
/会变成/home),别名不会改变 URL(访问/main仍显示/main)。
路由懒加载
核心本质
默认情况下,所有路由组件会在项目启动时一次性加载(首屏加载慢);路由懒加载让组件 "按需加载"------ 只有访问该路由时,才下载组件代码,减少首屏加载时间。
通俗比喻
商场里的 "冷门店铺"(如 "售后服务店"),平时不打开大门(不加载代码),只有顾客要去时才开门(加载代码),节省商场的电力(项目性能)。
代码示例
javascript
// src/router/index.js
const routes = [
// 1. 箭头函数 + import()(推荐,简洁)
{ path: '/home', component: () => import('@/views/Home.vue') },
// 2. 定义变量(可命名,便于维护)
const GoodsDetail = () => import('@/views/GoodsDetail.vue');
{ path: '/goods/:id', component: GoodsDetail }
]
新手容易误解的点
- 坑 1:把懒加载和异步组件搞混(路由懒加载是异步组件的一种特殊场景,专门用于路由);
- 坑 2:懒加载组件时忘记加
()(如component: import('@/views/Home.vue')),导致报错,正确是() => import(...); - 坑 3:认为懒加载会让页面切换变慢(实际首屏加载更快,切换时的加载时间可通过骨架屏优化,整体体验更好)。
2.5 状态管理 (Vuex/Pinia)
状态管理用于管理 Vue 应用中的全局共享状态,解决跨组件、跨层级的状态共享问题,比如用户登录信息、购物车数据、全局配置等,实现状态的统一管理、可追踪、可预测。
为什么需要状态管理
- 多层嵌套组件的传参极其繁琐,事件冒泡难以维护
- 兄弟组件、无关联组件之间无法便捷共享状态
- 多个组件依赖同一个状态,修改逻辑分散,难以维护
- 状态变更无追踪,出现 bug 难以定位原因
Vuex 核心概念
Vuex 是 Vue 官方的传统状态管理库,专为 Vue 设计,遵循单向数据流、状态唯一修改途径的设计原则。
当前版本对应:
- Vue 3 → Vuex 4
- Vue 2 → Vuex 3
Vuex 有 5 个核心概念:
把 Vuex 比作大型超市的仓储管理体系,5 个核心概念对应超市的不同岗位 / 区域:
| 概念 | 超市类比 | 核心本质 | 关键规则 |
|---|---|---|---|
| State | 超市总货架 | 全局唯一的原始数据仓库 | 只读,不能直接改 |
| Getter | 超市导购员 | State 数据的「加工 / 过滤器」 | 只读取、不修改数据 |
| Mutation | 货架理货员 | 修改 State 的唯一入口 | 必须是同步函数 |
| Action | 超市采购员 | 处理异步操作(如进货 / 网络请求) | 不能直接改 State,只能调用 Mutation |
| Module | 超市分区(零食区 / 生鲜区) | 大型项目的状态拆分 | 每个模块独立管理自己的数据 |
State(总货架):
单一状态树,应用的唯一数据源,所有全局状态都存储在 State 中,类似组件的 data。
- 作用 :存储整个应用的全局原始数据,是一个对象。
- 本质 :Vuex 的唯一数据源,所有共享数据都存在这里。
- 新手误区 :直接在组件里修改
this.$store.state.xxx❌ 禁止直接修改!必须通过 Mutation。
javascript
// store/index.js
import { createStore } from 'vuex'
export default createStore({
state: {
userInfo: null,
token: '',
cartCount: 0
}
})
组件中访问:
javascript
// 选项式API
this.$store.state.userInfo
// 组合式API
import { useStore } from 'vuex'
const store = useStore()
console.log(store.state.userInfo)
Getters(导购员):
派生状态,基于 State 计算得到,类似组件的 computed,有依赖缓存,只有依赖的 State 变化时才会重新计算。
- 作用 :对 State 数据做加工、过滤、计算 ,类似 Vue 组件的
computed计算属性。 - 本质:State 的「派生数据」,复用数据处理逻辑,不用每个组件都写一遍。
javascript
createStore({
state: {
goodsList: [
{ id: 1, name: '商品1', price: 100, checked: true },
{ id: 2, name: '商品2', price: 200, checked: false }
]
},
getters: {
// 计算选中商品的总价
checkedTotalPrice(state) {
return state.goodsList
.filter(item => item.checked)
.reduce((sum, item) => sum + item.price, 0)
}
}
})
组件中访问:
javascript
this.$store.getters.checkedTotalPrice
Mutations(理货员):
唯一修改 State 的合法途径 ,必须是同步函数,通过 commit 触发。
- 作用 :唯一能修改 State 的地方,修改逻辑写在这里。
- 本质:同步修改 State 的工具。
- 铁律 :必须是同步函数!(理货员只能当场理货,不能等半天)
- 新手误区:把异步请求(axios)写在 Mutation 里 ❌ 绝对不行!
javascript
createStore({
state: {
cartCount: 0
},
mutations: {
// 第一个参数是state,后续参数是传递的载荷payload
setCartCount(state, count) {
state.cartCount = count
},
incrementCartCount(state, step = 1) {
state.cartCount += step
}
}
})
组件中触发:
javascript
// 提交mutation
this.$store.commit('setCartCount', 10)
this.$store.commit('incrementCartCount', 2)
Actions(采购员):
用于处理异步操作,不能直接修改 State,必须通过 commit 提交 Mutation 修改 State,通过 dispatch 触发。
- 作用 :处理异步操作(如网络请求、定时器),拿到结果后,调用 Mutation 修改 State。
- 本质:异步操作的中转站,自己不碰货架,只指挥理货员。
- 铁律 :不能直接修改 State,必须通过
commit触发 Mutation。
javascript
createStore({
state: {
userInfo: null
},
mutations: {
setUserInfo(state, userInfo) {
state.userInfo = userInfo
}
},
actions: {
// 第一个参数是context对象,包含commit、dispatch、state、getters
// 后续参数是载荷payload
async fetchUserInfo(context, userId) {
// 异步接口请求
const res = await getUserInfoApi(userId)
// 提交mutation修改state
context.commit('setUserInfo', res.data)
}
}
})
组件中触发:
javascript
this.$store.dispatch('fetchUserInfo', 1001)
Modules(分区货架):
模块化,将庞大的 Store 拆分为多个独立的模块,每个模块拥有自己的 State、Getters、Mutations、Actions,甚至嵌套子模块,解决单状态树的臃肿问题。
- 作用:大型项目中,把 Vuex 拆分成多个小模块,避免所有数据挤在一起。
- 本质:模块化拆分,让代码更易维护。
- 场景:电商项目 → 用户模块、商品模块、订单模块,互不干扰。
javascript
// 用户模块
const userModule = {
namespaced: true, // 开启命名空间,避免命名冲突
state: { userInfo: null, token: '' },
mutations: {
setToken(state, token) {
state.token = token
}
},
actions: {
async login(context, loginForm) {
// 登录逻辑
}
}
}
// 购物车模块
const cartModule = {
namespaced: true,
state: { cartList: [] },
mutations: {},
actions: {}
}
// 根store注册模块
export default createStore({
modules: {
user: userModule,
cart: cartModule
}
})
命名空间模块的访问与触发:
javascript
// 访问state
this.$store.state.user.token
// 提交mutation
this.$store.commit('user/setToken', 'xxx')
// 触发action
this.$store.dispatch('user/login', loginForm)
Vuex 完整使用流程(代码 + 讲解)
步骤 1:安装
npm install vuex --save
步骤 2:配置 Store(储物柜核心代码)
这是标准模板,我加了注释,逐行看懂:
javascript
// 1. 引入Vue和Vuex
import Vue from 'vue';
import Vuex from 'vuex';
// 2. 给Vue安装Vuex插件
Vue.use(Vuex);
// 3. 创建Vuex仓库实例
const store = new Vuex.Store({
// 👉 State:总货架(原始数据)
state: {
count: 0 // 共享数据:数字计数器
},
// 👉 Getter:导购员(加工数据)
getters: {
// 加工:给count加个前缀
getCounter: (state) => {
return `当前计数:${state.count}`;
}
},
// 👉 Mutation:理货员(同步修改State)
mutations: {
// 定义修改方法:数字+1
increment(state) {
state.count++;
}
},
// 👉 Action:采购员(异步操作)
actions: {
// 模拟异步:延迟1秒后修改
asyncIncrement({ commit }) {
setTimeout(() => {
commit('increment'); // 调用Mutation修改数据
}, 1000);
}
}
});
// 4. 导出仓库,给Vue项目使用
export default store;
步骤 3:在 Vue 组件中使用(存取数据)
html
<template>
<div>
<!-- 直接读取State -->
<p>原始数据:{{ $store.state.count }}</p>
<!-- 读取Getter加工后的数据 -->
<p>加工数据:{{ $store.getters.getCounter }}</p>
<!-- 同步修改:触发Mutation -->
<button @click="$store.commit('increment')">同步+1</button>
<!-- 异步修改:触发Action -->
<button @click="$store.dispatch('asyncIncrement')">异步+1</button>
</div>
</template>
Pinia 简介与核心概念
Pinia 是 Vue 官方推荐的新一代状态管理库,被称为 Vuex 5,完全替代 Vuex,专为 Vue 3 设计,完美支持组合式 API 和 TypeScript。
核心优势:
- 去掉了 Mutations,仅保留 State、Getters、Actions,同步异步都可在 Actions 中处理,API 更简洁
- 原生完美支持 TypeScript,类型推导友好,无需复杂的类型声明
- 支持组合式 API,写法更灵活,和组件的组合式 API 风格统一
- 无命名空间的烦恼,每个 Store 都是独立的模块,天然隔离
- 体积更小,调试更友好,支持 Vue Devtools,状态变更可追踪
- 兼容 Vue 2 和 Vue 3
Pinia 基础使用
安装依赖
npm install pinia
全局挂载
javascript
// main.js
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
createApp(App).use(createPinia()).mount('#app')
定义 Store新建 src/stores/user.js:
javascript
import { defineStore } from 'pinia'
// 定义并导出Store,第一个参数是唯一的store id
export const useUserStore = defineStore('user', {
// 状态:类似Vuex的State,必须是函数返回对象
state: () => {
return {
userInfo: null,
token: localStorage.getItem('token') || '',
userId: 0
}
},
// 派生状态:类似Vuex的Getters,有缓存
getters: {
isLogin: (state) => !!state.token
},
// 方法:类似Vuex的Actions,同步异步都可处理,直接通过this访问state
actions: {
setToken(token) {
this.token = token
localStorage.setItem('token', token)
},
async fetchUserInfo() {
if (!this.token) return
// 异步接口请求
const res = await getUserInfoApi()
this.userInfo = res.data
this.userId = res.data.id
},
logout() {
this.token = ''
this.userInfo = null
localStorage.removeItem('token')
}
}
})
组件中使用
html
<template>
<div>
<p v-if="userStore.isLogin">欢迎你,{{ userStore.userInfo?.name }}</p>
<button @click="handleLogout" v-if="userStore.isLogin">退出登录</button>
</div>
</template>
<script setup>
// 导入定义的store
import { useUserStore } from '@/stores/user'
// 创建store实例
const userStore = useUserStore()
// 调用actions方法
const handleLogout = () => {
userStore.logout()
}
</script>
推荐用法:Pinia 优先支持组合式 API 写法,也可以使用
mapState、mapActions等辅助函数适配选项式 API,是 Vue 3 项目的首选状态管理方案。
第三部分:实战与进阶
3.1 Composition API (Vue 3 核心)
Composition API(组合式 API)是 Vue 3 引入的核心新特性,彻底改变了 Vue 组件的代码组织方式,解决了 Vue 2 选项式 API(Options API)在大型项目中逻辑分散、复用困难的问题,同时提供了完美的 TypeScript 支持。
Options API vs Composition API
| 特性 | Options API(选项式) | Composition API(组合式) |
|---|---|---|
| 代码组织 | 按选项拆分代码,data、methods、computed、watch 分块书写 | 按业务逻辑组织代码,相关的响应式数据、方法、计算属性写在一起 |
| 逻辑复用 | 依赖 mixin,存在命名冲突、来源不明、数据追踪困难的问题 | 依赖组合式函数(Composables),无命名冲突,来源清晰,类型安全 |
| TypeScript 支持 | 支持差,需要复杂的类型声明,类型推导困难 | 原生完美支持,类型推导友好,无需额外声明 |
| 适用场景 | 小型简单组件,逻辑单一 | 中大型复杂组件,多逻辑耦合,需要逻辑复用 |
| 执行上下文 | 所有选项都挂载到 this 上,this 指向固定 | 无 this,基于函数式编程,代码更易压缩,tree-shaking 友好 |
setup () 函数
setup() 是组合式 API 的入口函数,是组件中使用组合式 API 的唯一位置。
核心特性:
- 执行时机:在组件实例创建之前,
beforeCreate钩子之前执行,因此没有 this - 接收两个参数:
props:父组件传递的 props,是响应式的,不能解构,解构会丢失响应式context:上下文对象,包含attrs(透传属性)、slots(插槽)、emit(触发事件)、expose(暴露组件方法给父组件)
- 返回值:返回一个对象,对象中的属性和方法可以在模板中直接使用;也可以返回渲染函数
基础示例:
html
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">+1</button>
<p>双倍计数:{{ doubleCount }}</p>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup(props, context) {
// 定义响应式数据
const count = ref(0)
// 定义方法
const increment = () => {
count.value++
}
// 定义计算属性
const doubleCount = computed(() => count.value * 2)
// 返回给模板使用
return {
count,
increment,
doubleCount
}
}
}
</script>
更简洁的写法:<script setup> 语法糖 <script setup> 是单文件组件中使用组合式 API 的语法糖,大幅简化代码,无需写 setup 函数和 return 语句,顶层的变量、方法、导入的组件都可以直接在模板中使用,是 Vue 3 官方推荐的写法。
html
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="increment">+1</button>
<p>双倍计数:{{ doubleCount }}</p>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 直接定义,无需return,模板可直接使用
const count = ref(0)
const increment = () => {
count.value++
}
const doubleCount = computed(() => count.value * 2)
</script>
核心响应式 API
-
ref() 用于定义任意类型的响应式数据,优先用于基本类型(字符串、数字、布尔值),也支持引用类型(对象、数组)。
- 核心特点:通过
.value属性访问和修改值,模板中使用会自动解包,无需写.value - 底层实现:基本类型基于
Object.defineProperty,引用类型会自动调用reactive做深层代理
javascriptimport { ref } from 'vue' // 基本类型 const count = ref(0) console.log(count.value) // 0 count.value = 1 console.log(count.value) // 1 // 引用类型 const userInfo = ref({ name: '张三', age: 18 }) userInfo.value.age = 20 - 核心特点:通过
-
reactive() 用于定义引用类型(对象、数组、Map、Set 等)的响应式数据,不能用于基本类型。
- 核心特点:无需
.value,直接访问和修改属性,深层响应式,嵌套对象也有响应式 - 缺陷:直接解构会丢失响应式;直接给对象赋值新对象会丢失原代理
javascriptimport { reactive } from 'vue' const userInfo = reactive({ name: '张三', age: 18, address: { city: '北京' } }) // 直接修改,无需.value userInfo.age = 20 userInfo.address.city = '上海' - 核心特点:无需
-
toRefs() / toRef() 解决
reactive解构丢失响应式的问题。toRefs():将reactive对象的所有属性转为ref对象,解构后仍保持响应式toRef():将reactive对象的单个属性 转为ref对象
javascriptimport { reactive, toRefs, toRef } from 'vue' const userInfo = reactive({ name: '张三', age: 18 }) // 解构后仍有响应式 const { name, age } = toRefs(userInfo) console.log(age.value) // 18 age.value = 20 // 修改会同步到原reactive对象 // 单个属性转ref const userName = toRef(userInfo, 'name') -
**computed()**组合式 API 中的计算属性,支持只读和可写两种模式。
javascriptimport { ref, computed } from 'vue' const firstName = ref('张') const lastName = ref('三') // 只读模式(默认) const fullName = computed(() => { return firstName.value + lastName.value }) // 可写模式 const fullNameWritable = computed({ get: () => firstName.value + lastName.value, set: (newVal) => { const [first, last] = newVal.split('') firstName.value = first lastName.value = last } }) -
**watch()**侦听器,监听一个或多个响应式数据源,数据变化时执行回调函数,惰性执行(默认只有数据变化才执行),可获取新旧值。
javascriptimport { ref, watch } from 'vue' const count = ref(0) const userInfo = reactive({ name: '张三', age: 18 }) // 监听单个ref watch(count, (newVal, oldVal) => { console.log('count变化', newVal, oldVal) }) // 监听多个数据源 watch([count, () => userInfo.age], ([newCount, newAge], [oldCount, oldAge]) => { console.log('数据变化') }) // 深度监听:监听reactive对象的深层属性变化 watch( () => userInfo, (newVal) => { console.log('userInfo深层变化') }, { deep: true } ) // 立即执行:页面初始化就执行一次回调 watch( count, (newVal) => { console.log('立即执行', newVal) }, { immediate: true } ) -
**watchEffect()**立即执行的副作用函数,自动收集依赖,依赖的响应式数据变化时自动重新执行,无需指定监听源。
javascriptimport { ref, watchEffect } from 'vue' const count = ref(0) const name = ref('张三') // 立即执行一次,自动收集count和name为依赖 watchEffect(() => { console.log(`count: ${count.value}, name: ${name.value}`) }) count.value++ // 依赖变化,重新执行 name.value = '李四' // 依赖变化,重新执行
生命周期钩子在 setup 中的使用
组合式 API 中的生命周期钩子,需要先从 vue 中导入,以 on 开头,在 setup 中注册,执行时机和选项式 API 一致。
javascript
<script setup>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
onMounted(() => {
console.log('组件挂载完成')
})
onBeforeUnmount(() => {
console.log('组件即将销毁')
})
</script>
自定义组合式函数 (Composables)
组合式函数(Composables)是利用 Vue 组合式 API 封装和复用状态逻辑的函数,是 Vue 3 中逻辑复用的核心方案,彻底替代 Vue 2 中的 mixin。
命名规范 :以 use 开头的驼峰命名,比如 useMouse、useFetch、useLocalStorage
示例 1:封装鼠标位置逻辑
javascript
// src/composables/useMouse.js
import { ref, onMounted, onBeforeUnmount } from 'vue'
// 导出组合式函数
export function useMouse() {
// 封装内部响应式状态
const x = ref(0)
const y = ref(0)
// 封装内部方法
const updateMouse = (e) => {
x.value = e.pageX
y.value = e.pageY
}
// 生命周期管理
onMounted(() => {
window.addEventListener('mousemove', updateMouse)
})
onBeforeUnmount(() => {
window.removeEventListener('mousemove', updateMouse)
})
// 暴露给外部的状态和方法
return { x, y }
}
组件中使用:
html
<template>
<p>鼠标X坐标:{{ x }}</p>
<p>鼠标Y坐标:{{ y }}</p>
</template>
<script setup>
import { useMouse } from '@/composables/useMouse'
// 复用逻辑,解构获取状态
const { x, y } = useMouse()
</script>
核心优势:
- 无命名冲突:每个组件调用组合式函数,都会创建独立的响应式状态,互不影响
- 来源清晰:状态和方法的来源明确,直接看导入和调用即可
- 类型安全:完美支持 TypeScript,类型自动推导
- 按需复用:只导入需要的逻辑,tree-shaking 友好
- 逻辑内聚:相关的状态、方法、生命周期都封装在一个函数中,维护方便
3.2 项目结构与工程化
Vue 项目工程化是大型项目开发的基础,合理的项目结构、规范的代码组织、标准化的开发流程,能大幅提升团队协作效率、降低维护成本、提升项目稳定性。
目录结构最佳实践(Vue 3 + Vite)
以下是企业级 Vue 项目的标准目录结构,兼顾可扩展性、可维护性、团队协作:
vue-project/
├── public/ # 静态资源目录,不会被构建工具处理,直接复制到打包根目录
│ ├── favicon.ico # 网站图标
│ └── static/ # 不参与打包的静态资源,比如大图片、第三方库
├── src/ # 项目源码核心目录
│ ├── api/ # 接口请求目录,统一管理所有API接口
│ │ ├── modules/ # 按业务模块拆分接口
│ │ │ ├── user.js # 用户相关接口
│ │ │ └── goods.js # 商品相关接口
│ │ └── request.js # Axios实例封装,请求/响应拦截器
│ ├── assets/ # 参与构建的静态资源,会被Vite处理、压缩、哈希命名
│ │ ├── images/ # 图片资源
│ │ ├── fonts/ # 字体文件
│ │ └── styles/ # 全局样式文件
│ │ ├── index.scss # 全局样式入口
│ │ ├── variables.scss # 全局SCSS变量
│ │ └── reset.scss # 样式重置
│ ├── components/ # 组件目录
│ │ ├── common/ # 全局通用基础组件,比如按钮、输入框、弹窗
│ │ └── business/ # 业务通用组件,多个页面复用的业务组件
│ ├── composables/ # 组合式函数目录,封装可复用的业务逻辑
│ ├── directives/ # 全局自定义指令
│ ├── plugins/ # Vue插件目录,全局注册组件、指令、原型方法
│ ├── router/ # 路由配置目录
│ │ ├── index.js # 路由入口文件
│ │ └── modules/ # 按业务模块拆分路由配置
│ ├── stores/ # Pinia状态管理目录,按业务模块拆分store
│ ├── utils/ # 工具函数目录,封装通用工具方法
│ │ ├── auth.js # 权限、token相关工具
│ │ ├── validate.js # 表单校验工具
│ │ └── format.js # 数据格式化工具
│ ├── views/ # 页面组件目录,对应路由的页面
│ │ ├── home/ # 首页模块
│ │ ├── login/ # 登录页
│ │ └── 404.vue # 404页面
│ ├── App.vue # 根组件
│ └── main.js # 项目入口文件
├── .env # 全局环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .eslintrc.js # ESLint配置
├── .prettierrc # Prettier配置
├── vite.config.js # Vite配置文件
├── package.json # 项目依赖、脚本配置
└── README.md # 项目说明文档
模块化与组件划分原则
-
单一职责原则一个组件 / 模块只负责一件事,功能边界清晰,避免一个组件承载过多逻辑,通常单个组件代码不超过 500 行,超出则考虑拆分。
-
分层设计原则组件分为三层,从上到下依赖,禁止反向依赖:
- 页面组件(Page):对应路由页面,负责页面布局、数据获取、状态管理,不包含复杂的 UI 逻辑,只调用业务组件和基础组件
- 业务组件(Business Component):封装特定业务逻辑的可复用组件,比如商品卡片、订单列表、用户信息卡片,只接收 props、触发事件,不直接调用接口
- 基础组件(Common Component):无业务逻辑的通用 UI 组件,比如按钮、输入框、弹窗、表格,只负责 UI 渲染和基础交互,可在全项目复用
-
可复用性原则重复出现 2 次及以上的 UI 和逻辑,必须抽离为组件 / 组合式函数;组件设计要考虑通用性,通过 props 配置不同行为,避免硬编码业务逻辑。
-
数据流向原则严格遵循单向数据流,数据通过 props 向下传递,事件向上传递,禁止子组件直接修改 props、禁止直接修改父组件数据、禁止组件之间直接互相修改状态。
-
避免过度封装不要为了封装而封装,简单的逻辑无需过度拆分,平衡复用性和可维护性。
代码风格与规范
统一的代码规范是团队协作的基础,推荐使用 ESLint + Prettier 组合,实现代码检查和自动格式化,配合 Vue 官方的规范插件。
-
核心依赖安装
# ESLint 核心 npm install eslint --save-dev # Vue ESLint 插件 npm install eslint-plugin-vue --save-dev # Prettier 核心 npm install prettier --save-dev # ESLint 与 Prettier 兼容插件 npm install eslint-config-prettier eslint-plugin-prettier --save-dev -
基础规范约定
- 命名规范:
- 组件名:大驼峰命名(PascalCase),比如
UserCard.vue、MyButton.vue - 组合式函数:小驼峰命名,以 use 开头,比如
useFetch、useLocalStorage - 变量、方法:小驼峰命名(camelCase),语义化,避免拼音、缩写
- 常量:全大写,下划线分隔,比如
MAX_PAGE_SIZE、BASE_API_URL
- 组件名:大驼峰命名(PascalCase),比如
- 模板规范:
- 多个属性换行,每行一个属性
- 指令缩写统一,
:代替v-bind:,@代替v-on: - 模板中避免复杂逻辑,复杂计算抽离到 computed
- 脚本规范:
- 导入顺序:内置模块 → 第三方依赖 → 自定义组件 / 工具
- 避免 this 滥用,组合式 API 中无 this
- 回调函数优先使用箭头函数,避免 this 指向混乱
- 避免魔法数字,抽离为常量
- 样式规范:
- 单文件组件样式默认添加
scoped,避免样式污染 - 通用样式抽离到全局样式文件,避免重复代码
- 统一使用 SCSS/Less 预处理器,使用变量管理颜色、尺寸
- 单文件组件样式默认添加
- 命名规范:
环境变量与配置
Vite 内置了环境变量支持,通过 .env 文件管理不同环境的配置,避免硬编码,实现开发、测试、生产环境的隔离。
-
环境变量文件规则
.env:所有环境都会加载的全局环境变量.env.development:开发环境(npm run dev)加载.env.production:生产环境(npm run build)加载.env.test:测试环境加载,需配合--mode test指定模式
-
环境变量定义规则
-
Vite 环境变量必须以
VITE_开头,否则不会暴露给客户端.env.development 开发环境
VITE_BASE_API = /api
VITE_APP_TITLE = Vue开发环境.env.production 生产环境
VITE_BASE_API = https://api.xxx.com
VITE_APP_TITLE = Vue生产环境
-
-
环境变量使用
- 项目中通过
import.meta.env.变量名访问
javascript// request.js const service = axios.create({ baseURL: import.meta.env.VITE_BASE_API, timeout: 10000 }) - 项目中通过
构建工具简介与优化
Vue 项目主流构建工具分为 Vite 和 Webpack(Vue CLI),Vite 是 Vue 3 官方推荐的构建工具,基于 ESBuild 和 Rollup,开发体验远超 Webpack。
-
Vite 核心优势
- 极速的冷启动:基于浏览器原生 ES 模块,无需打包整个项目,启动时间几乎与项目复杂度无关
- 即时热更新(HMR):修改代码后,只更新修改的模块,无需刷新页面,更新速度不随项目变大而变慢
- 开箱即用:内置对 TypeScript、JSX、CSS 预处理器、静态资源的原生支持,无需复杂配置
- 优化的构建:基于 Rollup 构建,自动 tree-shaking、代码分割、压缩,打包产物更优
-
Vite 基础优化配置
javascript// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { resolve } from 'path' // 包体积分析插件 import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ // 插件配置 plugins: [ vue(), visualizer({ open: true }) // 打包后自动打开包体积分析页面 ], // 路径别名配置 resolve: { alias: { '@': resolve(__dirname, 'src') // @ 指向src目录 } }, // 开发服务配置 server: { port: 3000, // 端口号 open: true, // 启动自动打开浏览器 cors: true, // 开启跨域 // 代理配置,解决开发环境跨域问题 proxy: { '/api': { target: 'https://api.xxx.com', // 目标接口地址 changeOrigin: true, // 开启跨域 rewrite: (path) => path.replace(/^\/api/, '') // 路径重写 } } }, // 构建配置 build: { outDir: 'dist', // 打包输出目录 assetsDir: 'assets', // 静态资源目录 sourcemap: false, // 生产环境关闭sourcemap,减小包体积 // 分包配置 rollupOptions: { output: { // 分包策略:将第三方依赖单独打包 manualChunks: { vue: ['vue', 'vue-router', 'pinia'], ui: ['element-plus'], utils: ['axios', 'dayjs'] } } } } })
3.3 性能优化
Vue 项目性能优化分为运行时性能优化 和打包体积优化两大方向,核心目标是提升首屏加载速度、降低页面卡顿、提升用户体验。
虚拟 DOM 与 Diff 算法理解
Vue 的渲染核心是虚拟 DOM 和 Diff 算法,理解其原理是性能优化的基础。
- 虚拟 DOM:用 JavaScript 对象描述真实 DOM 的结构,避免直接操作 DOM。每次数据变化,生成新的虚拟 DOM 树,对比新旧虚拟 DOM 的差异,只把差异部分更新到真实 DOM,大幅减少 DOM 操作次数。
- Diff 算法 :Vue 的 Diff 算法采用同层比较 策略,只对比同一层级的节点,不跨层级对比,时间复杂度从 O (n³) 优化到 O (n);采用双端对比 算法,高效处理列表节点的移动、新增、删除;通过
key给节点唯一标识,避免就地复用导致的渲染错误和性能损耗。
优化关键点:
- 列表渲染必须添加
key,且key必须是唯一稳定的(比如 id),禁止使用 index 作为 key,否则节点顺序变化时,Diff 算法会出现错误复用,导致性能损耗和渲染 bug。 - 避免嵌套过深的 DOM 结构,减少虚拟 DOM 的对比层级,提升 Diff 效率。
避免不必要的渲染
-
v-once 指令只渲染元素和组件一次,后续数据变化不会重新渲染,适用于静态内容、无数据变化的节点,减少渲染开销。
html<p v-once>这是静态内容,不会重新渲染</p> -
合理使用 v-if 和 v-show
v-if:不满足条件时,节点不会渲染,直接销毁,切换时会触发组件的销毁和重建,开销大。适用于切换频率低的场景。v-show:始终渲染节点,通过display: none控制显隐,切换开销极小。适用于切换频率高的场景 。 错误用法:频繁切换的弹窗、tab 使用 v-if,导致每次切换都重新创建组件,性能极差。
-
避免模板中的复杂计算 模板中写复杂的计算逻辑,每次组件重新渲染都会重新执行,导致性能损耗。应将复杂计算抽离到
computed,利用缓存特性,依赖不变时不会重新计算。html<!-- 错误写法:模板中复杂计算 --> <p>{{ list.filter(item => item.checked).reduce((sum, item) => sum + item.price, 0) }}</p> <!-- 正确写法:抽离到computed --> <p>{{ totalPrice }}</p> <script setup> const totalPrice = computed(() => { return list.filter(item => item.checked).reduce((sum, item) => sum + item.price, 0) }) </script>
计算属性和侦听器的优化
-
优先使用
computed做数据派生,利用缓存特性,减少重复计算。 -
避免滥用
watch,只在必要的异步操作、副作用场景使用。 -
避免不必要的深度监听
deep: true,深度监听会遍历对象的所有属性,性能损耗大,尽量精准监听需要的属性。javascript// 错误写法:深度监听整个对象 watch( userInfo, () => { console.log('name变化') }, { deep: true } ) // 正确写法:只监听需要的属性 watch( () => userInfo.name, () => { console.log('name变化') } )
组件懒加载 / 路由懒加载
懒加载也叫按需加载,核心是将代码分包,首屏不加载非必要的代码,访问对应页面 / 组件时再加载,大幅减小首屏包体积,提升首屏加载速度。
-
路由懒加载(最常用)
javascript// router/index.js const routes = [ { path: '/', name: 'Home', component: () => import('@/views/Home.vue') // 懒加载 }, { path: '/about', name: 'About', component: () => import('@/views/About.vue') // 懒加载 } ] -
组件懒加载对于大组件、非首屏的组件,使用动态导入实现懒加载:
javascript<template> <div> <!-- 点击时才加载组件 --> <button @click="showBigComponent = true">显示大组件</button> <BigComponent v-if="showBigComponent" /> </div> </template> <script setup> import { ref } from 'vue' const showBigComponent = ref(false) // 动态导入,懒加载组件 const BigComponent = defineAsyncComponent(() => import('@/components/BigComponent.vue')) </script>
使用 keep-alive 缓存组件状态
<keep-alive> 可以缓存不活动的组件实例,避免重复创建和销毁,大幅提升组件切换的性能,同时保留组件的状态(比如表单输入内容、滚动位置)。
优化场景:
- tab 切换、页面跳转时,保留页面状态,无需重新请求接口、重新渲染
- 列表页跳转到详情页,返回列表页时,保留之前的筛选条件、滚动位置、分页状态
使用示例:
html
<!-- 缓存所有路由页面 -->
<keep-alive>
<router-view></router-view>
</keep-alive>
<!-- 只缓存指定名称的组件 -->
<keep-alive include="Home,List">
<router-view></router-view>
</keep-alive>
<!-- 排除指定名称的组件 -->
<keep-alive exclude="Detail,Edit">
<router-view></router-view>
</keep-alive>
打包体积优化
-
按需引入第三方依赖避免全量引入第三方库,只引入需要的模块,减小打包体积。
-
UI 组件库:Element Plus、Ant Design Vue 等都支持按需引入,自动导入使用的组件,不打包未使用的组件。
-
工具库:比如 Lodash,避免全量引入,只引入需要的函数:
javascript// 错误写法:全量引入,打包整个Lodash import _ from 'lodash' // 正确写法:按需引入,只打包用到的函数 import { debounce, cloneDeep } from 'lodash-es'
-
-
Tree-Shaking 优化利用构建工具的 Tree-Shaking 能力,剔除未使用的代码(死代码)。
- 优先使用 ES 模块规范(import/export),避免使用 CommonJS(require)
- 避免副作用代码,确保纯函数
- 生产环境开启压缩,剔除 console、debugger 等调试代码
javascript// vite.config.js 生产环境剔除console build: { minify: 'terser', terserOptions: { compress: { drop_console: true, drop_debugger: true } } } -
静态资源优化
- 图片优化:小图片转为 base64 内联,大图片使用 webp/avif 等高效格式,图片压缩后再引入项目
- 字体优化:字体文件子集化,只保留项目用到的字符,减小字体包体积
- 避免在项目中存放大体积静态资源,使用 CDN 托管
-
CDN 加速将不常更新的第三方依赖(Vue、Vue Router、Element Plus 等)通过 CDN 引入,不打包到项目中,减小打包体积,同时利用 CDN 的边缘节点加速加载。
性能分析工具
- Vue Devtools:Vue 官方调试工具,可查看组件树、组件渲染时间、性能瓶颈、状态变化,是 Vue 性能优化的首选工具。
- Chrome DevTools Performance:分析页面运行时性能,定位卡顿、长任务、重排重绘的瓶颈。
- Chrome DevTools Lighthouse:全面检测页面的性能、可访问性、SEO,给出优化评分和具体建议。
- rollup-plugin-visualizer / webpack-bundle-analyzer:包体积分析工具,可视化展示打包产物的构成,定位大体积依赖,针对性优化。
3.4 测试
前端测试是保障项目质量、减少线上 bug、便于重构的核心手段,Vue 项目测试分为单元测试 和端到端(E2E)测试两大类。
测试的重要性与策略
- 提升代码质量,提前发现潜在 bug,减少线上问题
- 便于代码重构,有测试覆盖的代码,重构时可快速验证是否影响原有功能
- 保障核心业务流程的稳定性,避免迭代中出现核心功能故障
- 提升团队协作效率,测试用例是最好的功能文档
测试策略:
- 单元测试:覆盖核心业务逻辑、工具函数、通用组件,保障代码单元的正确性
- E2E 测试:覆盖核心业务流程(登录、下单、支付等),模拟用户真实操作,保障全链路的正确性
- 避免过度测试:无需追求 100% 测试覆盖率,优先保障核心功能、高频使用场景、复杂逻辑的测试覆盖
单元测试
单元测试是对软件中最小可测试单元的验证,Vue 项目中,单元测试的核心对象是:工具函数、组合式函数、通用组件、Vuex/Pinia 的 store。
推荐技术栈:
- Vitest:Vue 3 首选测试框架,和 Vite 生态完美兼容,API 兼容 Jest,执行速度极快,对 Vue 组件支持友好
- Vue Test Utils:Vue 官方的组件测试工具库,提供了组件挂载、交互、属性访问的 API,是 Vue 组件测试的标准工具
基础示例:
-
安装依赖
npm install vitest @vue/test-utils happy-dom --save-dev -
配置 Vitest
javascript// vite.config.js import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], test: { environment: 'happy-dom', // 模拟浏览器DOM环境 globals: true, // 全局导入describe、it、expect等API } }) -
组件测试示例待测试组件
MyButton.vue:javascript<template> <button @click="handleClick" :disabled="disabled"> {{ text }} </button> </template> <script setup> const props = defineProps({ text: { type: String, default: '按钮' }, disabled: { type: Boolean, default: false } }) const emit = defineEmits(['click']) const handleClick = () => { emit('click') } </script>测试用例
MyButton.test.js:javascriptimport { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import MyButton from './MyButton.vue' // 测试套件 describe('MyButton组件', () => { // 测试用例1:渲染正确的文本 it('渲染正确的文本', () => { const wrapper = mount(MyButton, { props: { text: '测试按钮' } }) // 断言:按钮文本正确 expect(wrapper.text()).toBe('测试按钮') }) // 测试用例2:disabled状态生效 it('disabled状态禁用按钮', () => { const wrapper = mount(MyButton, { props: { disabled: true } }) // 断言:按钮被禁用 expect(wrapper.attributes('disabled')).toBeDefined() }) // 测试用例3:点击触发click事件 it('点击按钮触发click事件', () => { const wrapper = mount(MyButton) // 触发点击事件 wrapper.trigger('click') // 断言:事件被触发 expect(wrapper.emitted()).toHaveProperty('click') }) }) -
执行测试在 package.json 中添加脚本:
javascript{ "scripts": { "test": "vitest", "test:coverage": "vitest --coverage" } }执行
npm run test运行测试用例,npm run test:coverage生成测试覆盖率报告。
端到端 (E2E) 测试
端到端测试是模拟用户真实操作,从用户视角测试整个应用的完整流程,验证应用的功能是否符合预期,不关注内部实现,只关注最终的页面表现。
推荐技术栈:
- Cypress:开箱即用,API 简洁,调试友好,支持实时预览,是前端 E2E 测试的首选
- Playwright:微软开发的 E2E 测试框架,支持多浏览器、多平台,性能优异,适合复杂场景
核心测试场景:
- 用户登录、注册流程
- 商品列表、详情、下单、支付的完整购物流程
- 表单提交、校验、数据回显
- 页面跳转、权限控制、异常场景处理
3.5 高级特性
Teleport(传送门)
Teleport 是 Vue 3 新增的内置组件,用于将组件的模板内容渲染到 DOM 树的指定位置,不受父组件 DOM 结构的限制,解决了弹窗、tooltip、下拉菜单等组件的层级(z-index)、overflow:hidden 导致的遮挡问题。
基础用法:
html
<template>
<div class="container">
<button @click="showModal = true">打开弹窗</button>
<!-- 弹窗内容,通过to属性指定渲染到body标签下 -->
<teleport to="body">
<div v-if="showModal" class="modal">
<p>这是弹窗内容,渲染到body下,不受父组件样式影响</p>
<button @click="showModal = false">关闭弹窗</button>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>
<style scoped>
.container {
overflow: hidden;
position: relative;
z-index: 1;
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999;
background: #fff;
padding: 20px;
}
</style>
核心说明:即使父组件有
overflow: hidden和低 z-index,弹窗也会渲染到 body 下,不会被遮挡,层级正常生效。
Fragments(片段)
Fragments 是 Vue 3 新增的特性,支持组件模板有多个根节点,解决了 Vue 2 中组件必须有一个根 div 的限制,减少了不必要的 DOM 嵌套,优化了 DOM 结构。
Vue 2 错误写法(会报错):
html
<template>
<header>标题</header>
<main>内容</main>
<footer>底部</footer>
</template>
Vue 3 正确写法(多根节点):
html
<template>
<header>标题</header>
<main>内容</main>
<footer>底部</footer>
</template>
注意:多根节点组件使用透传属性时,需要手动指定属性绑定的节点,否则 Vue 会发出警告。
自定义指令
自定义指令用于对普通 DOM 元素进行底层操作,封装可复用的 DOM 交互逻辑,是 Vue 组件逻辑复用的补充。
自定义指令分为全局注册 和局部注册,包含一组生命周期钩子函数,对应元素的不同生命周期阶段。
核心钩子函数:
created:元素属性或事件监听器应用前调用beforeMount:元素被挂载到 DOM 前调用mounted:元素被挂载到 DOM 后调用(最常用)beforeUpdate:元素更新前调用updated:元素更新后调用beforeUnmount:元素卸载前调用unmounted:元素卸载后调用
示例 1:自动聚焦指令 v-focus
javascript
// 全局注册
// main.js
app.directive('focus', {
// 元素挂载后自动聚焦
mounted(el) {
el.focus()
}
})
html
<!-- 组件中使用 -->
<input v-focus type="text" placeholder="自动聚焦输入框">
示例 2:图片懒加载指令 v-lazy
javascript
app.directive('lazy', {
mounted(el, binding) {
// 图片默认占位图
el.src = '/images/loading.gif'
// 创建交叉观察器,监听元素是否进入视口
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// 进入视口,加载真实图片
el.src = binding.value
// 加载完成后停止观察
observer.unobserve(el)
}
})
// 观察元素
observer.observe(el)
}
})
javascript
<!-- 组件中使用 -->
<img v-lazy="https://example.com/real-image.jpg" alt="懒加载图片">
渲染函数 & JSX
Vue 的模板最终会被编译器编译为渲染函数,渲染函数是 Vue 组件渲染的底层实现,用 JavaScript 完全描述 DOM 结构,比模板更灵活,适合复杂动态渲染的场景。
Vue 3 完美支持 JSX 语法,JSX 是 JavaScript 的语法扩展,允许在 JS 中写 HTML 标签,写渲染函数比原生 h () 函数更简洁直观。
渲染函数基础示例:
javascript
<script>
import { h } from 'vue'
export default {
props: {
level: {
type: Number,
default: 1
},
title: {
type: String,
default: ''
}
},
// 渲染函数
render() {
// h函数创建虚拟DOM,三个参数:标签名、属性、子节点
return h(`h${this.level}`, { class: 'title' }, this.title)
}
}
</script>
JSX 示例:
javascript
<script setup>
const props = defineProps({
level: {
type: Number,
default: 1
},
title: {
type: String,
default: ''
}
})
// JSX直接返回
const renderTitle = () => {
const Tag = `h${props.level}`
return <Tag class="title">{props.title}</Tag>
}
</script>
<template>
<renderTitle />
</template>
适用场景:动态渲染层级不确定的组件、复杂的条件渲染、高阶组件封装、需要完全控制渲染逻辑的场景。
插件开发
插件是为 Vue 添加全局功能的扩展方式,用于封装全局组件、全局指令、原型方法、全局资源、自定义功能等,是 Vue 生态的核心组成部分。
Vue 3 插件是一个包含 install 方法的对象,或者直接是一个函数,接收两个参数:app 应用实例、options 插件配置选项。
插件开发示例:
javascript
// plugins/my-plugin.js
// 定义插件
const MyPlugin = {
// 必须的install方法
install(app, options) {
// 1. 全局注册组件
app.component('MyButton', () => import('@/components/MyButton.vue'))
// 2. 全局注册指令
app.directive('focus', {
mounted(el) {
el.focus()
}
})
// 3. 全局注入属性,所有组件可通过inject访问
app.provide('globalConfig', options)
// 4. 挂载全局方法,所有组件可通过app.config.globalProperties访问
app.config.globalProperties.$message = (text) => {
alert(text)
}
}
}
export default MyPlugin
插件使用:
javascript
运行
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import MyPlugin from './plugins/my-plugin'
const app = createApp(App)
// 安装插件,传递配置选项
app.use(MyPlugin, {
baseUrl: '/api',
timeout: 10000
})
app.mount('#app')
持续学习与社区
Vue 技术生态在持续迭代,想要精通 Vue,需要建立持续学习的习惯,紧跟官方更新,融入社区,不断提升技术深度。
官方文档
官方文档是最权威、最准确的学习资料,是学习 Vue 的首选:
- Vue 3 官方中文文档 :https://cn.vuejs.org/
- Vue 2 官方中文文档 :https://v2.cn.vuejs.org/
- Vue Router 官方文档 :https://router.vuejs.org/zh/
- Pinia 官方文档 :https://pinia.vuejs.org/zh/
- Nuxt 官方文档 :https://nuxt.com/docs
- Vue Test Utils 官方文档 :https://test-utils.vuejs.org/
核心 RFCs
Vue 的所有重大变更、新特性、设计决策,都通过 RFC(Request For Comments)流程公开讨论,所有 RFC 都托管在 GitHub 仓库:https://github.com/vuejs/rfcs
阅读 RFC 可以:
- 了解 Vue 新特性的设计背景、解决的问题、使用方式
- 理解 Vue 团队的设计理念和技术演进方向
- 参与社区讨论,提出自己的建议和意见
- 提前了解 Vue 未来的版本更新,做好技术储备
优质博客与社区
- GitHub :Vue 官方仓库 https://github.com/vuejs/core,关注 issue、PR、release,了解最新更新,参与开源贡献
- 掘金:国内优质的前端技术社区,有大量 Vue 相关的优质教程、实战经验、源码解析文章
- InfoQ:关注前端技术前沿,有 Vue 相关的深度技术文章、行业实践分享
- 知乎:Vue 相关的话题、专栏,有很多技术大佬的深度分享
- Dev.to / Medium:国外优质技术社区,有很多 Vue 的国际前沿技术分享
- Vue 官方论坛 :https://forum.vuejs.org/,官方维护的论坛,可提问、交流、分享
关注 Vue 3 及未来版本的新特性
Vue 团队的迭代节奏稳定,会持续发布新特性、性能优化、bug 修复,关注新特性可以:
- 及时使用新 API,优化代码,提升开发效率
- 了解 Vue 的技术演进方向,提前做好技术规划
- 避免使用废弃 API,减少技术债务
关注渠道:
- Vue 官方博客:https://blog.vuejs.org/
- Vue 官方 Twitter/X:@vuejs
- Vue 官方 GitHub Release 页面
- 尤雨溪(Vue 作者)的社交媒体账号
附录
常用工具库
- Axios:基于 Promise 的 HTTP 请求库,是 Vue 项目接口请求的首选方案,支持请求 / 响应拦截器、取消请求、JSON 自动转换、客户端 XSRF 防护等能力。
- Lodash :JavaScript 实用工具库,提供了大量数组、对象、函数、字符串的处理方法,比如防抖节流、深拷贝、类型判断、集合处理等,是前端开发的瑞士军刀,推荐使用支持 ES 模块的
lodash-es版本,支持按需引入。 - Day.js:轻量级日期处理库,API 和 Moment.js 兼容,体积仅 2KB,是 Moment.js 的完美替代品,支持日期格式化、解析、计算、时区等能力。
- Vue I18n:Vue 官方的国际化解决方案,支持多语言切换、复数、日期时间格式化、组件内本地化等能力,完美支持 Vue 3 和 Nuxt。
- VueUse:基于 Vue 组合式 API 的实用工具函数库,提供了 200 + 常用的组合式函数,比如 useMouse、useLocalStorage、useFetch、useDebounce 等,开箱即用,是 Vue 项目的必备工具库。
UI 组件库
PC 端组件库
- Element Plus:饿了么团队开发的基于 Vue 3 的 PC 端组件库,是国内使用率最高的 Vue PC 组件库,提供了 60 + 常用组件,文档友好,生态完善,适合中后台管理系统开发。
- Ant Design Vue:蚂蚁集团开发的企业级 UI 组件库,基于 Ant Design 设计规范,功能丰富,严谨专业,提供了 80 + 组件,适合大型企业级中后台系统开发,完美支持 Vue 3 和 TypeScript。
- Vuetify:基于 Material Design 3 的 Vue 组件库,样式美观,功能丰富,内置 100 + 组件,支持响应式布局、主题定制,无需 CSS 基础即可快速开发美观的页面,完美支持 Vue 3。
- Quasar Framework:高性能的 Vue 跨端框架,一套代码可发布到 PC、H5、移动端 App、小程序、桌面端等多个平台,内置 200 + 组件,性能优异,功能全面,适合多端项目开发。
移动端组件库
- Vant:有赞团队开发的轻量级移动端 Vue 组件库,国内移动端首选,内置 60 + 常用组件,性能优异,文档完善,完美支持 Vue 3 和 SSR。
- NutUI:京东团队开发的移动端 Vue 组件库,基于京东 APP 设计规范,内置 70 + 组件,支持 Vue 3、多端适配、主题定制。
- Varlet:基于 Material Design 3 的 Vue 3 移动端组件库,完全使用 TypeScript 开发,性能优异,支持按需引入、主题定制、SSR。
调试工具
Vue Devtools:Vue 官方开发的浏览器调试插件,是 Vue 开发的必备工具,支持 Vue 2 和 Vue 3,核心能力:
- 组件树查看:查看应用的组件层级结构,组件的 props、data、computed、事件等状态
- 状态管理调试:查看 Pinia/Vuex 的状态变化、mutation/action 触发记录,支持时间旅行、状态回滚
- 路由调试:查看路由配置、导航记录、路由参数、元信息
- 性能分析:分析组件的渲染时间、更新次数,定位性能瓶颈
- 事件追踪:追踪组件的事件触发、自定义事件传递
- 源码定位:快速定位组件对应的源码文件,方便调试
支持 Chrome、Edge、Firefox 等主流浏览器,可直接在浏览器应用商店搜索安装。