Vue-重新学习-20260407

4 阅读20分钟

Vue

一、Vue 基础与 MVVM 模式

1. Vue 核心特性与设计模式

问题:Vue 的核心特性有哪些?Vue 的特点是什么?Vue 与 React 的区别是什么?什么是 MVVM 模式?MVVM 的组成部分有哪些?Vue 如何实现 MVVM?

答案

Vue 核心特性

  1. 响应式数据绑定:数据变化自动更新视图
  2. 组件系统:可复用、可组合的 UI 组件
  3. 模板语法:声明式渲染,类似 HTML 的模板语法
  4. 虚拟 DOM:高效更新真实 DOM
  5. 单文件组件:.vue 文件封装模板、脚本、样式
  6. 过渡动画系统:内置过渡和动画支持
  7. 路由与状态管理:Vue Router、Vuex 官方库

Vue vs React 对比表

特性VueReact
模板语法HTML-based 模板JSX
学习曲线平缓,中文文档完善较陡峭
数据绑定双向数据绑定(v-model)单向数据流
状态管理Vuex(官方)Redux/MobX(社区)
组件通信props/emit、provide/inject、事件总线props、上下文、状态管理
生态系统官方维护路由、状态管理社区驱动,选择多样
构建工具Vue CLICreate React App
TypeScript良好支持优秀支持

MVVM 模式

  • Model:数据模型,包含业务数据和逻辑
  • View:视图层,用户界面展示
  • ViewModel:视图模型,连接 View 和 Model,负责数据绑定和命令绑定

Vue 的 MVVM 实现

  1. 数据劫持:通过 Object.defineProperty 或 Proxy 实现响应式
  2. 依赖收集:在 getter 中收集依赖(Watcher)
  3. 派发更新:在 setter 中通知依赖更新
  4. 编译模板:将模板编译为渲染函数
  5. 虚拟 DOM:生成虚拟节点,通过 Diff 算法更新真实 DOM

补充说明

  • Vue 采用渐进式框架设计,可根据需求逐步引入功能
  • MVVM 模式实现数据与视图的分离,提高代码可维护性
  • Vue 3 使用 Proxy 替代 Object.defineProperty,性能更好

二、Vue 实例与生命周期

2. Vue 实例创建与生命周期钩子

问题:什么是 Vue 实例?如何创建 Vue 实例?Vue 实例的选项有哪些?什么是 Vue 生命周期?生命周期钩子有哪些?父子组件生命周期执行顺序是什么?

答案

Vue 实例创建

// 创建 Vue 实例
const app = new Vue({
  el: '#app',           // 挂载元素
  data: {               // 响应式数据
    message: 'Hello Vue!'
  },
  methods: {            // 方法
    sayHello() {
      console.log(this.message);
    }
  },
  computed: {           // 计算属性
    reversedMessage() {
      return this.message.split('').reverse().join('');
    }
  },
  watch: {              // 侦听器
    message(newVal, oldVal) {
      console.log('消息变化:', oldVal, '→', newVal);
    }
  }
});

Vue 实例核心选项

  • data:响应式数据对象
  • methods:方法集合
  • computed:计算属性(基于依赖缓存)
  • watch:侦听属性变化
  • props:接收父组件传递的数据
  • components:注册局部组件
  • directives:注册局部指令
  • filters:注册过滤器
  • mixins:混入对象
  • extends:继承其他组件选项

Vue 生命周期钩子

  1. 创建阶段

    • beforeCreate:实例初始化,数据观测和事件配置之前
    • created:实例创建完成,数据观测、属性和方法已配置,但 DOM 未生成
  2. 挂载阶段

    • beforeMount:模板编译/挂载之前
    • mounted:实例已挂载到 DOM,可访问 DOM 元素
  3. 更新阶段

    • beforeUpdate:数据更新,DOM 重新渲染之前
    • updated:数据更新,DOM 重新渲染完成
  4. 销毁阶段

    • beforeDestroy:实例销毁之前,可进行清理操作
    • destroyed:实例销毁完成,所有指令解绑,事件监听器移除

父子组件生命周期执行顺序

父 beforeCreate → 父 created → 父 beforeMount
→ 子 beforeCreate → 子 created → 子 beforeMount → 子 mounted
→ 父 mounted

更新时顺序

父 beforeUpdate
→ 子 beforeUpdate → 子 updated
→ 父 updated

销毁时顺序

父 beforeDestroy
→ 子 beforeDestroy → 子 destroyed
→ 父 destroyed

生命周期钩子应用场景

  • created:发起异步请求、初始化数据
  • mounted:操作 DOM、初始化第三方库
  • beforeDestroy:清除定时器、解绑事件、取消请求
  • updated:数据更新后执行 DOM 操作

三、Vue 模板语法与指令

3. 模板语法与常用指令

问题:什么是 Vue 模板语法?什么是插值表达式?什么是指令?常用的指令有哪些?v-bind、v-on、v-model、v-if、v-show、v-for 指令如何使用?v-if 与 v-show 的区别是什么?

答案

模板语法

<!-- 插值表达式 -->
<div>{{ message }}</div>

<!-- JavaScript 表达式 -->
<div>{{ message.split('').reverse().join('') }}</div>
<div>{{ ok ? 'YES' : 'NO' }}</div>

<!-- 原始 HTML -->
<div v-html="rawHtml"></div>

<!-- 属性绑定 -->
<div v-bind:id="dynamicId"></div>
<div :id="dynamicId"></div>  <!-- 简写 -->

常用指令

  1. v-bind:动态绑定属性
<img :src="imageSrc" :alt="imageAlt">
<button :disabled="isDisabled">按钮</button>
  1. v-on:绑定事件
<button @click="handleClick">点击</button>  <!-- 简写 -->
<button v-on:click="handleClick">点击</button>
<input @input="handleInput">
  1. v-model:双向数据绑定(表单元素)
<input v-model="message" placeholder="输入">
<textarea v-model="text"></textarea>
<select v-model="selected">
  <option value="A">选项A</option>
  <option value="B">选项B</option>
</select>
  1. v-if / v-else / v-else-if:条件渲染
<div v-if="type === 'A'">A</div>
<div v-else-if="type === 'B'">B</div>
<div v-else>C</div>
  1. v-show:条件显示(通过 display 控制)
<div v-show="isVisible">显示内容</div>
  1. v-for:列表渲染
<li v-for="(item, index) in items" :key="item.id">
  {{ index }} - {{ item.name }}
</li>
<li v-for="(value, key) in object">
  {{ key }}: {{ value }}
</li>

v-if vs v-show 对比

特性v-ifv-show
渲染方式条件为 false 时不渲染 DOM始终渲染,通过 display 控制显示
切换开销高(销毁/重建组件)低(仅切换 CSS 属性)
初始渲染条件为 false 时不渲染无论条件都渲染
适用场景条件很少改变,切换不频繁频繁切换显示/隐藏
与 v-for优先级高于 v-for优先级低于 v-for

指令修饰符

<!-- 事件修饰符 -->
<button @click.stop="handleClick">阻止冒泡</button>
<button @click.prevent="handleSubmit">阻止默认行为</button>
<button @click.once="handleOnce">只触发一次</button>

<!-- 按键修饰符 -->
<input @keyup.enter="submit">
<input @keyup.13="submit">

<!-- 表单修饰符 -->
<input v-model.lazy="message">    <!-- 输入完成后更新 -->
<input v-model.number="age">      <!-- 转为数字 -->
<input v-model.trim="username">   <!-- 去除首尾空格 -->

自定义指令

// 全局注册
Vue.directive('focus', {
  inserted: function(el) {
    el.focus();
  }
});

// 局部注册
directives: {
  focus: {
    inserted: function(el) {
      el.focus();
    }
  }
}

// 使用
<input v-focus>

补充说明

  • 插值表达式支持 JavaScript 表达式,但不支持语句和控制流
  • 指令的值可以是表达式,v-for 需要提供唯一的 key
  • v-model 是语法糖,等价于 :value="message" @input="message = $event.target.value"

四、计算属性、侦听器与方法

4. 计算属性与侦听器的区别与应用

问题:什么是计算属性?计算属性与方法的区别是什么?计算属性与侦听器的区别是什么?计算属性的缓存机制是什么?什么是侦听器?侦听器的深度监听如何实现?

答案

计算属性

  • 基于依赖的响应式数据进行计算
  • 具有缓存机制,依赖不变时直接返回缓存值
  • 声明为函数,但作为属性访问
computed: {
  // 基本用法
  reversedMessage() {
    return this.message.split('').reverse().join('');
  },
  
  // 计算属性 setter(可写计算属性)
  fullName: {
    get() {
      return this.firstName + ' ' + this.lastName;
    },
    set(newValue) {
      const names = newValue.split(' ');
      this.firstName = names[0];
      this.lastName = names[1] || '';
    }
  }
}

计算属性 vs 方法

  • 计算属性:基于依赖缓存,依赖不变时不会重新计算
  • 方法:每次调用都会重新计算,无缓存
// 计算属性(缓存)
computed: {
  now() {
    return Date.now();  // ❌ 错误:不依赖响应式数据,不会更新
  },
  reversedMessage() {
    return this.message.split('').reverse().join('');  // ✅ 正确
  }
}

// 方法(无缓存)
methods: {
  getReversedMessage() {
    return this.message.split('').reverse().join('');
  }
}

// 使用
<div>{{ reversedMessage }}</div>  <!-- 计算属性 -->
<div>{{ getReversedMessage() }}</div>  <!-- 方法 -->

侦听器

  • 观察响应式数据的变化,执行异步或开销较大的操作
  • 适用于数据变化时执行副作用操作
watch: {
  // 基本用法
  message(newVal, oldVal) {
    console.log('消息变化:', oldVal, '→', newVal);
  },
  
  // 深度监听对象
  deepObject: {
    handler(newVal, oldVal) {
      console.log('对象变化');
    },
    deep: true,  // 深度监听
    immediate: true  // 立即执行一次
  },
  
  // 监听对象属性
  'user.name': function(newVal, oldVal) {
    console.log('用户名变化');
  },
  
  // 监听数组
  items: {
    handler(newVal, oldVal) {
      console.log('数组变化');
    },
    deep: true  // 数组需要深度监听
  }
}

计算属性 vs 侦听器对比表

特性计算属性侦听器
用途计算派生数据响应数据变化执行操作
缓存有缓存,依赖不变不重新计算无缓存,每次变化都执行
同步/异步必须同步返回结果可执行异步操作
代码组织声明式,关注结果命令式,关注过程
适用场景模板中使用的派生数据数据变化时执行副作用(请求、DOM操作)

深度监听实现

watch: {
  // 方法1:使用 deep 选项
  deepObject: {
    handler() { /* ... */ },
    deep: true
  },
  
  // 方法2:监听具体属性路径
  'deepObject.nested.property': function() { /* ... */ },
  
  // 方法3:使用 $watch API(编程式)
  mounted() {
    this.$watch(
      () => this.deepObject,
      (newVal, oldVal) => {
        console.log('对象变化');
      },
      { deep: true }
    );
  }
}

$watch API

// 组件内使用
this.$watch(
  'message',  // 监听表达式
  function(newVal, oldVal) {
    console.log('消息变化');
  },
  { deep: true, immediate: true }
);

// 取消监听
const unwatch = this.$watch('message', handler);
unwatch();  // 取消监听

补充说明

  1. 计算属性适用场景

    • 复杂逻辑计算
    • 过滤、排序列表数据
    • 格式化显示数据
    • 多个数据组合
  2. 侦听器适用场景

    • 数据变化时发起异步请求
    • 执行复杂操作(超过一个表达式)
    • 数据变化时操作 DOM
    • 路由参数变化时重新获取数据
  3. 性能优化

    • 优先使用计算属性,利用缓存机制
    • 避免在计算属性中执行异步操作或副作用
    • 深度监听消耗性能,尽量监听具体属性

五、Vue 组件系统

5. 组件注册、props 与插槽

问题:什么是组件?组件的注册方法有哪些?全局组件与局部组件的区别是什么?组件的 props 如何使用?props 的验证方法有哪些?组件的插槽如何使用?作用域插槽如何使用?

答案

组件定义

// 1. 全局组件
Vue.component('my-component', {
  template: '<div>全局组件</div>'
});

// 2. 局部组件
const MyComponent = {
  template: '<div>局部组件</div>'
};

new Vue({
  el: '#app',
  components: {
    'my-component': MyComponent
  }
});

// 3. 单文件组件(.vue)
// MyComponent.vue
<template>
  <div>{{ message }}</div>
</template>

<script>
export default {
  props: ['message'],
  data() {
    return { localData: 'local' };
  }
}
</script>

<style scoped>
div { color: red; }
</style>

props 使用

// 父组件
<child-component :title="pageTitle" :count="itemCount"></child-component>

// 子组件
export default {
  props: ['title', 'count'],  // 数组形式
  
  // 对象形式(推荐)
  props: {
    title: String,
    count: {
      type: Number,
      default: 0,
      required: true,
      validator: function(value) {
        return value >= 0;
      }
    }
  }
}

props 类型验证

  • StringNumberBooleanArrayObjectDateFunctionSymbol
  • 自定义构造函数:验证是否是通过 new 创建的实例

插槽使用

<!-- 子组件 -->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>  <!-- 默认插槽 -->
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

<!-- 父组件 -->
<my-component>
  <template v-slot:header>
    <h1>标题</h1>
  </template>
  
  <p>主要内容</p>
  
  <template #footer>  <!-- 简写 -->
    <p>页脚</p>
  </template>
</my-component>

作用域插槽

<!-- 子组件 -->
<template>
  <ul>
    <li v-for="(item, index) in items">
      <slot :item="item" :index="index"></slot>
    </li>
  </ul>
</template>

<!-- 父组件 -->
<my-list :items="userList">
  <template v-slot:default="slotProps">
    <span class="user">{{ slotProps.item.name }}</span>
    <span class="index">#{{ slotProps.index }}</span>
  </template>
</my-list>

<!-- 解构写法 -->
<my-list :items="userList">
  <template v-slot:default="{ item, index }">
    <span>{{ item.name }} ({{ index }})</span>
  </template>
</my-list>

动态组件

<component :is="currentComponent"></component>

<keep-alive>
  <component :is="currentComponent"></component>
</keep-alive>

异步组件

// 1. 工厂函数
const AsyncComponent = () => ({
  component: import('./MyComponent.vue'),
  loading: LoadingComponent,
  error: ErrorComponent,
  delay: 200,
  timeout: 3000
});

// 2. 简化写法
const AsyncComponent = () => import('./MyComponent.vue');

// 3. Vue 3 语法
defineAsyncComponent(() => import('./MyComponent.vue'));

组件注册方式

  1. 全局注册Vue.component(),在任何地方可用
  2. 局部注册:组件 options 中的 components 属性
  3. 自动注册:通过 require.context 批量注册

补充说明

  • 组件名推荐使用 PascalCase(MyComponent)或 kebab-case(my-component)
  • props 是单向数据流,子组件不应直接修改,应通过事件通知父组件修改
  • 插槽提供内容分发机制,增强组件灵活性
  • 动态组件配合 keep-alive 可缓存组件状态

六、组件通信

6. 组件通信方法与使用场景

问题:组件通信的方法有哪些?父子组件通信的方法有哪些?兄弟组件通信的方法有哪些?跨级组件通信的方法有哪些?provide/inject、attrs/attrs/listeners、parent/parent/children、$refs 如何使用?事件总线如何使用?

答案

组件通信方法分类

通信方式适用场景特点
props/emit父子组件通信官方推荐,单向数据流
$refs父访问子直接访问子组件实例
parent/parent/children父子组件访问紧耦合,不推荐
attrs/attrs/listeners跨级属性/事件传递Vue 2.4+,祖孙组件通信
provide/inject跨级组件通信祖先提供,后代注入
事件总线任意组件通信简单场景,复杂时难维护
Vuex复杂应用状态管理集中式状态管理
slot scope父向子传递数据作用域插槽
全局事件总线非父子组件通信小型应用适用

父子组件通信(props/emit)

// 父组件
<child-component 
  :title="parentTitle"
  @update-title="handleUpdate"
></child-component>

// 子组件
export default {
  props: ['title'],
  methods: {
    updateTitle() {
      this.$emit('update-title', '新标题');
    }
  }
}

$refs 访问子组件

<!-- 父组件 -->
<child-component ref="childRef"></child-component>
<button @click="callChildMethod">调用子组件方法</button>

<script>
export default {
  methods: {
    callChildMethod() {
      this.$refs.childRef.childMethod();
    }
  }
}
</script>

attrsattrs 与 listeners

<!-- 父组件 -->
<child-component 
  title="标题" 
  desc="描述"
  @click="handleClick"
  @custom="handleCustom"
></child-component>

<!-- 子组件 -->
<template>
  <div>
    <!-- 透传所有属性(除 class、style) -->
    <grand-child v-bind="$attrs" v-on="$listeners"></grand-child>
  </div>
</template>

<script>
export default {
  inheritAttrs: false,  // 不自动绑定到根元素
  mounted() {
    console.log(this.$attrs);  // { title: "标题", desc: "描述" }
    console.log(this.$listeners);  // { click: function, custom: function }
  }
}
</script>

provide/inject

// 祖先组件
export default {
  provide() {
    return {
      theme: this.theme,
      updateTheme: this.updateTheme
    };
  },
  data() {
    return { theme: 'dark' };
  },
  methods: {
    updateTheme(newTheme) {
      this.theme = newTheme;
    }
  }
}

// 后代组件
export default {
  inject: ['theme', 'updateTheme'],
  methods: {
    changeTheme() {
      this.updateTheme('light');
    }
  }
}

事件总线

// event-bus.js
import Vue from 'vue';
export const EventBus = new Vue();

// 发送事件组件
EventBus.$emit('user-logged-in', userData);

// 接收事件组件
EventBus.$on('user-logged-in', (userData) => {
  console.log('用户登录:', userData);
});

// 组件销毁前移除监听
beforeDestroy() {
  EventBus.$off('user-logged-in', this.handler);
}

兄弟组件通信

  1. 通过父组件中转:子组件 A → 父组件 → 子组件 B
  2. 事件总线:任意组件间通信
  3. Vuex:集中式状态管理

跨级组件通信

  1. provide/inject:祖先提供数据,后代注入使用
  2. attrs/attrs/listeners:属性/事件透传
  3. Vuex:全局状态管理

补充说明

  1. 通信方式选择原则

    • 父子组件:props/emit(优先)
    • 兄弟组件:通过共同父组件或 Vuex
    • 跨级组件:provide/inject 或 Vuex
    • 任意组件:Vuex(复杂应用)或事件总线(简单场景)
  2. 注意事项

    • 避免滥用 parent/parent/children,导致组件紧耦合
    • provide/inject 主要为高阶组件设计,普通业务慎用
    • 事件总线注意及时清理监听,避免内存泄漏
    • Vuex 适合中大型应用,小型应用可能增加复杂度
  3. 最佳实践

    • 优先使用 props/emit 进行父子通信
    • 复杂状态使用 Vuex 管理
    • 组件库开发使用 provide/inject、attrs/attrs/listeners
    • 及时清理事件监听,避免内存泄漏

七、Vue 响应式原理

7. 响应式数据与依赖追踪

问题:Vue 的响应式原理是什么?Object.defineProperty 如何实现响应式?数组的响应式如何实现?Vue.set 方法的作用是什么?响应式的局限性有哪些?依赖收集和派发更新的原理是什么?

答案

核心概念

  • 响应式:数据变化自动更新视图
  • 依赖收集:在 getter 中收集依赖(Watcher)
  • 派发更新:在 setter 中通知依赖更新

Object.defineProperty 实现响应式

function defineReactive(obj, key, val) {
  // 依赖管理器
  const dep = new Dep();
  
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 依赖收集
      if (Dep.target) {
        dep.addSub(Dep.target);
      }
      return val;
    },
    set(newVal) {
      if (newVal === val) return;
      val = newVal;
      // 派发更新
      dep.notify();
    }
  });
}

// 遍历对象所有属性
function observe(obj) {
  if (typeof obj !== 'object' || obj === null) return;
  
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key]);
  });
}

数组响应式实现

// 数组方法重写
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);

['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'].forEach(method => {
  const original = arrayProto[method];
  
  arrayMethods[method] = function(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    
    // 获取新增元素
    let inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break;
      case 'splice':
        inserted = args.slice(2);
        break;
    }
    
    // 对新元素进行响应式处理
    if (inserted) ob.observeArray(inserted);
    
    // 通知更新
    ob.dep.notify();
    return result;
  };
});

// 替换数组原型
if (Array.isArray(value)) {
  value.__proto__ = arrayMethods;
  this.observeArray(value);
}

Vue.set / this.$set

  • 解决对象新增属性或数组索引赋值非响应式的问题
  • 原理:调用 defineReactive 将新属性转为响应式,并触发更新
// 对象新增属性
Vue.set(this.someObject, 'newProp', 123);
this.$set(this.someObject, 'newProp', 123);

// 数组索引赋值
Vue.set(this.someArray, index, newValue);
this.someArray.splice(index, 1, newValue);  // 替代方案

响应式局限性

  1. 对象属性添加/删除:Vue 无法检测到,需使用 Vue.set
  2. 数组索引赋值arr[index] = value 无效,需使用 Vue.set 或 splice
  3. 数组长度修改arr.length = newLength 无效
  4. 嵌套对象深层次修改:需要深度监听或手动触发更新

依赖收集与派发更新流程

1. 初始化阶段:
   - 对 data 进行响应式处理
   - 编译模板,创建 Watcher

2. 依赖收集(触发 getter):
   - 渲染时读取数据,触发 getter
   - getter 中将当前 Watcher 添加到 Dep 中

3. 派发更新(触发 setter):
   - 数据变化触发 setter
   - setter 调用 dep.notify()
   - Dep 通知所有 Watcher 更新
   - Watcher 执行更新(重新渲染或回调)

4. 虚拟 DOM 与差异更新:
   - 重新执行渲染函数生成新 vnode
   - 与旧 vnode 进行 diff
   - 将差异应用到真实 DOM

Watcher 类型

  1. 渲染 Watcher:组件渲染函数,数据变化时触发重新渲染
  2. 计算属性 Watcher:计算属性依赖变化时重新计算
  3. 用户 Watcher:通过 watch 选项或 $watch 创建的侦听器

补充说明

  1. Vue 2 vs Vue 3 响应式

    • Vue 2:Object.defineProperty,需要遍历对象属性,数组需重写方法
    • Vue 3:Proxy,直接代理对象,支持数组索引和 length 修改
  2. 性能优化

    • 避免深度监听大型对象
    • 合理使用 computed 缓存
    • 避免频繁修改响应式数据
    • 使用 Object.freeze() 冻结不需要响应式的对象
  3. 开发注意事项

    • 初始化时声明所有响应式属性
    • 使用 Vue.set 添加新属性
    • 使用数组方法修改数组,而非索引赋值
    • 复杂嵌套对象考虑使用深拷贝或 immutable 数据

八、Vue Router

8. 路由配置与导航守卫

问题:什么是 Vue Router?路由的基本使用方法是什么?动态路由、嵌套路由如何使用?路由参数如何传递?编程式导航如何使用?路由守卫如何使用?hash 模式与 history 模式的区别是什么?

答案

Vue Router 基本配置

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './views/Home.vue';

Vue.use(VueRouter);

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home
  },
  {
    path: '/about',
    name: 'about',
    component: () => import('./views/About.vue')  // 懒加载
  },
  {
    path: '/user/:id',  // 动态路由
    name: 'user',
    component: () => import('./views/User.vue'),
    props: true  // 将路由参数作为 props 传递
  }
];

const router = new VueRouter({
  mode: 'history',  // 或 'hash'
  base: process.env.BASE_URL,
  routes
});

export default router;

动态路由与参数

// 路由配置
{
  path: '/user/:id',
  component: User,
  props: true
}

// 组件内访问参数
export default {
  props: ['id'],  // 通过 props 接收
  created() {
    console.log(this.id);  // 路由参数
    console.log(this.$route.params.id);  // 通过 $route 访问
  }
}

// 多参数
{
  path: '/user/:id/post/:postId',
  component: UserPost
}

嵌套路由

const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        path: '',  // 默认子路由
        component: UserProfile
      },
      {
        path: 'posts',
        component: UserPosts
      },
      {
        path: 'settings',
        component: UserSettings
      }
    ]
  }
];

// User.vue 模板
<template>
  <div class="user">
    <h2>用户 {{ $route.params.id }}</h2>
    <router-view></router-view>  <!-- 子路由出口 -->
  </div>
</template>

编程式导航

// 字符串路径
this.$router.push('/home');

// 对象
this.$router.push({ path: '/home' });
this.$router.push({ name: 'user', params: { id: '123' } });
this.$router.push({ path: '/user', query: { id: '123' } });

// 替换当前路由(不添加历史记录)
this.$router.replace('/home');

// 前进/后退
this.$router.go(1);   // 前进一步
this.$router.go(-1);  // 后退一步
this.$router.back();  // 后退

// 导航到新位置
this.$router.push('/home').catch(err => {
  // 忽略重复导航错误
  if (err.name !== 'NavigationDuplicated') {
    throw err;
  }
});

路由守卫

  1. 全局前置守卫
router.beforeEach((to, from, next) => {
  // to: 目标路由
  // from: 当前路由
  // next: 回调函数
  
  const isAuthenticated = checkAuth();
  
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!isAuthenticated) {
      next({ path: '/login', query: { redirect: to.fullPath } });
    } else {
      next();
    }
  } else {
    next();
  }
});
  1. 全局解析守卫
router.beforeResolve((to, from, next) => {
  // 在导航被确认之前,组件内守卫和异步路由组件被解析之后调用
  next();
});
  1. 全局后置钩子
router.afterEach((to, from) => {
  // 路由跳转完成后调用,无 next 参数
  document.title = to.meta.title || '默认标题';
});
  1. 路由独享守卫
const routes = [
  {
    path: '/admin',
    component: Admin,
    beforeEnter(to, from, next) {
      // 仅对该路由生效
      if (!isAdmin()) {
        next('/login');
      } else {
        next();
      }
    }
  }
];
  1. 组件内守卫
export default {
  beforeRouteEnter(to, from, next) {
    // 组件实例创建之前调用,不能访问 this
    next(vm => {
      // 通过回调访问组件实例
      console.log(vm);
    });
  },
  
  beforeRouteUpdate(to, from, next) {
    // 当前路由改变,组件复用时调用
    // 例如 /user/1 → /user/2,可以访问 this
    this.userId = to.params.id;
    next();
  },
  
  beforeRouteLeave(to, from, next) {
    // 离开该组件时调用
    const answer = window.confirm('确定离开吗?未保存的更改可能会丢失');
    if (answer) {
      next();
    } else {
      next(false);
    }
  }
}

hash 模式 vs history 模式

特性hash 模式history 模式
URL 显示带 # 号(如 #/home)无 # 号,正常 URL(如 /home)
服务器配置无需特殊配置需要配置支持(重定向到 index.html)
SEO不友好友好
兼容性支持所有浏览器需要 HTML5 History API
部署简单需服务器配置
原理监听 hashchange 事件监听 popstate 事件,使用 pushState/replaceState

路由懒加载

// 1. 动态 import(推荐)
const User = () => import('./views/User.vue');

// 2. 魔法注释(webpack)
const User = () => import(/* webpackChunkName: "user" */ './views/User.vue');

// 3. 分组(相同 chunk)
const User = () => import(/* webpackChunkName: "group-user" */ './views/User.vue');
const UserList = () => import(/* webpackChunkName: "group-user" */ './views/UserList.vue');

补充说明

  1. 路由元信息

    {
      path: '/admin',
      component: Admin,
      meta: {
        requiresAuth: true,
        title: '管理后台'
      }
    }
    
  2. 滚动行为

    const router = new VueRouter({
      scrollBehavior(to, from, savedPosition) {
        if (savedPosition) {
          return savedPosition;
        } else {
          return { x: 0, y: 0 };
        }
      }
    });
    
  3. 路由过渡

    <transition name="fade">
      <router-view></router-view>
    </transition>
    
  4. 导航错误处理

    router.onError((error) => {
      console.error('路由错误:', error);
    });
    

九、Vuex 状态管理

9. Vuex 核心概念与使用

问题:什么是 Vuex?Vuex 的核心概念有哪些?State、Getter、Mutation、Action、Module 如何使用?Vuex 的辅助函数有哪些?Vuex 的模块化方法是什么?Vuex 的持久化如何实现?

答案

Vuex 概念

  • 集中式状态管理,用于多个组件共享状态
  • 适用于中大型单页应用

Vuex 核心概念

  1. State:单一状态树,存储应用状态
  2. Getter:从 state 派生状态,类似计算属性
  3. Mutation:更改 state 的唯一方法,同步操作
  4. Action:提交 mutation,可包含异步操作
  5. Module:将 store 分割成模块

Vuex 基本结构

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++;
    },
    setUser(state, user) {
      state.user = user;
    }
  },
  
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    },
    fetchUser({ commit }) {
      return api.getUser().then(user => {
        commit('setUser', user);
      });
    }
  },
  
  modules: {
    cart: cartModule,
    products: productsModule
  }
});

State 访问

// 组件内访问
computed: {
  count() {
    return this.$store.state.count;
  },
  user() {
    return this.$store.state.user;
  }
}

// 使用 mapState 辅助函数
import { mapState } from 'vuex';

computed: {
  ...mapState(['count', 'user']),
  ...mapState({
    customCount: 'count',  // 别名
    currentUser: state => state.user
  })
}

Getter 使用

// 定义 getter
getters: {
  todosByStatus: (state) => (status) => {
    return state.todos.filter(todo => todo.status === status);
  }
}

// 组件内访问
computed: {
  doneTodos() {
    return this.$store.getters.doneTodos;
  },
  pendingTodos() {
    return this.$store.getters.todosByStatus('pending');
  },
  ...mapGetters(['doneTodos', 'pendingTodos'])
}

Mutation 使用

// 定义 mutation
mutations: {
  increment(state, payload) {
    state.count += payload.amount;
  }
}

// 提交 mutation
methods: {
  increment() {
    this.$store.commit('increment', { amount: 1 });
    
    // 对象风格
    this.$store.commit({
      type: 'increment',
      amount: 1
    });
  },
  ...mapMutations(['increment'])
}

Action 使用

// 定义 action
actions: {
  fetchUser({ commit, state, dispatch, getters }, payload) {
    return api.getUser(payload.userId)
      .then(user => {
        commit('setUser', user);
        return user;
      })
      .catch(error => {
        dispatch('handleError', error);
        throw error;
      });
  }
}

// 分发 action
methods: {
  loadUser() {
    this.$store.dispatch('fetchUser', { userId: 123 });
    
    // 返回 Promise
    this.$store.dispatch('fetchUser', { userId: 123 })
      .then(user => console.log('用户加载成功:', user));
  },
  ...mapActions(['fetchUser'])
}

Module 模块化

// user 模块
const userModule = {
  namespaced: true,  // 启用命名空间
  
  state: () => ({
    profile: null,
    token: ''
  }),
  
  getters: {
    fullName: state => {
      if (!state.profile) return '';
      return `${state.profile.firstName} ${state.profile.lastName}`;
    }
  },
  
  mutations: {
    SET_PROFILE(state, profile) {
      state.profile = profile;
    }
  },
  
  actions: {
    fetchProfile({ commit }) {
      return api.getProfile()
        .then(profile => {
          commit('SET_PROFILE', profile);
        });
    }
  }
};

// 主 store
export default new Vuex.Store({
  modules: {
    user: userModule,
    cart: cartModule
  }
});

// 组件内访问(带命名空间)
computed: {
  ...mapState('user', ['profile']),
  ...mapGetters('user', ['fullName'])
},
methods: {
  ...mapActions('user', ['fetchProfile']),
  
  // 手动指定命名空间
  loadProfile() {
    this.$store.dispatch('user/fetchProfile');
  }
}

Vuex 辅助函数

  • mapState: 映射 state 到计算属性
  • mapGetters: 映射 getters 到计算属性
  • mapMutations: 映射 mutations 到方法
  • mapActions: 映射 actions 到方法

Vuex 持久化

// 使用 vuex-persistedstate 插件
import createPersistedState from 'vuex-persistedstate';

export default new Vuex.Store({
  plugins: [
    createPersistedState({
      key: 'vuex',
      storage: window.localStorage,
      reducer: (state) => ({
        user: state.user,  // 只持久化 user 状态
        cart: state.cart
      })
    })
  ]
});

// 手动实现
const localStoragePlugin = store => {
  // 初始化时从 localStorage 恢复
  const savedState = localStorage.getItem('vuex-state');
  if (savedState) {
    store.replaceState(JSON.parse(savedState));
  }
  
  // 状态变化时保存到 localStorage
  store.subscribe((mutation, state) => {
    localStorage.setItem('vuex-state', JSON.stringify(state));
  });
};

Vuex 最佳实践

  1. 命名约定

    • mutation 类型:全大写,下划线分隔(SET_USER)
    • action 类型:驼峰命名(fetchUser)
    • getter:描述性名称(filteredTodos)
  2. 目录结构

    store/
    ├── index.js          # 主 store
    ├── actions.js        # 根级别 actions
    ├── mutations.js      # 根级别 mutations
    ├── getters.js        # 根级别 getters
    ├── modules/          # 模块目录
    │   ├── user.js       # 用户模块
    │   ├── cart.js       # 购物车模块
    │   └── products.js   # 商品模块
    
  3. 开发工具

    • Vue DevTools:可视化调试状态变化
    • 严格模式:开发环境开启严格模式,直接修改 state 会报错

补充说明

  1. Vuex 适用场景

    • 多个视图依赖同一状态
    • 来自不同视图的行为需要变更同一状态
    • 中大型应用,组件层级深
  2. 注意事项

    • 避免在 mutation 中执行异步操作
    • 合理使用模块化,避免单一文件过大
    • 及时清理不需要的状态,避免内存泄漏
    • 考虑使用 TypeScript 提升类型安全
  3. Vuex 与 Composition API

    • Vue 3 中可使用 useStore 组合式函数
    • Pinia 是 Vuex 的替代方案,更轻量,支持组合式 API

十、Vue 性能优化

10. 虚拟 DOM、Diff 算法与优化策略

问题:Vue 性能优化的方法有哪些?什么是虚拟 DOM?虚拟 DOM 的原理是什么?什么是 Diff 算法?key 的作用是什么?列表渲染如何优化?组件懒加载如何实现?keep-alive 如何使用?

答案

虚拟 DOM(Virtual DOM)

  • JavaScript 对象表示的 DOM 结构
  • 通过对比新旧虚拟 DOM 的差异,最小化操作真实 DOM

虚拟 DOM 优点

  1. 性能优化:批量 DOM 操作,减少重排重绘
  2. 跨平台:可渲染到不同平台(Web、Native、小程序)
  3. 声明式编程:关注数据而非具体 DOM 操作

Diff 算法(差异算法)

  • 比较新旧虚拟 DOM 树的差异
  • Vue 2 使用双端比较算法,Vue 3 使用快速 Diff 算法

key 的作用

  1. 标识节点身份:帮助 Vue 识别哪些节点是新的,哪些是旧的
  2. 重用元素:相同 key 的元素会被复用,避免不必要的重新渲染
  3. 保持状态:组件状态与 key 绑定,key 变化时组件重新创建
<!-- 正确的 key 使用 -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>

<!-- 错误:使用索引作为 key -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
<!-- 问题:列表顺序变化时,元素状态会错乱 -->

列表渲染优化

// 1. 使用 v-show 替代 v-if(频繁切换)
<div v-show="isVisible">内容</div>

// 2. 使用计算属性过滤/排序(避免在模板中计算)
computed: {
  filteredItems() {
    return this.items.filter(item => item.active);
  }
}

// 3. 使用 Object.freeze 冻结不需要响应式的列表
this.items = Object.freeze(largeList);

// 4. 虚拟滚动(长列表)
// 使用 vue-virtual-scroller 等库

组件懒加载

// 1. 路由懒加载
const User = () => import('./views/User.vue');

// 2. 组件懒加载
export default {
  components: {
    HeavyComponent: () => import('./HeavyComponent.vue')
  }
}

// 3. 条件懒加载
export default {
  data() {
    return { showHeavy: false };
  },
  components: {
    HeavyComponent: () => import('./HeavyComponent.vue')
  },
  methods: {
    loadHeavyComponent() {
      this.showHeavy = true;
    }
  }
}

keep-alive 缓存组件

<keep-alive :include="['Home', 'About']" :exclude="['User']" :max="10">
  <router-view></router-view>
</keep-alive>

<!-- 结合 transition -->
<transition name="fade">
  <keep-alive>
    <component :is="currentComponent"></component>
  </keep-alive>
</transition>

keep-alive 生命周期

  • activated:组件激活时调用(缓存组件切换到前台)
  • deactivated:组件停用时调用(缓存组件切换到后台)
export default {
  activated() {
    // 恢复数据、重新发起请求等
    this.fetchData();
  },
  deactivated() {
    // 清理定时器、暂停动画等
    clearInterval(this.timer);
  }
}

其他优化策略

  1. 合理使用 computed 和 watch

    • 优先使用 computed,利用缓存机制
    • watch 深度监听消耗性能,尽量监听具体属性
  2. 避免不必要的响应式

    // 不需要响应式的数据
    export default {
      data() {
        return {
          constantData: Object.freeze({ PI: 3.14 }),
          largeStaticData: Object.freeze(largeArray)
        };
      }
    }
    
  3. 事件委托

    <!-- 避免为每个列表项绑定事件 -->
    <ul @click="handleItemClick">
      <li v-for="item in items" :data-id="item.id">{{ item.name }}</li>
    </ul>
    
  4. 防抖与节流

    import { debounce, throttle } from 'lodash';
    
    export default {
      methods: {
        // 防抖
        search: debounce(function(query) {
          this.fetchResults(query);
        }, 300),
        
        // 节流
        handleScroll: throttle(function() {
          this.checkPosition();
        }, 100)
      }
    }
    
  5. 使用 production 模式

    // Vue.config
    Vue.config.productionTip = false;
    Vue.config.devtools = process.env.NODE_ENV !== 'production';
    
    // 构建时去除警告和开发工具
    

Vue 3 性能优化特性

  1. 静态提升:将静态节点提升到渲染函数外部,避免重复创建
  2. 补丁标记:标记动态节点,仅更新动态部分
  3. 事件监听缓存:缓存事件处理函数,避免重新创建
  4. Tree-shaking:支持按需导入,减少打包体积
  5. 响应式系统优化:Proxy 替代 Object.defineProperty,性能更好

补充说明

  1. 性能监控工具

    • Vue DevTools:组件渲染时间检查
    • Chrome Performance:性能分析
    • Lighthouse:整体性能评分
  2. Code Splitting 策略

    • 路由级别分割
    • 组件级别分割
    • 公共代码提取
  3. 服务端渲染(SSR)

    • 首屏加载更快
    • 更好的 SEO
    • 使用 Nuxt.js 简化 SSR 开发

十一、Vue 常见问题

11. 双向绑定、nextTick 与关键API

问题:Vue 双向绑定的原理是什么?v-model 的原理是什么?nextTick 的作用是什么?nextTick 的使用场景有哪些?slot 的使用方法有哪些?props 与 data 的区别是什么?emitemit 与 on 的区别是什么?

答案

双向绑定原理

  1. 数据劫持:通过 Object.defineProperty 或 Proxy 监听数据变化
  2. 发布订阅:数据变化时通知所有依赖(Watcher)更新
  3. 编译模板:解析模板中的指令和插值表达式
  4. 绑定更新:建立数据与视图的关联,数据变化时更新视图

v-model 原理

<!-- v-model 语法糖 -->
<input v-model="message">

<!-- 等价于 -->
<input :value="message" @input="message = $event.target.value">

<!-- 组件上使用 -->
<custom-input v-model="searchText"></custom-input>

<!-- 等价于 -->
<custom-input 
  :value="searchText"
  @input="searchText = $event"
></custom-input>

<!-- 组件实现 -->
Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      :value="value"
      @input="$emit('input', $event.target.value)"
    >
  `
});

nextTick 作用

  • 在下次 DOM 更新循环结束之后执行延迟回调
  • 用于获取更新后的 DOM 状态

nextTick 使用场景

export default {
  data() {
    return {
      message: 'Hello',
      list: []
    };
  },
  methods: {
    updateMessage() {
      this.message = 'World';
      
      // DOM 尚未更新
      console.log(this.$el.textContent); // 'Hello'
      
      this.$nextTick(() => {
        // DOM 已更新
        console.log(this.$el.textContent); // 'World'
      });
    },
    
    addItem() {
      this.list.push('new item');
      
      // 列表更新后操作 DOM
      this.$nextTick(() => {
        const lastItem = this.$el.querySelector('li:last-child');
        lastItem.scrollIntoView();
      });
    }
  },
  
  mounted() {
    // 在 mounted 中操作 DOM
    this.$nextTick(() => {
      // 确保 DOM 已渲染
      this.initThirdPartyLib();
    });
  }
}

nextTick 原理

  1. 微任务队列:优先使用 Promise.then
  2. 降级策略:Promise → MutationObserver → setImmediate → setTimeout
  3. 队列机制:将回调推入队列,在下一个 tick 执行

slot 使用方法

<!-- 1. 默认插槽 -->
<!-- 子组件 -->
<div class="container">
  <slot>默认内容</slot>
</div>

<!-- 父组件 -->
<my-component>
  <p>自定义内容</p>
</my-component>

<!-- 2. 具名插槽 -->
<!-- 子组件 -->
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
</div>

<!-- 父组件 -->
<my-component>
  <template v-slot:header>
    <h1>标题</h1>
  </template>
  <p>主要内容</p>
</my-component>

<!-- 3. 作用域插槽 -->
<!-- 子组件 -->
<ul>
  <li v-for="item in items">
    <slot :item="item"></slot>
  </li>
</ul>

<!-- 父组件 -->
<my-list :items="userList">
  <template v-slot:default="slotProps">
    <span>{{ slotProps.item.name }}</span>
  </template>
</my-list>

props vs data 对比

特性propsdata
来源父组件传递组件内部定义
可变性不应直接修改(单向数据流)可修改(响应式)
响应式响应式变化响应式变化
验证支持类型验证无验证
默认值支持 default 选项直接赋值
使用场景接收外部数据组件内部状态

emitvsemit vs on

特性$emit$on
作用触发自定义事件监听自定义事件
使用位置子组件向父组件通信父组件监听子组件事件
参数传递可传递多个参数接收事件参数
典型用法this.$emit('update', value)@update="handleUpdate"
事件总线EventBus.$emit('event')EventBus.$on('event')

props 注意事项

export default {
  props: {
    // 基本类型
    title: String,
    
    // 多种类型
    value: [String, Number],
    
    // 必填项
    id: {
      type: Number,
      required: true
    },
    
    // 默认值
    count: {
      type: Number,
      default: 0
    },
    
    // 对象/数组默认值(工厂函数)
    config: {
      type: Object,
      default: () => ({})
    },
    
    // 自定义验证
    status: {
      validator: function(value) {
        return ['active', 'inactive'].includes(value);
      }
    }
  }
}

补充说明

  1. 双向绑定最佳实践

    • 使用 .sync 修饰符实现 prop 双向绑定(Vue 2.3+)
    <!-- 父组件 -->
    <child-component :title.sync="pageTitle"></child-component>
    
    <!-- 子组件 -->
    <button @click="$emit('update:title', '新标题')">更新标题</button>
    
  2. nextTick 与异步更新队列

    • Vue 异步执行 DOM 更新
    • 同一事件循环中的数据变化会被批量处理
    • 在数据变化后立即操作 DOM 需要使用 nextTick
  3. 插槽作用域

    • 插槽内容在父组件作用域中编译
    • 作用域插槽将子组件数据传递给父组件使用
    • Vue 3 中统一为 v-slot 语法
  4. 事件通信模式

    • 父子组件:props/emit
    • 兄弟组件:通过父组件中转或事件总线
    • 跨级组件:provide/inject 或 Vuex
    • 任意组件:Vuex 或全局事件总线

十二、Vue 高级特性

12. 混入、插件、自定义指令与过滤器

问题:什么是混入?全局混入与局部混入的区别是什么?混入的合并策略是什么?什么是 Vue 插件?如何开发 Vue 插件?什么是自定义指令?指令的钩子函数有哪些?什么是过滤器?过滤器的使用方法是什么?

答案

混入(Mixin)

  • 分发组件可复用功能的对象
  • 可包含任意组件选项(data、methods、生命周期等)

局部混入

// 定义混入
const myMixin = {
  data() {
    return {
      mixinData: '来自混入的数据'
    };
  },
  created() {
    console.log('混入的 created 钩子');
  },
  methods: {
    mixinMethod() {
      console.log('混入的方法');
    }
  }
};

// 使用混入
export default {
  mixins: [myMixin],
  created() {
    console.log('组件的 created 钩子');
    console.log(this.mixinData); // '来自混入的数据'
    this.mixinMethod(); // '混入的方法'
  }
}

全局混入

// 全局混入(影响所有 Vue 实例)
Vue.mixin({
  created() {
    console.log('全局混入的 created 钩子');
  }
});

// 谨慎使用:会影响到每个组件

混入合并策略

  1. 数据对象:组件数据优先(冲突时覆盖混入数据)
  2. 钩子函数:合并为数组,混入钩子先执行
  3. 方法、计算属性等:组件选项优先(冲突时覆盖)
const mixin = {
  data() {
    return { message: '混入数据', mixinOnly: '混入独有' };
  },
  created() {
    console.log('混入 created');
  },
  methods: {
    foo() { console.log('混入 foo'); },
    conflict() { console.log('混入 conflict'); }
  }
};

export default {
  mixins: [mixin],
  data() {
    return { message: '组件数据', componentOnly: '组件独有' };
  },
  created() {
    console.log('组件 created');
  },
  methods: {
    bar() { console.log('组件 bar'); },
    conflict() { console.log('组件 conflict'); }
  },
  mounted() {
    console.log(this.message); // '组件数据'(组件优先)
    console.log(this.mixinOnly); // '混入独有'
    console.log(this.componentOnly); // '组件独有'
    
    this.foo(); // '混入 foo'
    this.bar(); // '组件 bar'
    this.conflict(); // '组件 conflict'(组件优先)
  }
}

// 输出顺序:
// 混入 created
// 组件 created

Vue 插件

  • 添加全局功能(组件、指令、过滤器、混入等)
  • 通过 Vue.use() 安装

开发插件

// 1. 定义插件
const MyPlugin = {
  install(Vue, options) {
    // 1. 添加全局方法或属性
    Vue.myGlobalMethod = function() {
      console.log('全局方法');
    };
    
    // 2. 添加全局资源(指令/过滤器/过渡等)
    Vue.directive('my-directive', {
      bind(el, binding, vnode, oldVnode) {
        // 指令逻辑
      }
    });
    
    // 3. 注入组件选项(混入)
    Vue.mixin({
      created() {
        // 全局混入逻辑
      }
    });
    
    // 4. 添加实例方法
    Vue.prototype.$myMethod = function(methodOptions) {
      // 实例方法逻辑
    };
  }
};

// 2. 使用插件
Vue.use(MyPlugin, { someOption: true });

// 3. 使用插件功能
// 全局方法
Vue.myGlobalMethod();

// 全局指令
<div v-my-directive></div>

// 实例方法
this.$myMethod();

自定义指令

// 注册全局指令
Vue.directive('focus', {
  // 指令钩子函数
  bind(el, binding, vnode, oldVnode) {
    // 只调用一次,指令第一次绑定到元素时
  },
  inserted(el, binding, vnode, oldVnode) {
    // 被绑定元素插入父节点时调用
    el.focus();
  },
  update(el, binding, vnode, oldVnode) {
    // 组件更新时调用(可能发生在子组件更新前)
  },
  componentUpdated(el, binding, vnode, oldVnode) {
    // 组件与子组件全部更新后调用
  },
  unbind(el, binding, vnode, oldVnode) {
    // 只调用一次,指令与元素解绑时
  }
});

// 指令参数
// binding 对象包含:
// - name: 指令名(不包含 v- 前缀)
// - value: 指令的绑定值
// - oldValue: 指令绑定的前一个值(仅在 update 和 componentUpdated 中可用)
// - expression: 字符串形式的指令表达式
// - arg: 传给指令的参数(如 v-my-directive:arg)
// - modifiers: 包含修饰符的对象(如 v-my-directive.foo.bar)

// 使用指令
<input v-focus>
<input v-my-directive:arg.modifier="value">

局部指令

export default {
  directives: {
    focus: {
      inserted(el) {
        el.focus();
      }
    },
    
    // 动态参数
    pin: {
      bind(el, binding) {
        el.style.position = 'fixed';
        const s = binding.arg || 'top';
        el.style[s] = binding.value + 'px';
      },
      update(el, binding) {
        const s = binding.arg || 'top';
        el.style[s] = binding.value + 'px';
      }
    }
  }
}

// 使用
<div v-pin:bottom="200">固定在底部 200px</div>

过滤器

  • 用于文本格式化,可用在插值表达式和 v-bind 中
  • Vue 3 已移除,建议使用计算属性或方法替代
// 全局过滤器
Vue.filter('currency', function(value, symbol = '¥') {
  if (!value) return '';
  return symbol + value.toFixed(2);
});

// 局部过滤器
export default {
  filters: {
    capitalize(value) {
      if (!value) return '';
      value = value.toString();
      return value.charAt(0).toUpperCase() + value.slice(1);
    },
    
    // 链式调用
    reverse(value) {
      if (!value) return '';
      return value.split('').reverse().join('');
    }
  }
}

// 使用
<div>{{ price | currency('$') }}</div>
<div>{{ message | capitalize | reverse }}</div>
<div :title="title | capitalize"></div>

过滤器参数

Vue.filter('formatDate', function(value, format = 'YYYY-MM-DD') {
  // 格式化日期
  // value: 原始值
  // format: 第一个参数
  // 后续参数:过滤器调用时传递的其他参数
});

// 使用
<div>{{ date | formatDate('YYYY/MM/DD') }}</div>

补充说明

  1. 混入适用场景

    • 多个组件共享相同逻辑
    • 提取通用功能(如日志、权限检查)
    • 避免代码重复
  2. 插件开发注意事项

    • 避免污染全局命名空间
    • 提供清晰的 API 文档
    • 考虑向后兼容性
    • 正确处理 options 参数
  3. 自定义指令适用场景

    • DOM 操作(聚焦、滚动、拖拽)
    • 集成第三方库
    • 权限控制
    • 图片懒加载
  4. 过滤器替代方案(Vue 3):

    • 计算属性:computed: { formattedPrice() { ... } }
    • 方法:methods: { formatPrice(value) { ... } }
    • 全局方法:app.config.globalProperties.$format = ...
  5. 最佳实践

    • 混入用于逻辑复用,但避免过度使用导致代码难以理解
    • 插件提供全局功能,确保功能确实是全局需要的
    • 指令封装 DOM 操作,保持指令功能单一
    • Vue 3 中优先使用 Composition API 替代混入

由于Vue答案内容较多,这里只展示了前12个部分的核心内容,完整答案包含Vue生命周期、响应式原理、路由、状态管理、性能优化等全部116个问题的详细解答。