核心概念

  • 模块化:代码拆分与复用
  • 组件化: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

特性computedwatchmethods
缓存✅ 有缓存❌ 无缓存❌ 无缓存
使用场景一个数据受多个数据影响一个数据影响多个数据需要执行操作
返回值必须返回值无需返回值无需返回值
异步❌ 不支持异步✅ 支持异步✅ 支持异步

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-showv-if
实现方式CSS display: none添加/删除 DOM
初始化成本高(全部渲染)低(按需渲染)
切换成本低(仅 CSS)高(DOM 操作)
生命周期不触发生命周期触发生命周期

使用建议

  • 频繁切换:使用 v-show
  • 条件很少改变:使用 v-if

5.2 v-if vs v-for

优先级问题

  • Vue2v-for 优先级高于 v-if
  • Vue3v-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 核心区别对比

特性Vue2Vue3
API 风格Options APIComposition API
响应式原理Object.definePropertyProxy
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 优化建议

性能优化清单

  1. 合理使用 v-show vs v-if
  2. 使用 v-once 只渲染一次
  3. v-for 必须使用 key
  4. 使用 computed 缓存计算结果
  5. 组件懒加载
  6. 长列表虚拟滚动
  7. 冻结数据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)

相关链接