核心概念
- 模块化:代码拆分与复用
- 组件化:UI 元素封装
- 响应式:数据驱动视图
一、基础概念
1.1 什么是模块化?
定义 把一些可复用的代码,抽离为单个的模块,便于项目的维护和开发
模块化的好处:
- 避免全局变量污染
- 提高代码复用性
- 便于维护和测试
1.2 什么是组件化?
定义 把一些可复用的 UI 元素,抽离为单独的组件,便于项目的维护和开发
组件化的优势:
- 提高开发效率
- 便于团队协作
- 降低维护成本
1.3 什么是虚拟 DOM?
核心概念 虚拟 DOM 是对真实 DOM 的补充和增强(抽象),因为在渲染过程中 Vue 需要添加一些属性或者标记,方便内部处理渲染。
虚拟 DOM 的优势:
- 减少真实 DOM 操作
- 提升渲染性能
- 跨平台渲染能力
二、组件与数据
2.1 Vue 组件中 data 为什么必须是函数?
原因分析 Vue 组件可能会有多个实例,采用函数返回一个全新 data 形式,使每个实例对象的数据不会受到其他实例对象数据的污染。
// ❌ 错误:对象形式(所有实例共享同一数据)
const Component = {
data: {
count: 0
}
};
// ✅ 正确:函数形式(每个实例独立数据)
const Component = {
data() {
return {
count: 0
};
}
};2.2 keep-alive 的作用
缓存组件
keep-alive可以保存组件的状态,避免重复渲染。
<template>
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
</template>作用:
- 缓存组件实例
- 保留组件状态
- 避免重复渲染
2.3 key 的作用
节点标识 key 用于标识节点的唯一性,优化 diff 算法性能。
<template>
<!-- ✅ 推荐:使用唯一 ID 作为 key -->
<div v-for="item in list" :key="item.id">{{ item.name }}</div>
<!-- ❌ 避免:使用 index 作为 key -->
<div v-for="(item, index) in list" :key="index">{{ item.name }}</div>
</template>2.4 为什么不建议用 index 作为 key?
性能问题 当数组节点移动、新增或删除时,虚拟 DOM 可能会重新分配后续下标。
// 示例:数组在索引 1 处插入新元素
list = [
{ id: 1, name: 'A' }, // key: 0
{ id: 2, name: 'B' }, // key: 1
{ id: 3, name: 'C' } // key: 2
];
// 插入新元素后
list = [
{ id: 1, name: 'A' }, // key: 0
{ id: 4, name: 'D' }, // key: 1(新增)
{ id: 2, name: 'B' }, // key: 2(原 key:1)
{ id: 3, name: 'C' } // key: 3(原 key:2)
];
// 使用 index 作为 key,会导致 B 和 C 强制更新
// 使用 id 作为 key,只有 D 会新增,A 不变三、组件通信
3.1 通信方式对比
| 方式 | 适用场景 | 说明 |
|---|---|---|
props / $emit | 父子组件 | 父传子用 props,子传父用 $emit |
ref / $refs | 父子组件 | 父组件直接调用子组件方法 |
$parent / $children | 父子组件 | 直接访问父/子组件实例 |
$attrs / $listeners | 隔代组件 | 传递属性和事件监听器 |
provide / inject | 隔代组件 | 依赖注入,跨层级传递 |
EventBus | 跨组件 | 事件总线(Vue3 推荐使用 mitt) |
Vuex / Pinia | 跨组件 | 状态管理 |
localStorage | 跨组件 | 本地存储 |
3.2 props / $emit
<!-- 父组件 -->
<template>
<Child :message="msg" @update="handleUpdate" />
</template>
<!-- 子组件 -->
<template>
<button @click="$emit('update', newValue)">更新</button>
</template>
<script setup>
const props = defineProps(['message']);
const emit = defineEmits(['update']);
</script>3.3 provide / inject
// 祖先组件
import { provide } from 'vue';
provide('userInfo', { name: '张三' });
// 后代组件
import { inject } from 'vue';
const userInfo = inject('userInfo');
console.log(userInfo.name); // "张三"3.4 EventBus(Vue2)
// 创建事件总线
Vue.prototype.$bus = new Vue();
// 发送事件
this.$bus.$emit('event-name', data);
// 监听事件
this.$bus.$on('event-name', (data) => {
console.log(data);
});
// 销毁事件
this.$bus.$off('event-name');Vue3 替代方案 Vue3 中推荐使用
mitt库替代 EventBus
四、计算属性与监听器
4.1 computed vs watch vs methods
| 特性 | computed | watch | methods |
|---|---|---|---|
| 缓存 | ✅ 有缓存 | ❌ 无缓存 | ❌ 无缓存 |
| 使用场景 | 一个数据受多个数据影响 | 一个数据影响多个数据 | 需要执行操作 |
| 返回值 | 必须返回值 | 无需返回值 | 无需返回值 |
| 异步 | ❌ 不支持异步 | ✅ 支持异步 | ✅ 支持异步 |
4.2 computed 示例
import { computed } from 'vue';
const firstName = ref('张');
const lastName = ref('三');
// 计算属性(有缓存)
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value;
});
// 可写计算属性
const fullName = computed({
get() {
return firstName.value + ' ' + lastName.value;
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ');
}
});4.3 watch 示例
import { watch, ref } from 'vue';
const count = ref(0);
// 基本监听
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
// 立即执行
watch(count, (val) => {
console.log('count:', val);
}, { immediate: true });
// 深度监听
const user = ref({ name: '张三' });
watch(user, (newVal) => {
console.log(newVal);
}, { deep: true });
// 监听多个源
watch([firstName, lastName], ([newFirst, newLast]) => {
console.log(newFirst, newLast);
});五、指令与模板
5.1 v-show vs v-if
| 特性 | v-show | v-if |
|---|---|---|
| 实现方式 | CSS display: none | 添加/删除 DOM |
| 初始化成本 | 高(全部渲染) | 低(按需渲染) |
| 切换成本 | 低(仅 CSS) | 高(DOM 操作) |
| 生命周期 | 不触发生命周期 | 触发生命周期 |
使用建议
- 频繁切换:使用
v-show- 条件很少改变:使用
v-if
5.2 v-if vs v-for
优先级问题
- Vue2:
v-for优先级高于v-if- Vue3:
v-if优先级高于v-for
<!-- ❌ 不推荐:永远不要在同一元素上使用 -->
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>
<!-- ✅ 推荐:使用计算属性 -->
<template v-for="user in activeUsers" :key="user.id">
<li>{{ user.name }}</li>
</template>
<script setup>
const activeUsers = computed(() => users.value.filter(u => u.isActive));
</script>六、Vue2 vs Vue3
6.1 核心区别对比
| 特性 | Vue2 | Vue3 |
|---|---|---|
| API 风格 | Options API | Composition API |
| 响应式原理 | Object.defineProperty | Proxy |
| TypeScript | 支持一般 | 原生支持 |
| Tree-shaking | 部分支持 | 完全支持 |
| Diff 算法 | 双端比较 | 静态标记 + 最长递增子序列 |
| 根节点 | 单根节点 | 多根节点 |
6.2 响应式原理升级
Vue2 的问题
- 数组无法通过下标修改
- 无法监听对象属性的新增和删除
- 需要使用
Vue.set/this.$set
// Vue2 的问题
this.list[0] = newValue; // ❌ 不会触发更新
this.obj.newProp = 'value'; // ❌ 不会触发更新
// 解决方案
this.$set(this.list, 0, newValue);
this.$set(this.obj, 'newProp', 'value');Vue3 的改进
- 使用
Proxy代理整个对象- 可以监听数组下标修改
- 可以监听对象属性新增/删除
- 支持 Map、Set、WeakMap、WeakSet
// Vue3 无问题
list.value[0] = newValue; // ✅ 会触发更新
obj.value.newProp = 'value'; // ✅ 会触发更新6.3 Composition API
核心优势
- 更好的 TypeScript 支持
- 没有
this,减少指向混乱- 更灵活的代码组织
- 更好的逻辑复用
// Vue2 Options API
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
},
mounted() {
console.log('mounted');
}
};
// Vue3 Composition API
import { ref, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
onMounted(() => {
console.log('mounted');
});
return {
count,
increment
};
}
};6.4 Teleport 传送门
<!-- 将内容传送到 body -->
<Teleport to="body">
<div class="modal">模态框内容</div>
</Teleport>七、性能优化
7.1 优化建议
性能优化清单
- 合理使用 v-show vs v-if
- 使用 v-once 只渲染一次
- v-for 必须使用 key
- 使用 computed 缓存计算结果
- 组件懒加载
- 长列表虚拟滚动
- 冻结数据(
Object.freeze())
7.2 组件懒加载
// 路由懒加载
const Home = () => import('./views/Home.vue');
// 异步组件
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(import('./Comp.vue'));
}, 1000);
});
});八、常见问题
8.1 mixin 的问题
不推荐使用 mixin
- 命名冲突:多个 mixin 可能有相同属性名
- 数据来源不清晰:难以追踪数据来源
- 隐式依赖:mixin 之间的依赖不明确
推荐替代方案:
- Composition API
- Composables
- Mixin Functions
8.2 数组变异方法
Vue2/3 中会触发视图更新的数组方法
| 方法 | 说明 |
|---|---|
push() | 尾部添加 |
pop() | 尾部删除 |
shift() | 头部删除 |
unshift() | 头部添加 |
splice() | 删除/插入 |
sort() | 排序 |
reverse() | 反转 |
fill() | 填充(Vue3) |