一、Vue 框架概述
Vue.js 是一套用于构建用户界面的渐进式 JavaScript 框架 ,于 2014 年正式发布。它的核心思想是 "渐进式",意味着开发者可以根据项目需求,逐步引入 Vue 的功能模块,而无需一次性接纳整个框架。无论是小型单页应用,还是大型复杂项目,Vue 都能灵活适配。具有轻量、高效、易上手的特点,其核心库只关注视图层,与其他库或现有项目整合时兼容性良好。同时,Vue 拥有完善的官方文档和活跃的社区,为开发者提供了丰富的学习资源和问题解决方案。
二、Vue 的核心特性
2.1 响应式数据绑定
Vue 的响应式系统是其核心亮点之一。当数据发生变化时,视图会自动更新,无需开发者手动操作 DOM。这一特性基于 JavaScript 的 Object.defineProperty()
方法(Vue 2.x)或 Proxy
对象(Vue 3.x)实现。
Vue 2.x 实现原理
通过递归遍历数据对象的属性,使用 Object.defineProperty()
为每个属性添加 getter 和 setter。具体实现过程如下:
- 初始化阶段:遍历 data 对象的所有属性
- 依赖收集:当属性被访问时,触发 getter 将当前 Watcher 实例添加到依赖列表中
- 变更通知:当属性值被修改时,触发 setter 通知所有依赖进行更新
- 视图更新:触发组件的重新渲染
示例:
javascript
// 简化的响应式实现
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`读取 ${key}: ${val}`);
return val;
},
set(newVal) {
console.log(`设置 ${key}: ${newVal}`);
val = newVal;
// 通知依赖更新
dep.notify();
}
});
}
局限性:
- 无法检测到对象属性的添加或删除
- 对数组的索引修改和长度变化无法检测
- 需要递归遍历整个对象,性能开销较大
Vue 3.x 优化
采用 Proxy
对象替代 Object.defineProperty()
,主要改进包括:
- 全面响应式:可以监听到对象属性的添加、删除和数组索引变化
- 性能提升:不需要递归遍历整个对象,按需响应
- 简化实现:统一处理对象和数组的响应式逻辑
- 惰性监听:只有当属性被访问时才会创建响应式
示例:
javascript
const reactive = (target) => {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 依赖收集
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return true;
}
});
};
实际应用场景:
- 表单输入实时反馈
- 数据可视化图表自动更新
- 列表数据变更时的UI同步
2.2 组件化开发
组件化是 Vue 的另一个核心特性,它允许开发者将页面拆分为多个独立、可复用的组件,每个组件包含自己的模板、脚本和样式。这种开发方式具有以下优势:
组件化优势
-
代码复用:
- 通用组件(如按钮、表单控件)可以在不同项目或页面中重复使用
- 业务组件(如商品卡片、评论模块)可以在同一项目的多个位置复用
- 减少重复代码量,提高开发效率
-
维护便捷:
- 每个组件独立维护,职责单一
- 修改功能时只需关注对应组件,不影响其他部分
- 通过props和events明确定义组件接口,降低耦合度
-
团队协作:
- 不同开发者可以并行开发不同组件
- 组件文档化后便于团队共享使用
- 适合大型项目开发和管理
组件类型
全局组件
通过 Vue.component()
方法注册,在整个 Vue 应用中都可以使用。
注册示例:
javascript
Vue.component('my-button', {
template: '<button class="btn"><slot></slot></button>',
props: ['type']
});
使用场景:
- 应用级通用组件(如布局组件、导航栏)
- 频繁使用的UI组件(如按钮、图标)
局部组件
在组件的 components
选项中注册,只能在当前组件内部使用。
注册示例:
javascript
const UserProfile = {
template: '<div class="profile">{{ username }}</div>',
props: ['username']
};
new Vue({
el: '#app',
components: {
'user-profile': UserProfile
}
});
使用场景:
- 特定业务场景的专用组件
- 组件只在有限范围内使用
- 需要保持组件封装性的情况
组件通信方式
- Props向下传递:父组件向子组件传递数据
- Events向上通知:子组件向父组件发送事件
- Provide/Inject:跨层级组件通信
- Event Bus:非父子组件间通信
- Vuex:全局状态管理
2.3 模板语法
Vue 使用基于 HTML 的模板语法,允许开发者将 DOM 与 Vue 实例中的数据进行绑定。模板语法主要包括以下几种形式:
文本插值
使用双大括号 {``{ }}
将数据插入到 DOM 中。
示例:
html
<p>消息:{{ message }}</p>
<p>计算值:{{ count * 2 }}</p>
<p>格式化日期:{{ formatDate(date) }}</p>
特点:
- 支持简单的JavaScript表达式
- 自动更新数据变化
- 可以使用过滤器(Vue 2.x)或方法处理数据
指令系统
带有 v-
前缀的特殊属性,用于实现DOM操作和数据绑定。
常用指令
-
条件渲染:
v-if
/v-else
/v-else-if
:根据条件渲染DOM元素
html<div v-if="score >= 90">优秀</div> <div v-else-if="score >= 60">及格</div> <div v-else>不及格</div>
-
列表渲染:
v-for
:遍历数组或对象
html<ul> <li v-for="(item, index) in items" :key="item.id"> {{ index }} - {{ item.name }} </li> </ul>
-
属性绑定:
v-bind
(缩写:
):动态绑定HTML属性
html<img :src="imagePath" :alt="imageAlt"> <button :disabled="isDisabled">提交</button>
-
事件绑定:
v-on
(缩写@
):绑定事件监听器
html<button @click="handleClick">点击</button> <input @keyup.enter="submitForm">
-
双向绑定:
v-model
:表单元素与数据的双向绑定
html<input v-model="username"> <select v-model="selectedOption"> <option value="A">选项A</option> <option value="B">选项B</option> </select>
计算属性与侦听器
计算属性
通过 computed
选项定义,基于现有数据计算出新的数据。
特点:
- 具有缓存特性,只有当依赖的数据变化时才会重新计算
- 适合处理复杂逻辑和数据转换
- 声明式编程,模板中使用更简洁
示例:
javascript
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
},
filteredList() {
return this.list.filter(item => item.active);
}
}
侦听器
通过 watch
选项定义,用于监听数据的变化。
特点:
- 适合执行异步操作或复杂逻辑
- 可以监听特定数据的变化
- 命令式编程,更灵活
示例:
javascript
watch: {
searchQuery(newVal, oldVal) {
this.fetchResults(newVal);
},
'user.id': {
handler: 'loadUserData',
immediate: true
}
}
应用场景对比:
- 计算属性:需要基于现有数据派生新值(如格式化显示、过滤列表)
- 侦听器:需要在数据变化时执行特定操作(如API调用、验证逻辑)
三、Vue 实例与生命周期
3.1 Vue 实例
每个 Vue 应用都是从创建 Vue 实例开始的,这是构建 Vue 应用的起点。Vue 实例是 Vue 应用的根组件,通过 new Vue({ 选项 })
的方式创建。这个实例包含了应用的所有核心功能:
javascript
const vm = new Vue({
// 挂载目标,指定Vue实例管理的DOM元素
// 可以是CSS选择器字符串或直接传入DOM元素
el: '#app',
// 应用的数据对象,Vue会递归将其转换为getter/setter
data: {
message: 'Hello Vue!',
counter: 0,
user: {
name: 'John',
age: 25
}
},
// 应用的方法集合
methods: {
showMessage() {
alert(this.message);
},
incrementCounter() {
this.counter++;
}
},
// 计算属性
computed: {
reversedMessage() {
return this.message.split('').reverse().join('');
}
},
// 监听属性
watch: {
counter(newVal, oldVal) {
console.log(`计数器从${oldVal}变为${newVal}`);
}
}
});
在 Vue 实例中,this
指向当前的 Vue 实例,开发者可以通过 this
访问实例中的数据和方法。例如,在方法中可以通过 this.message
访问数据,在模板中则可以直接使用 message
。
3.2 生命周期钩子函数
Vue 实例从创建到销毁的过程称为生命周期,在这个过程中,Vue 会自动调用一系列的钩子函数,开发者可以在这些钩子函数中执行相应的操作。
详细的生命周期钩子函数说明:
-
beforeCreate:
- 实例创建前调用
- 此时数据观测 (data observer) 和事件/监视器配置尚未初始化
- 示例场景:可以在此时添加一些全局事件总线的事件监听
-
created:
- 实例创建后调用
- 已经完成了数据观测、属性和方法的运算,但挂载阶段还未开始
- 常用于异步获取数据、初始化非DOM相关的操作
- 此时可以访问
this.data
和this.methods
-
beforeMount:
- DOM 挂载前调用
- 模板编译已完成,但尚未将模板渲染到页面中
- 很少在此阶段进行操作,因为此时DOM还未生成
-
mounted:
- DOM 挂载后调用
- 此时可以访问DOM元素,常用于:
- 初始化需要DOM的第三方插件
- 添加事件监听器
- 执行依赖于DOM的操作
- 注意:不保证所有子组件也都一起被挂载
-
beforeUpdate:
- 数据更新前调用
- 发生在虚拟DOM重新渲染和打补丁之前
- 可以在此钩子中进一步更改状态,不会触发附加的重渲染过程
-
updated:
- 数据更新后调用
- 虚拟DOM已重新渲染和打补丁
- 避免在该钩子函数中修改数据,否则可能导致无限循环
- 适合执行依赖于DOM更新的操作
-
beforeDestroy:
- 实例销毁前调用
- 此时实例仍然完全可用
- 常用于:
- 清除定时器
- 取消事件监听
- 清理第三方插件实例
-
destroyed:
- 实例销毁后调用
- 所有指令都已解绑,所有事件监听器都已移除
- 所有子实例也都被销毁
生命周期使用示例
javascript
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
beforeCreate() {
console.log('beforeCreate: 实例刚刚创建');
},
created() {
console.log('created: 实例创建完成');
// 可以在这里发起AJAX请求获取初始数据
this.fetchData();
},
beforeMount() {
console.log('beforeMount: 模板编译完成,即将挂载');
},
mounted() {
console.log('mounted: 实例已挂载到DOM');
// 可以在这里初始化需要DOM的插件
this.initThirdPartyPlugin();
},
beforeUpdate() {
console.log('beforeUpdate: 数据即将更新');
},
updated() {
console.log('updated: 数据已更新完成');
},
beforeDestroy() {
console.log('beforeDestroy: 实例即将销毁');
// 清除定时器
clearInterval(this.timer);
},
destroyed() {
console.log('destroyed: 实例已销毁');
},
methods: {
fetchData() {
// 模拟异步数据获取
setTimeout(() => {
this.message = 'Data loaded!';
}, 1000);
},
initThirdPartyPlugin() {
// 初始化第三方插件
console.log('初始化第三方插件');
}
}
});
四、Vue 生态系统
4.1 Vue Router
Vue Router 是 Vue 官方提供的路由管理库,专门用于构建单页应用程序(SPA)。它通过将 URL 映射到对应的组件,实现了页面的无刷新切换,提供了流畅的用户体验。Vue Router 支持多种高级功能,如动态路由匹配、嵌套路由视图、路由参数传递等,是现代前端开发中不可或缺的工具。
4.1.1 基本使用
安装 Vue Router
在项目中使用 npm 或 yarn 安装 Vue Router:
bash
npm install vue-router --save
# 或
yarn add vue-router
创建路由实例
创建一个 router.js 文件来配置路由:
javascript
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './components/Home.vue';
import About from './components/About.vue';
import NotFound from './components/NotFound.vue';
// 使用 VueRouter 插件
Vue.use(VueRouter);
// 定义路由配置
const routes = [
{
path: '/',
component: Home,
name: 'Home' // 命名路由,便于编程式导航
},
{
path: '/about',
component: About,
name: 'About'
},
{
path: '*', // 404 页面匹配
component: NotFound
}
];
// 创建路由实例
const router = new VueRouter({
mode: 'history', // 使用 HTML5 history 模式,去除 URL 中的 #
routes,
scrollBehavior(to, from, savedPosition) {
// 路由切换时的滚动行为控制
if (savedPosition) {
return savedPosition;
} else {
return { x: 0, y: 0 };
}
}
});
export default router;
在 Vue 实例中集成路由
在 main.js 中引入并使用路由:
javascript
import Vue from 'vue';
import App from './App.vue';
import router from './router'; // 导入路由配置
new Vue({
router, // 注入路由实例
render: h => h(App)
}).$mount('#app');
模板中使用路由
在 App.vue 或任何组件模板中使用路由相关组件:
html
<template>
<div id="app">
<nav>
<!-- 使用 router-link 进行导航 -->
<router-link to="/" exact>首页</router-link> |
<router-link :to="{ name: 'About' }">关于我们</router-link>
</nav>
<!-- 路由匹配的组件将渲染在这里 -->
<router-view></router-view>
</div>
</template>
4.1.2 高级特性
动态路由
动态路由允许我们根据参数动态匹配路由:
javascript
// 路由配置
{
path: '/user/:userId',
component: User,
props: true // 将路由参数作为 props 传递给组件
}
// 在组件中获取参数
// 通过 $route.params.userId
// 或通过 props: ['userId']
嵌套路由
嵌套路由允许我们在组件内部再嵌套路由视图:
javascript
const routes = [
{
path: '/user/:id',
component: User,
children: [
{
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
]
编程式导航
除了使用 <router-link>
,还可以通过 JavaScript 进行导航:
javascript
// 基本导航
this.$router.push('/about')
this.$router.push({ name: 'About' })
this.$router.replace('/login') // 替换当前历史记录
// 带参数的导航
this.$router.push({
name: 'User',
params: { userId: 123 }
})
// 导航守卫
router.beforeEach((to, from, next) => {
// 全局前置守卫
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else {
next()
}
})
4.2 Vuex
Vuex 是 Vue 的官方状态管理库,用于集中管理应用中多个组件共享的状态。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
4.2.1 核心概念
State
State 是 Vuex 存储应用状态的地方,类似于组件的 data 选项:
javascript
state: {
user: null,
isLoading: false,
todos: []
}
Getters
Getters 用于从 state 中派生出新的状态,类似于计算属性:
javascript
getters: {
completedTodos: state => {
return state.todos.filter(todo => todo.completed)
},
activeTodosCount: (state, getters) => {
return state.todos.length - getters.completedTodos.length
}
}
Mutations
Mutations 是修改 state 的唯一方式,必须是同步函数:
javascript
mutations: {
ADD_TODO(state, todo) {
state.todos.push(todo)
},
TOGGLE_TODO(state, id) {
const todo = state.todos.find(t => t.id === id)
if (todo) {
todo.completed = !todo.completed
}
}
}
Actions
Actions 用于处理异步操作,可以包含任意异步操作:
javascript
actions: {
fetchTodos({ commit }) {
return api.fetchTodos().then(response => {
commit('SET_TODOS', response.data)
})
},
addTodoAsync({ commit }, todo) {
setTimeout(() => {
commit('ADD_TODO', todo)
}, 1000)
}
}
Modules
Modules 允许我们将 store 分割成模块:
javascript
const userModule = {
state: { user: null },
mutations: { SET_USER(state, user) { state.user = user } },
actions: { login({ commit }, credentials) { /* ... */ } }
}
const store = new Vuex.Store({
modules: {
user: userModule,
products: productsModule
}
})
4.2.2 基本使用
安装 Vuex
bash
npm install vuex --save
# 或
yarn add vuex
创建 Vuex Store
store/index.js:
javascript
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
user: null
},
getters: {
doubleCount: state => state.count * 2,
isAuthenticated: state => !!state.user
},
mutations: {
INCREMENT(state) {
state.count++
},
SET_USER(state, user) {
state.user = user
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('INCREMENT')
}, 1000)
},
login({ commit }, credentials) {
return authService.login(credentials)
.then(user => commit('SET_USER', user))
}
}
})
在 Vue 实例中使用
main.js:
javascript
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
在组件中使用
javascript
// 访问 state
computed: {
count() {
return this.$store.state.count
},
// 使用 mapState 辅助函数
...mapState(['count', 'user'])
}
// 调用 mutations
methods: {
increment() {
this.$store.commit('INCREMENT')
},
// 使用 mapMutations 辅助函数
...mapMutations(['INCREMENT'])
}
// 调用 actions
methods: {
incrementAsync() {
this.$store.dispatch('incrementAsync')
},
// 使用 mapActions 辅助函数
...mapActions(['incrementAsync'])
}
4.3 Vue CLI
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供了项目脚手架、开发服务器、构建工具和各种插件支持。
4.3.1 安装与使用
安装 Vue CLI
bash
npm install -g @vue/cli
# 或
yarn global add @vue/cli
创建新项目
bash
vue create my-project
交互式项目创建过程:
- 选择预设配置(默认或手动)
- 选择需要的特性(Babel, Router, Vuex, CSS 预处理器等)
- 选择 Vue 版本(2.x 或 3.x)
- 选择路由的 history 模式
- 选择 CSS 预处理器(Sass, Less, Stylus)
- 选择代码风格配置(ESLint + Standard/Prettier)
- 选择何时进行代码检查(保存时或提交时)
- 选择测试解决方案(Jest, Mocha 等)
- 选择配置文件位置(单独文件或 package.json)
项目命令
bash
# 启动开发服务器
npm run serve
# 构建生产版本
npm run build
# 运行测试
npm run test
# 检查并修复代码
npm run lint
4.3.2 项目结构
典型 Vue CLI 项目结构:
my-project/
├── public/ # 静态资源目录
│ ├── index.html # 主 HTML 文件
│ └── favicon.ico # 网站图标
├── src/ # 源代码目录
│ ├── assets/ # 静态资源(图片、字体等)
│ ├── components/ # 公共组件
│ ├── views/ # 页面级组件
│ ├── router/ # 路由配置
│ │ └── index.js
│ ├── store/ # Vuex 状态管理
│ │ └── index.js
│ ├── styles/ # 全局样式
│ ├── utils/ # 工具函数
│ ├── App.vue # 根组件
│ └── main.js # 应用入口文件
├── .env # 环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── vue.config.js # Vue CLI 配置
└── package.json # 项目配置和依赖
自定义配置
vue.config.js:
javascript
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/'
: '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV === 'development',
productionSourceMap: false,
devServer: {
port: 8080,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
},
chainWebpack: config => {
// 添加自定义 webpack 配置
}
}
五、Vue 3.x 新特性
5.1 组合式 API(Composition API)
组合式 API 是 Vue 3.x 的核心新特性之一,它改变了传统选项式 API 的组织方式,允许开发者按照逻辑功能而非选项(如 data、methods、computed)来组织代码。这种架构方式特别适合大型项目,能够显著提高代码的可维护性和可复用性。
实现方式
组合式 API 主要通过setup()
函数实现,该函数在组件创建前调用,其返回的对象中的属性和方法可以在模板中直接使用。这种设计使得相关逻辑可以更好地聚合在一起,而不是分散在不同的选项块中。
示例代码
javascript
import { ref, computed } from 'vue';
export default {
setup() {
// 定义响应式数据
const count = ref(0);
// 定义计算属性
const doubleCount = computed(() => count.value * 2);
// 定义方法
const increment = () => {
count.value++;
};
// 定义一个重置功能
const reset = () => {
count.value = 0;
};
// 返回模板可用的属性和方法
return {
count,
doubleCount,
increment,
reset
};
}
};
实际应用场景
- 表单处理:可以将表单验证、提交逻辑封装在一个组合函数中
- 数据获取:将API调用、加载状态管理、错误处理逻辑集中管理
- 复杂业务逻辑:将相关操作聚合在一起,提高代码可读性
5.2 更高效的虚拟 DOM
Vue 3.x 对虚拟 DOM 实现进行了全面重写,带来了显著的性能提升:
优化措施
- 静态节点提升:识别并提升静态节点,避免不必要的重渲染
- 补丁标记:为动态节点添加标记,减少diff时需要比较的范围
- 缓存事件处理函数:避免不必要的重新绑定
- 更高效的渲染函数生成:通过编译时优化生成更精简的代码
性能对比
在典型的中型应用测试中,Vue 3.x 的虚拟DOM操作速度比Vue 2.x快约2倍,内存占用减少约50%。
实际影响
- 列表渲染性能提升30-50%
- 组件初始化和更新速度大幅提高
- 内存占用更少,适合大型单页应用
5.3 更好的 TypeScript 支持
Vue 3.x 从架构层面优化了对TypeScript的支持:
改进点
- 代码库完全使用TypeScript重写
- 提供了完整的类型定义
- 组合式API天然支持类型推断
- 模板中的表达式也支持类型检查
类型支持示例
typescript
import { defineComponent, ref } from 'vue';
interface User {
id: number;
name: string;
email: string;
}
export default defineComponent({
setup() {
// 具有完整类型推断的响应式数据
const user = ref<User>({
id: 1,
name: 'John Doe',
email: 'john@example.com'
});
// 类型安全的更新方法
const updateName = (newName: string) => {
user.value.name = newName;
};
return {
user,
updateName
};
}
});
5.4 其他重要新特性
Teleport
解决组件在DOM结构中位置受限的问题,典型应用场景包括:
- 模态对话框
- 通知提示
- 全局加载指示器
示例:
html
<teleport to="body">
<div class="modal">
<!-- 模态框内容 -->
</div>
</teleport>
Suspense
处理异步组件加载状态,提供更好的用户体验:
html
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
Emits 选项
明确声明组件事件,提高代码可维护性:
javascript
export default {
emits: ['submit', 'cancel'],
methods: {
handleSubmit() {
this.$emit('submit', formData);
}
}
}