vue

77 阅读24分钟

Vue3和vue2的生命周期

Vue 2 生命周期Vue 3 生命周期变化说明作用触发时机典型用途
beforeCreate被 setup() 替代组件实例初始化前触发,无法访问 data 和 methods组件初始化最早阶段极少使用
created被 setup() 替代组件实例创建完成,可访问 data 和 methodsbeforeCreate 之后,beforeMount 之前初始化数据、异步请求
beforeMountonBeforeMount名称前加 on,需显式导入组件挂载到 DOM 前触发created 之后,首次 render 调用前极少使用
mountedonMounted名称前加 on,需显式导入组件挂载到 DOM 后触发,可操作 DOM真实 DOM 已生成操作 DOM、初始化第三方库(如地图、图表)
beforeUpdateonBeforeUpdate名称前加 on,需显式导入数据变化导致 DOM 更新前触发数据更新后,虚拟 DOM 重新渲染前获取更新前的 DOM 状态(如滚动位置)
updatedonUpdated名称前加 on,需显式导入DOM 更新完成后触发虚拟 DOM 重新渲染并修补到真实 DOM 后操作更新后的 DOM(需谨慎)
beforeDestroyonBeforeUnmount重命名为 onBeforeUnmount组件销毁前触发,实例仍可用组件销毁前清理定时器、取消事件监听
destroyedonUnmounted重命名为 onUnmounted组件销毁后触发组件销毁后最终清理操作
activatedonActivated保留<keep-alive> 缓存的组件激活时触发组件从缓存中重新插入 DOM 后恢复组件状态(如重新请求数据)
deactivatedonDeactivated保留<keep-alive> 缓存的组件失活时触发组件从 DOM 移除进入缓存后保存临时状态(如表单内容)
onServerPrefetchVue 3 新增服务端渲染 (SSR) 时异步获取数据服务端渲染期间,组件实例创建后预取数据并存储到 Vuex 或组件状态中

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

在 Vue 中,父子组件的生命周期钩子函数执行顺序遵循一定的规律,主要是父组件先创建,子组件再创建;子组件先销毁,父组件再销毁

阶段父组件子组件
创建beforeCreate → created → beforeMountbeforeCreate → created → beforeMount → mounted
(子组件 mounted 后父组件才 mounted
更新beforeUpdatebeforeUpdate → updated
updated
销毁beforeDestroybeforeDestroy → destroyed
destroyed(子组件 destroyed 后父组件才 destroyed
  1. 创建阶段:父组件先执行钩子,子组件后执行,最终父组件 mounted
  2. 更新阶段:父组件更新会触发子组件更新,钩子按从父到子的顺序执行。
  3. 销毁阶段:子组件先销毁,父组件后销毁。

vue响应式原理

Vue 2 的响应式:

  • 怎么做的:用 Object.defineProperty 给对象的每个属性加“监听器”(getter/setter)。

  • 缺点

    • 只能监听对象已有的属性,新增或删除属性无法检测(必须用 Vue.set/Vue.delete)。
    • 数组需要特殊处理(比如 pushpop 能被监听,但直接改 arr[0] = 1 无效)。

Vue 3 的响应式:

  • 怎么做的:用 Proxy 直接代理整个对象,像“门卫”一样拦截所有操作(读、写、增、删)。

  • 优点

    • 自动检测所有变动(包括新增/删除属性、数组下标直接修改)。
    • 性能更好(按需处理数据,不用一上来就递归遍历所有属性)。

一句话理解:Vue3 的响应式从“盯每个房间”升级为“盯整栋楼”,省事又全面。

Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

谈一谈对 MVVM 的理解?

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。

v-model 双向绑定的原理是什么?

v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。 可以通过model属性的propevent属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性 。

vue2.x 和 vuex3.x 渲染器的 diff 算法分别说一下?

简单来说,diff算法有以下过程

  • 同级比较,再比较子节点
  • 先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
  • 比较都有子节点的情况(核心diff)
  • 递归比较子节点

Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

Vue3.x借鉴了 ivi算法和 inferno算法

在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)

该算法中还运用了动态规划的思想求解最长递归子序列。

vue-router 路由模式有几种

1. Hash 模式(默认模式)

  • URL 格式http://example.com/#/home

  • 原理

    • 利用 URL 的 hash 值# 后的部分)实现路由。
    • 通过监听 hashchange 事件响应路由变化。
  • 特点

    • 无需服务器支持:URL 中的 # 后的内容不会发送到服务器。
    • 兼容性好:支持所有浏览器(包括 IE9)。
    • 缺点:URL 中带 #,美观性较差。
  • 配置方式

    const router = new VueRouter({
      mode: 'hash', // 默认值,可省略
      routes: [...]
    })
    

2. History 模式

  • URL 格式http://example.com/home

  • 原理

    • 基于 HTML5 的 History APIpushStatereplaceState)。
    • 通过监听 popstate 事件响应路由变化。
  • 特点

    • URL 更简洁:无 #,符合常规 URL 习惯。
    • 需要服务器配合:避免直接访问子路由时返回 404(需配置服务器回退到 index.html)。
    • 兼容性:要求浏览器支持 HTML5 History API(IE10+)。
  • 配置方式

    const router = new VueRouter({
      mode: 'history',
      routes: [...]
    })
    

3. Abstract 模式

  • URL 格式:无 URL 变化(内存中管理路由)。

  • 原理

    • 不依赖浏览器环境,路由信息保存在内存中。
    • 适用于 非浏览器环境(如 Electron、React Native 等)。
  • 特点

    • 无 URL 交互:适合完全脱离浏览器地址栏的场景。
    • 手动控制路由:通过 router.push 或 router.replace 导航。
  • 配置方式

    const router = new VueRouter({
      mode: 'abstract',
      routes: [...]
    })
    

模式对比表

特性Hash 模式History 模式Abstract 模式
URL 形式带 #(如 /#/home无 #(如 /home无 URL 变化
兼容性所有浏览器IE10+ 或支持 History API 的浏览器所有环境
服务器要求无需配置需配置回退到 index.html无需配置
适用场景兼容性要求高的项目现代浏览器,追求 URL 美观非浏览器环境(如 Electron、移动端应用)

如何选择?

  1. Hash 模式:兼容性优先,或无法配置服务器时使用。
  2. History 模式:现代应用首选,需确保服务器配置正确。
  3. Abstract 模式:非浏览器环境开发(如桌面/移动端混合应用)。

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

(1)Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

(2)改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

主要包括以下几个模块:

  • State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。

  • Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。

  • Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。

  • Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。

  • Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。

Pinia

Pinia 是 Vue 官方推荐的状态管理库,是 Vuex 的现代替代方案,尤其适合 Vue 3 的 Composition API。以下是对 Pinia 的详细理解:

1. 核心特点

  • 简洁性:API 设计简洁,去掉了 Vuex 中的 mutations,直接通过 actions 修改状态,语法更直观。
  • 轻量级:相比 Vuex,Pinia 更轻量,没有不必要的抽象层,有助于提高性能和减小包大小。
  • 模块化:每个 store 是一个独立的模块,声明更清晰,方便导入和组合,且不需要命名空间。
  • 类型安全:原生支持 TypeScript,类型推断完善,减少类型错误的可能性。
  • 响应式优化:得益于 Vue 3 的改进,Pinia 在状态更新和响应式方面表现更佳,提升应用性能。
  • 开发工具支持:Pinia 拥有自己的 DevTools 插件,方便开发者调试状态变化,提高开发效率。

2. 与 Vuex 的区别

  • API 设计:Vuex 使用 mutations、actions 和 getters 来定义状态、修改和异步操作,API 较为繁琐;而 Pinia 简化了 API,无需 mutations,直接用 state、getters 和 actions,语法更直观。
  • 类型支持:Vuex 4 支持 TypeScript,但类型推断复杂,尤其在大型项目中;Pinia 原生支持 TypeScript,更加方便,类型推断完善。
  • 模块化管理:Vuex 可以定义命名空间的模块,通过 namespace 管理,但嵌套和类型管理复杂;Pinia 每个 store 是独立的,声明更清晰,方便导入和组合,且不需要命名空间。
  • 性能:Vuex 中每次状态变更都会重新计算所有 getters,可能导致性能开销;Pinia 通过 Composition API 优化了状态追踪机制,具备更好的性能表现,避免不必要的更新。

3. 使用场景

  • 适合中小型项目:Pinia 的简洁性和灵活性使其更适合中小型项目,以及对灵活性和简洁性有更高要求的开发者。
  • 支持 SSR:Pinia 支持 Nuxt 3 和 Vue SSR,相比 Vuex 更简单。
  • 动态注册 store:Pinia 支持动态注册 store,方便在运行时根据需要创建新的 store。

4. 实现原理

  • 基于 Vue 3 的响应式机制:Pinia 的实现本质原理是基于 Vue 3 的 Composition API 和响应式系统(reactive、ref),状态(state)通过 Vue 的 reactive API 实现响应式。
  • 计算属性(getters) :利用 Vue 的 computed 实现,一旦 state 发生变化,自动触发计算更新。
  • Actions:作为普通方法,可以自由执行异步代码,合并了 Vuex 中的 mutation 和 action 的概念。

5. 插件支持

  • 持久化存储:Pinia 支持通过插件实现状态持久化,例如使用 pinia-plugin-persistedstate
  • 扩展性:Pinia 的设计允许开发者根据需要轻松添加自定义插件或功能。

6. 兼容性

  • Vue 2 和 Vue 3:尽管为 Vue 3 而生,Pinia 通过集成 @vue/composition-api,同样能在 Vue 2 项目中发挥效能,为 Vue 2 用户提供了一条平滑过渡至 Vue 3 的路径。

7. 最佳实践

  • 定义 Store:使用 defineStore 函数创建 store,每个 store 都是一个独立的模块,无需复杂的配置即可上手。
  • 读取和修改 State:通过 store.state 或解构的方式读取 state,通过 actions 或直接修改 state 的方式更新状态。
  • 使用 Getters:定义计算属性,基于 state 返回新的衍生数据。
  • 持久化存储:使用 pinia-plugin-persistedstate 插件实现状态的持久化存储。

8. 示例


	// 定义一个 Store

	import { defineStore } from 'pinia';

	 

	export const useCounterStore = defineStore('counter', {

	  state: () => ({

	    count: 0

	  }),

	  actions: {

	    increment() {

	      this.count++;

	    },

	    async incrementAsync() {

	      setTimeout(() => this.increment(), 1000);

	    }

	  },

	  getters: {

	    doubleCount: (state) => state.count * 2

	  }

	});

	 

	// 在组件中使用 Store

	import { useCounterStore } from '@/stores/counter';

	 

	export default {

	  setup() {

	    const store = useCounterStore();

	    return {

	      count: store.count,

	      doubleCount: store.doubleCount,

	      increment: store.increment

	    };

	  }

	};

v-show 与 v-if 有什么区别?

v-if真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。

所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。

组件中 data 为什么是一个函数?

为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?


// data
data() {
  return {
	message: "子组件",
	childName:this.name
  }
}

// new Vue
new Vue({
  el: '#app',
  router,
  template: '<App/>',
  components: {App}
})

因为组件是用来复用的,且 JS 里对象是引用关系,如果组件中 data 是一个对象,那么这样作用域没有隔离,子组件中的 data 属性值会相互影响,如果组件中 data 选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的 data 属性值不会互相影响;而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。

Vue的通信方式

Vue3的8种和Vue2的12种组件通信,值得收藏

Vue3通信方式:

  • props
  • $emit
  • expose / ref
  • $attrs
  • v-model
  • provide / inject(原理:原型链)
  • Vuex/pinia
  • mitt

Vue2.x 组件通信共有12种

  • props
  • $emit / v-on
  • .sync
  • v-model
  • ref
  • children/children / children/parent
  • attrs/attrs / attrs/listeners
  • provide / inject
  • EventBus
  • Vuex
  • $root
  • slot

vue 修饰符都有哪些

在 Vue 中,修饰符(Modifiers)是用于改变指令行为的特殊后缀,通常以点(.)开头。它们可以简化代码逻辑,提升开发效率。以下是 Vue 中常见的修饰符分类及示例:

1. 事件修饰符

用于处理 DOM 事件,控制事件传播或默认行为。

  • .stop:阻止事件冒泡。

    	<button @click.stop="handleClick">Click me</button>
    
  • .prevent:阻止事件的默认行为。

    	<form @submit.prevent="handleSubmit">Submit</form>
    
  • .capture:在捕获阶段触发事件。

    	<div @click.capture="handleCapture">Capture Click</div>
    
  • .self:仅当事件由元素自身触发时才触发回调。

    	<div @click.self="handleSelfClick">Self Click</div>
    
  • .once:事件只触发一次。

    	<button @click.once="handleClickOnce">Click Once</button>
    
  • .passive:提示浏览器事件处理程序不会调用 event.preventDefault(),提升滚动性能。

    	<div @scroll.passive="handleScroll">Scroll</div>
    

2. 表单输入修饰符

用于处理表单输入,简化数据绑定逻辑。

  • .lazy:将输入事件改为在 change 事件时触发,而不是 input

    	<input v-model.lazy="message">
    
  • .number:将用户输入自动转换为数值类型。

    	<input v-model.number="age" type="number">
    
  • .trim:自动过滤用户输入的首尾空白字符。

    	<input v-model.trim="username">
    

3. 按键修饰符

用于监听特定键盘按键事件。

  • 常见别名

    • .enter.tab.delete(捕获“删除”和“退格”键)、.esc.space.up.down.left.right
    	<input @keyup.enter="submitForm">
    
  • 自定义按键
    可以通过 Vue.config.keyCodes 自定义按键别名。

    	Vue.config.keyCodes.f1 = 112;
    
    	<input @keyup.f1="handleF1">
    

4. 系统修饰符

用于监听组合键事件。

  • 常用修饰符

    • .ctrl.alt.shift.meta(在 Mac 系统中是 Command 键,在 Windows 系统中是 Windows 键)
    	<input @keyup.ctrl.enter="handleCtrlEnter">
    
  • 组合使用
    系统修饰符可以与其他修饰符组合使用。

    	<input @keyup.ctrl.shift.enter="handleCtrlShiftEnter">
    

5. 鼠标按钮修饰符

用于监听特定鼠标按钮的点击事件。

  • 常用修饰符

    • .left.right.middle
    	<div @mousedown.right="handleRightClick">Right click me</div>
    

6. 其他特殊修饰符

  • .native
    监听组件根元素的原生事件,而不是组件内部通过 $emit 派发的事件。

    	<my-component @click.native="handleNativeClick"></my-component>
    
  • .sync(Vue 2.x):
    用于实现子组件和父组件之间的双向绑定(Vue 3 中已移除,推荐使用 v-model 的参数形式)。

    	<child-component :value.sync="parentValue"></child-component>
    

7. 路由相关修饰符

  • .exact(用于 router-link):
    只有当路由完全匹配时才激活。

    	<router-link to="/home" exact>Home</router-link>
    

Vue 中的 key 有什么作用?

在 Vue 中,key 是一个特殊的属性,主要用于虚拟 DOM 的高效更新。它的核心作用是帮助 Vue 区分哪些节点是相同的、哪些是不同的,从而在 DOM 更新时实现更高效的复用和替换。

  • key 必须唯一且稳定。
  • 优先使用数据中的唯一标识符作为 key

好的!简单明了地说,ref 和 reactive 的区别如下:

Vue 中ref 和 reactive 的区别

1. 数据类型

  • ref:用于基本类型(数字、字符串、布尔值)或单个对象
  • reactive:用于对象类型(对象、数组、Map、Set 等)。

2. 返回值

  • ref:返回一个对象,数据存放在 .value 里。

    	const count = ref(0);
    	console.log(count.value); // 0
    
  • reactive:直接返回一个代理对象,数据直接操作。

    	const state = reactive({ name: 'Alice' });
    	console.log(state.name); // 'Alice'
    

3. 模板使用

  • ref:模板中自动解包 .value,直接用变量名。

    	<div>{{ count }}</div> <!-- 不用写 count.value -->
    
  • reactive:模板中直接用对象的属性。

    	<div>{{ state.name }}</div>
    

4. 嵌套对象

  • ref:如果包装的是对象,内部属性会自动变成响应式。

    	const obj = ref({ name: 'Alice' });
    	obj.value.name = 'Bob'; // 响应式
    
  • reactive:整个对象及其属性都是响应式。

    	const state = reactive({ name: 'Alice' });
    	state.name = 'Bob'; // 响应式
    

5. 总结:最佳实践

  • 非对象数据 → 一律用ref:避免reactive的类型限制和响应丢失风险。
  • 深嵌套对象 → 可谨慎用reactive:确保熟悉响应式维护规则,或配合toRefs解构。
  • 追求开发效率 → 推荐ref一把梭:尤雨溪及官方文档均推荐ref作为首选API35,搭配Volar插件自动补全.value提升效率5。

💡 核心原则: “能用ref不用reactive,必须用对象且无替换需求时才考虑reactive 。灵活结合toRefsshallowRef等API可覆盖更复杂场景。

toRef 和 toRefs

在 Vue 3 的 Composition API 中,toRef 和 toRefs 是用于处理响应式对象属性的工具函数,它们的主要作用是将响应式对象中的属性转换为 ref,以便在模板或逻辑中更灵活地使用。以下是两者的详细对比与用法:

1. toRef

  • 定义
    toRef 用于将响应式对象(reactive 创建的对象)的某个属性转换为单独的 ref

  • 特点

    • 转换后的 ref 与原始对象的属性保持引用关系,即修改 ref 的值会同步到原始对象,反之亦然。
    • 不会破坏原始响应式对象的结构。
    • 适用于只需要对单个属性进行响应式处理的场景。
  • 语法

    	import { reactive, toRef } from 'vue';
    	const state = reactive({ name: 'Alice', age: 25 });
    	const nameRef = toRef(state, 'name');
    
  • 使用场景
    当需要单独解构或传递某个响应式属性时,使用 toRef 可以保持其响应式。


2. toRefs

  • 定义
    toRefs 用于将整个响应式对象的所有属性转换为 ref,并返回一个新的对象。

  • 特点

    • 转换后的每个 ref 都与原始对象的属性保持引用关系。
    • 适用于需要解构响应式对象或批量处理属性的场景。
    • 返回的对象可以直接解构,且解构后的属性仍然是响应式的。
  • 语法

    	import { reactive, toRefs } from 'vue';
    	const state = reactive({ name: 'Alice', age: 25 });
    	const refs = toRefs(state);
    
  • 使用场景
    当需要解构响应式对象时,使用 toRefs 可以避免解构后丢失响应式。


3. 区别对比

特性toReftoRefs
目标单个属性整个对象
返回值单个 ref包含多个 ref 的对象
适用场景需要单独处理某个属性需要解构或批量处理属性
响应式保持
解构后响应性不涉及解构,保持响应性解构后仍保持响应性

4. 示例代码

使用 toRef

	import { reactive, toRef } from 'vue';

	 

	export default {

	  setup() {

	    const state = reactive({ name: 'Alice', age: 25 });

	    const nameRef = toRef(state, 'name');

	 

	    const updateName = () => {

	      nameRef.value = 'Bob'; // 同步更新到 state.name

	    };

	 

	    return { state, nameRef, updateName };

	  },

	};

使用 toRefs

	import { reactive, toRefs } from 'vue';


	export default {

	  setup() {

	    const state = reactive({ name: 'Alice', age: 25 });

	    const { name, age } = toRefs(state); // 解构后仍保持响应式

	 

	    const updateAge = () => {

	      age.value += 1; // 同步更新到 state.age

	    };

	 

	    return { state, name, age, updateAge };

	  },

	};

5. 注意事项

  • 非响应式对象
    如果对普通对象(非 reactive 创建的对象)使用 toRef 或 toRefs,结果不会具备响应式。
  • 解构与响应式
    直接解构 reactive 对象会丢失响应式,因此需要使用 toRefs
  • 性能
    toRefs 会遍历对象的所有属性,因此在处理大型对象时可能影响性能。

6. 总结

  • toRef:适用于单独处理某个响应式属性。
  • toRefs:适用于解构或批量处理响应式对象的属性。

Vue 3 新特性简单总结:

一、核心变化

  1. Composition API

    • 替代 Options API 的代码组织方式,用 setup() 函数集中管理逻辑(数据、方法、计算属性等)。
    • 优势:逻辑复用更灵活(类似 React Hooks),代码更内聚。
    import { ref, computed } from 'vue'
    export default {
      setup() {
        const count = ref(0)
        const double = computed(() => count.value * 2)
        return { count, double }
      }
    }
    
  2. 响应式系统升级

    • 用 Proxy 替代 Object.defineProperty完美监听对象/数组变化,解决 Vue 2 中无法检测新增属性的问题。
    • 提供 ref 和 reactive 显式声明响应式数据。
  3. 性能优化

    • 更快的渲染:虚拟 DOM 重写,编译时优化(如静态节点标记)。
    • Tree-shaking 支持:按需引入 API,减小打包体积。
    • SSR 提速:服务端渲染性能提升。

二、新功能

  1. Fragment(片段)
    组件模板支持多根节点,不用再包一层 <div>

    <template>
      <header></header>
      <main></main>
    </template>
    
  2. Teleport(传送门)
    将组件渲染到 DOM 的任意位置(如全局弹窗)。

    <teleport to="#modal-container">
      <div class="modal">弹窗内容</div>
    </teleport>
    
  3. Suspense(异步组件)
    处理异步组件加载状态,展示加载中/错误状态。

    <Suspense>
      <template #default> <AsyncComponent /> </template>
      <template #fallback> 加载中... </template>
    </Suspense>
    

三、其他改进

  • TypeScript 全面支持
    源码用 TS 重写,提供完整类型定义,开发更友好。

  • 全局 API 调整
    用 createApp() 替代 new Vue(),避免全局污染。

  • Vite 官方推荐
    开发工具链升级,启动和热更新速度极快。

    <keep-alive> 是 Vue.js 提供的一个内置抽象组件,主要用于缓存动态组件的状态,避免组件在切换时被重复销毁和重建,从而提升性能和用户体验。以下是关于 <keep-alive> 的详细介绍:


Vue 中的 <keep-alive>

核心功能

  • 缓存组件实例:当组件被 <keep-alive> 包裹时,组件在切换出去后不会被销毁,而是被缓存起来,再次切换回来时可以直接复用缓存的实例。
  • 性能优化:避免重复的 DOM 操作和组件生命周期钩子(如 createdmounted)的重复执行。
  • 状态保留:保留组件的内部状态(如表单输入、滚动位置等)。

怎么用

vue复制代码
	<keep-alive>

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

	</keep-alive>
  • current 是动态组件的名称,切换时组件不会被销毁,而是缓存起来。

常用属性

  1. include:只缓存指定名称的组件(如 "A,B")。
  2. exclude:不缓存指定名称的组件。
  3. max:限制缓存组件的最大数量,超过时会销毁最早的。

生命周期

  • activated:组件被激活时(从缓存中取出)触发。
  • deactivated:组件被停用时(切换到其他组件)触发。

适用场景

  • 表单编辑页面切换后保留内容。
  • 多标签页切换时保留页面状态。
  • 动态路由组件缓存。

Vue 3 vs Vue 2 区别

特性Vue 2Vue 3
缓存结构对象缓存Map 缓存
生命周期activated/deactivatedonActivated/onDeactivated
最大缓存数支持 max 属性支持 max 属性
实现方式选项式 API组合式 API + 渲染函数
Tree Shaking不支持支持

keep-alive缓存后如何获取数据

1.beforeRouteEnter

每次组件渲染的时候,都会执行 beforeRouteEnter

beforeRouteEnter(to, from, next){
  next(vm=>{
    console.log(vm)
    // 每次进入路由执行
    vm.getData()  // 获取数据
  })
},

2.actived

在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子

activated(){
  this.getData() // 获取数据
},

注意:服务器端渲染期间 avtived 不被调用

Vue 的单向数据流

Vue 的单向数据流是指数据从父组件单向流向子组件,数据流动方向是固定的、不可逆的。这种设计模式让数据流动清晰可控,避免数据状态混乱。

核心特点

  1. 父组件 → 子组件

    • 父组件通过 props 将数据传递给子组件。
    • 子组件只能接收数据,不能直接修改父组件的数据。
  2. 子组件 → 父组件

    • 子组件通过 事件($emit  将数据或操作通知父组件。
    • 父组件根据事件更新自己的数据,再通过 props 传递给子组件。
  3. 不可逆性

    • 数据不能直接从子组件流向父组件,也不能从子组件反向修改父组件的数据。

注意事项

  1. 避免直接修改 props

    • 子组件如果需要修改 props,应该通过 $emit 通知父组件,而不是直接修改。
  2. 使用 v-model 的双向绑定本质

    • Vue 的 v-model 是语法糖,背后仍然是单向数据流:

      • 父组件通过 value 传递数据给子组件。
      • 子组件通过 input 事件通知父组件更新数据。

Vue 项目性能优化是提升用户体验和系统效率的关键,可从代码层面打包层面部署层面等多个维度入手。以下是具体优化策略:

Vue 项目优化

参考另一篇文章《 Vue 项目性能优化 — 实践指南 》

(1)代码层面的优化

  • v-if 和 v-show 区分使用场景
  • computed 和 watch 区分使用场景
  • v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
  • 长列表性能优化
  • 事件的销毁
  • 图片资源懒加载
  • 路由懒加载
  • 第三方插件的按需引入
  • 优化无限列表性能
  • 服务端渲染 SSR or 预渲染

(2)Webpack 层面的优化

  • Webpack 对图片进行压缩
  • 减少 ES6 转为 ES5 的冗余代码
  • splitChunks 抽离公共文件
  • 模板预编译
  • 提取组件的 CSS
  • 优化 SourceMap
  • 构建结果输出分析
  • Vue 项目的编译优化

(3)基础的 Web 技术的优化

  • 开启 gzip 压缩
  • 浏览器缓存
  • CDN 的使用
  • 使用 Chrome Performance 查找性能瓶颈

Vue2 Mixin

Mixin 是一种在 Vue 中实现代码复用的方式,允许将可复用的功能模块混入到多个组件中。

Mixin 合并规则

  1. 生命周期钩子:mixin 钩子 → 组件钩子
  2. 多个 mixin:按数组顺序执行
  3. 数据对象:组件数据覆盖 mixin 数据
  4. 方法:组件方法覆盖 mixin 方法
  5. 全局 mixin:最先执行,然后是局部 mixin,最后是组件

Vue 3 的替代方案:Composition API

Vue 2 和 Vue 3 父组件监听子组件生命周期

Vue 2 中的方法

1. 使用 @hook: 语法(推荐)

<template>
  <child-component 
    @hook:created="onChildCreated"
    @hook:mounted="onChildMounted"
    @hook:updated="onChildUpdated"
    @hook:destroyed="onChildDestroyed"
  />
</template>

<script>
export default {
  methods: {
    onChildCreated() {
      console.log('子组件 created')
    },
    onChildMounted() {
      console.log('子组件 mounted')
    },
    onChildUpdated() {
      console.log('子组件 updated')
    },
    onChildDestroyed() {
      console.log('子组件 destroyed')
    }
  }
}
</script>

2. 使用 $on 方法

<template>
  <child-component ref="child" />
</template>

<script>
export default {
  mounted() {
    // 监听子组件生命周期
    this.$refs.child.$on('hook:created', this.onChildCreated)
    this.$refs.child.$on('hook:mounted', this.onChildMounted)
  },
  beforeDestroy() {
    // 移除监听
    this.$refs.child.$off('hook:created', this.onChildCreated)
    this.$refs.child.$off('hook:mounted', this.onChildMounted)
  },
  methods: {
    onChildCreated() {
      console.log('子组件 created')
    },
    onChildMounted() {
      console.log('子组件 mounted')
    }
  }
}
</script>

3. 通过 props 传递回调函数

<!-- 父组件 -->
<template>
  <child-component 
    :on-mounted="onChildMounted"
    :on-updated="onChildUpdated"
  />
</template>

<script>
export default {
  methods: {
    onChildMounted() {
      console.log('子组件 mounted')
    },
    onChildUpdated() {
      console.log('子组件 updated')
    }
  }
}
</script>

<!-- 子组件 -->
<script>
export default {
  props: {
    onMounted: Function,
    onUpdated: Function
  },
  mounted() {
    this.onMounted?.()
  },
  updated() {
    this.onUpdated?.()
  }
}
</script>

Vue 3 中的方法

1. 使用 @vue: 语法(Vue 3 方式)

<template>
  <child-component 
    @vue:created="onChildCreated"
    @vue:mounted="onChildMounted"
    @vue:updated="onChildUpdated"
    @vue:unmounted="onChildUnmounted"
  />
</template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  methods: {
    onChildCreated() {
      console.log('子组件 created')
    },
    onChildMounted() {
      console.log('子组件 mounted')
    },
    onChildUpdated() {
      console.log('子组件 updated')
    },
    onChildUnmounted() {
      console.log('子组件 unmounted')
    }
  }
})
</script>

2. 使用组合式 API

<template>
  <child-component ref="childRef" />
</template>

<script>
import { defineComponent, ref, onMounted, onUnmounted } from 'vue'

export default defineComponent({
  setup() {
    const childRef = ref(null)

    const onChildMounted = () => {
      console.log('子组件 mounted')
    }

    const onChildUpdated = () => {
      console.log('子组件 updated')
    }

    // 使用生命周期钩子监听
    onMounted(() => {
      // 可以在这里访问子组件实例
      if (childRef.value) {
        console.log('子组件实例:', childRef.value)
      }
    })

    return {
      childRef,
      onChildMounted,
      onChildUpdated
    }
  }
})
</script>

3. 使用 v-on 监听器

<template>
  <child-component 
    v-on="{
      'vue:mounted': onChildMounted,
      'vue:updated': onChildUpdated
    }"
  />
</template>

Vue 项目错误处理全面方案

1. 全局错误处理

Vue 3 全局错误捕获

// main.js
const app = createApp(App)

app.config.errorHandler = (err, instance, info) => {
  console.error('全局错误:', err)
  console.error('组件实例:', instance)
  console.error('错误信息:', info)
  
  // 错误上报
  errorTracker.report(err, { instance, info })
}

2. 生命周期钩子错误处理

选项式 API

<template>
  <div>
    <child-component />
  </div>
</template>

<script>
export default {
  name: 'ParentComponent',
  errorCaptured(err, instance, info) {
    console.error('Vue3 - 错误捕获:', err)
    console.error('组件实例:', instance)
    console.error('生命周期:', info)
    
    return false
  }
}
</script>

组合式 API

<template>
  <div>
    <child-component />
  </div>
</template>

<script setup>
import { onErrorCaptured, ref } from 'vue'

const error = ref(null)

onErrorCaptured((err, instance, info) => {
  console.error('Vue3 组合式 - 错误捕获:', err)
  console.error('组件实例:', instance)
  console.error('生命周期:', info)
  
  error.value = err
  // 返回 false 阻止错误继续向上传播
  return false
})
</script>

3. 路由错误处理

Vue 3 + Vue Router 4

基本错误处理
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [...]
})

// 全局路由错误处理
router.onError((error, to, from) => {
  console.error('Vue3 路由错误:', error)
  console.error('目标路由:', to)
  console.error('来源路由:', from)
  
  // 处理 chunk 加载失败
  if (error.message.includes('Failed to fetch dynamically imported module')) {
    window.location.reload()
  }
})
导航守卫错误处理
// Vue 3 导航守卫
router.beforeEach((to, from) => {
  try {
    // 权限验证逻辑
    if (to.meta.requiresAuth && !isAuthenticated()) {
      return '/login' // 返回路径字符串
    }
    
    if (to.meta.requiresAdmin && !isAdmin()) {
      return { path: '/unauthorized' } // 返回路由位置
    }
    
    return true // 允许导航
  } catch (error) {
    console.error('导航守卫错误:', error)
    return '/error' // 跳转到错误页面
  }
})

router.afterEach((to, from, failure) => {
  if (failure) {
    console.error('导航失败:', failure)
    // 处理导航失败情况
  } else {
    console.log('导航成功:', to.path)
  }
})
Vue 3异步路由加载错误处理对比
// Vue 3
import { defineAsyncComponent } from 'vue'

const AsyncComponent = defineAsyncComponent({
  loader: () => import('./components/AsyncComponent.vue'),
  loadingComponent: LoadingComponent,
  errorComponent: ErrorComponent,
  delay: 200,
  timeout: 3000,
  onError(error, retry, fail, attempts) {
    if (error.message.includes('Failed to fetch') && attempts <= 3) {
      retry()
    } else {
      fail()
    }
  }
})

const router = createRouter({
  routes: [
    {
      path: '/async',
      component: AsyncComponent
    }
  ]
})
Vue 3导航失败处理对比
// Vue 3 - 编程式导航错误处理
try {
  await router.push('/some-path')
} catch (error) {
  if (error.type === NavigationFailureType.duplicated) {
    // 忽略重复导航错误
    return
  }
  console.error('导航失败:', error)
  
  // 显示错误提示
  showErrorMessage('导航失败,请重试')
}
vue 完整路由配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { NavigationFailureType } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('../views/Home.vue')
    },
    {
      path: '/error',
      component: () => import('../views/Error.vue')
    }
  ]
})

// 错误处理
router.onError((error, to, from) => {
  console.error('路由错误:', error)
  console.log('目标路由:', to)
  console.log('来源路由:', from)
  
  if (error.message.includes('Failed to fetch dynamically imported module')) {
    // 动态导入模块失败
    router.push('/error?type=chunk')
  }
})

// 导航守卫错误处理
router.beforeEach((to, from) => {
  try {
    const result = checkPermissions(to)
    if (result.redirect) {
      return result.redirect
    }
    return true
  } catch (error) {
    console.error('路由守卫错误:', error)
    return '/error'
  }
})

// 全局导航失败处理
router.afterEach((to, from, failure) => {
  if (failure) {
    if (failure.type === NavigationFailureType.duplicated) {
      return // 忽略重复导航
    }
    console.error('导航失败:', failure)
  }
})

export default router

Vue 3 虚拟 DOM 详细解析

什么是虚拟 DOM?

虚拟 DOM (Virtual DOM) 是一个轻量级的 JavaScript 对象树,它是真实 DOM 的抽象表示。Vue 3 使用虚拟 DOM 来描述 UI 的结构、状态和行为。

// Vue 3 虚拟 DOM 节点结构
const vnode = {
  __v_isVNode: true,      // 标识这是一个虚拟节点
  type: 'div',            // 节点类型(字符串=原生元素,对象=组件)
  props: {                // 属性对象
    id: 'app',
    class: ['container', 'active'],
    onClick: handleClick,
    style: { color: 'red' }
  },
  children: [             // 子节点
    {
      type: 'h1',
      props: null,
      children: 'Hello Vue 3',
      shapeFlag: 9,       // 文本子节点
      patchFlag: -1       // 静态节点
    },
    {
      type: 'p',
      props: { class: 'content' },
      children: dynamicText,
      shapeFlag: 9,
      patchFlag: 1        // 文本内容是动态的
    }
  ],
  el: null,               // 对应的真实 DOM 元素
  key: null,              // 节点的 key
  ref: null,              // 引用
  shapeFlag: 16,          // 节点形状标志(元素类型 + 子节点类型)
  patchFlag: 9,           // 补丁标志(TEXT + PROPS)
  dynamicChildren: [      // 动态子节点(优化)
    /* 只包含动态变化的子节点引用 */
  ]
}

虚拟 DOM vs 真实 DOM

特性虚拟 DOM真实 DOM
本质JavaScript 对象浏览器原生对象
内存位置堆内存中浏览器渲染引擎中
操作成本内存操作,成本极低触发重排重绘,成本高
更新方式Diff 算法 + 批量更新直接操作,立即生效
性能特点避免不必要的 DOM 操作直接但效率低
创建方式createVNode()document.createElement()
跨平台一套代码多端渲染仅限浏览器环境
调试可序列化,易于调试浏览器开发者工具

为什么 Vue 3 要用虚拟 DOM?

1. 性能优化

// 没有虚拟 DOM - 性能差
function updateView() {
  // 每次数据变化都直接操作 DOM
  document.getElementById('title').textContent = newTitle
  document.getElementById('content').innerHTML = newContent
  document.getElementById('list').innerHTML = newList.map(item => 
    `<div class="${item.active ? 'active' : ''}">${item.name}</div>`
  ).join('')
  // 导致多次重排重绘,性能低下
}

// 使用虚拟 DOM - 性能好
function updateWithVirtualDOM() {
  // 1. 创建新的虚拟 DOM(内存操作,很快)
  const newVNode = renderFunction(newState)
  
  // 2. Diff 比较找出最小变更(算法优化)
  const patches = diff(oldVNode, newVNode)
  
  // 3. 批量更新到真实 DOM(一次重排)
  patch(container, patches)
}

2. 声明式开发体验

<!-- 开发者只需关注数据,不用关心 DOM 操作 -->
<template>
  <div>
    <h1>{{ title }}</h1>
    <ul>
      <li v-for="item in list" :key="item.id" :class="{ active: item.isActive }">
        {{ item.name }}
      </li>
    </ul>
    <button @click="addItem">添加</button>
  </div>
</template>

3. 跨平台能力

// 同一套虚拟 DOM,不同渲染器
const vnode = createVNode('div', { class: 'app' }, 'Hello World')

// 不同平台的渲染器
const renderers = {
  // 浏览器 DOM
  dom: (vnode) => createDOMElement(vnode),
  
  // 服务端渲染
  ssr: (vnode) => renderToString(vnode),
  
  // 原生应用
  native: (vnode) => createNativeView(vnode),
  
  // Canvas
  canvas: (vnode) => drawOnCanvas(vnode)
}

Vue 3 虚拟 DOM 转换详细过程

阶段 1:模板编译

// 源代码
const App = {
  template: `
    <div class="container">
      <h1>{{ title }}</h1>
      <button @click="increment">Count: {{ count }}</button>
    </div>
  `,
  data() {
    return { title: 'Hello', count: 0 }
  }
}

// Vue 3 编译后的渲染函数
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    class: "container"
  }, [
    _createElementVNode("h1", null, _toDisplayString(_ctx.title), 1 /* TEXT */),
    _createElementVNode("button", {
      onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.increment && _ctx.increment(...args)))
    }, "Count: " + _toDisplayString(_ctx.count), 9 /* TEXT, PROPS */, ["onClick"])
  ]))
}

阶段 2:创建虚拟 DOM 树

// 执行渲染函数,生成虚拟 DOM 
const vnode = {
  type: 'div',
  props: { class: 'container' },
  children: [
    {
      type: 'h1',
      children: 'Hello',  // 动态文本
      patchFlag: 1,       // TEXT - 表示文本内容可能变化
      dynamicChildren: null
    },
    {
      type: 'button',
      props: { onClick: increment },
      children: 'Count: 0',
      patchFlag: 9,       // TEXT | PROPS - 文本和属性都可能变化
      dynamicChildren: null
    }
  ],
  patchFlag: 0,           // 0 表示只有子节点可能变化
  dynamicChildren: [      // 只包含动态子节点的引用
    /* 引用指向 h1  button 节点 */
  ]
}

阶段 3:Diff 算法(精细化比较)

3.1 基于 Patch Flags 的快速路径
// 根据 patchFlag 快速判断更新类型
function patchElement(n1, n2) {
  const { patchFlag, dynamicChildren } = n2
  
  // 快速路径:如果只有文本变化
  if (patchFlag & PatchFlags.TEXT) {
    if (n1.children !== n2.children) {
      setElementText(n2.el, n2.children)
    }
  }
  
  // 快速路径:如果只有 class 变化
  if (patchFlag & PatchFlags.CLASS) {
    patchClass(n2.el, n2.props.class)
  }
  
  // 动态子节点优化
  if (dynamicChildren) {
    patchBlockChildren(n1.dynamicChildren, dynamicChildren)
  } else {
    // 全量 Diff
    patchChildren(n1, n2, n2.el)
  }
}
3.2 树结构压平优化
// Vue 3 会压平静态节点,只追踪动态节点
const vnode = {
  type: 'div',
  children: [
    { type: 'header', children: '静态头部' },     // 静态 - 被压平
    { type: 'nav', children: '静态导航' },         // 静态 - 被压平  
    { type: 'main', children: dynamicContent },    // 动态 - 被追踪
    { type: 'footer', children: '静态底部' }       // 静态 - 被压平
  ],
  // 只包含动态子节点的引用,Diff 时只比较这些节点
  dynamicChildren: [
    /* 只引用 main 节点 */
  ]
}

阶段 4:Patch 更新真实 DOM

// Patch 过程的简化实现
function patch(oldVNode, newVNode, container) {
  if (!oldVNode) {
    // 挂载新节点
    mountElement(newVNode, container)
  } else {
    // 更新现有节点
    patchElement(oldVNode, newVNode)
  }
}

function mountElement(vnode, container) {
  const { type, props, children, shapeFlag } = vnode
  
  // 1. 创建 DOM 元素
  const el = vnode.el = document.createElement(type)
  
  // 2. 设置属性
  if (props) {
    for (const key in props) {
      patchProp(el, key, null, props[key])
    }
  }
  
  // 3. 处理子节点
  if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
    // 文本子节点
    el.textContent = children
  } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    // 多个子节点
    mountChildren(children, el)
  }
  
  // 4. 插入到容器
  container.appendChild(el)
}

function patchProp(el, key, prevValue, nextValue) {
  if (key === 'class') {
    el.className = nextValue
  } else if (key === 'style') {
    // 处理样式
    for (const styleName in nextValue) {
      el.style[styleName] = nextValue[styleName]
    }
  } else if (key.startsWith('on')) {
    // 处理事件
    const eventName = key.slice(2).toLowerCase()
    el.addEventListener(eventName, nextValue)
  } else {
    // 其他属性
    el.setAttribute(key, nextValue)
  }
}

Vue 3 vs Vue 2 虚拟 DOM 核心区别

1. 编译时优化

优化特性Vue 2Vue 3
静态提升❌ 无✅ 静态节点提升到外部
Patch Flags❌ 无✅ 标识动态内容
树结构压平❌ 无✅ 压平静态节点
事件缓存❌ 无✅ 缓存事件处理器

2. 运行时优化

// Vue 2 - 全量 Diff
function updateChildren(oldCh, newCh) {
  // 双端比较算法,但仍然是全量比较
  // 需要比较所有子节点,无论是否静态
}

// Vue 3 - 基于标志的优化 Diff
function patchElement(n1, n2) {
  // 根据 patchFlag 选择优化路径
  if (n2.patchFlag > 0) {
    // 快速路径:只更新标志指定的内容
    if (n2.patchFlag & PatchFlags.TEXT) {
      // 只更新文本
    }
    if (n2.patchFlag & PatchFlags.CLASS) {
      // 只更新 class
    }
    // ... 其他标志
  } else {
    // 全量比较(较少情况)
    fullDiff(n1, n2)
  }
}

3. 内存和性能优化

// Vue 3 的优化效果示例
const App = {
  template: `
    <div>
      <header>静态头部</header>      <!-- 提升到外部 -->
      <main>{{ dynamicContent }}</main> <!-- 动态,有 patchFlag -->
      <footer>静态底部</footer>      <!-- 提升到外部 -->
    </div>
  `
}

// Vue 3 生成的虚拟 DOM 只追踪动态节点
const vnode = {
  dynamicChildren: [
    /* 只包含 main 节点的引用 */
  ]
  // 静态的 header 和 footer 不会被重复创建和比较
}

虚拟 DOM 转换过程简述

Vue 3 虚拟 DOM 转换过程可以概括为以下步骤:

  1. 模板编译:Vue 模板被编译为优化过的渲染函数
  2. 创建 VNode:执行渲染函数,生成带有优化标志的虚拟 DOM 树
  3. Diff 比较:基于 Patch Flags 和树压平进行最小化比较
  4. Patch 更新:根据比较结果,只更新真正变化的部分到真实 DOM
  5. 建立关联:虚拟节点保存对应的真实 DOM 引用供下次更新使用

整个过程通过编译时优化和运行时标志系统,实现了比 Vue 2 更高效的更新机制。

总结

Vue 3 的虚拟 DOM 通过编译时优化 + 运行时标志系统,实现了:

  • 🚀 更快的更新速度(比 Vue 2 快 1.3-2 倍)
  • 💾 更少的内存占用(树压平减少节点创建)
  • 🎯 更精准的更新(Patch Flags 指导最小化更新)
  • 📦 更好的 Tree Shaking(移除未使用的代码)

这些优化使得 Vue 3 在保持声明式开发体验的同时,提供了接近原生 JavaScript 的性能。

nextTick()

等待下一次 DOM 更新刷新的工具方法。

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

  • 详细信息

    当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

    nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

  • 示例

    <script setup>
    import { ref, nextTick } from 'vue'
    
    const count = ref(0)
    
    async function increment() {
      count.value++
    
      // DOM 还未更新
      console.log(document.getElementById('counter').textContent) // 0
    
      await nextTick()
      // DOM 此时已经更新
      console.log(document.getElementById('counter').textContent) // 1
    }
    </script>
    
    <template>
      <button id="counter" @click="increment">{{ count }}</button>
    </template>
    

1.Vue 的异步更新机制

  • 数据变化触发响应式更新:修改 Vue 实例的响应式数据时,会触发其 setter 方法,通知依赖更新。
  • 异步批量更新:Vue 不会立即更新 DOM,而是将 DOM 更新操作放入队列,在下一个事件循环中批量执行。
  • 优势:避免重复渲染,提升性能(例如连续修改 10 次数据,只触发一次 DOM 更新)。

2. nextTick 的执行时机

  • 队列清空后执行:当所有数据变化对应的 DOM 更新完成后,执行 nextTick 中的回调。
  • 基于微任务实现:Vue 2 使用 Promise.then 或 MutationObserver(降级为 setTimeout),Vue 3 直接使用 Promise.then
  • 执行顺序:微任务 → DOM 更新 → nextTick 回调 → 宏任务(如 setTimeout)。

3. 什么需要 nextTick?**

    • Vue 的 DOM 更新是异步的,修改数据后无法立即获取更新后的 DOM。

4. extTick 与 setTimeout 的区别?**

    • nextTick 基于微任务,在 DOM 更新后立即执行;setTimeout 是宏任务,执行时机晚于 nextTick

5. vue 3与 Vue 2 的 nextTick 有何不同?**

    • Vue 3 直接使用 Promise.then 实现,且支持 await nextTick() 语法。

任务队列分为两种:

  • 宏任务:setTimeout、setInterval、I/O操作、UI渲染等
  • 微任务:Promise.then、MutationObeserver

JS事件循环的工作流程:

  1. 执行栈所有同步代码执行完毕
  2. 处理“微任务队列”中的所有任务(直到队列为空)
  3. 执行一个宏任务(UI渲染)
  4. 重复步骤2和3