Vue
MVC、MVP 和 MVVM 模式
在前端开发中,MVC(Model-View-Controller)、MVP(Model-View-Presenter)和 MVVM(Model-View-ViewModel)是常见的架构模式,用于组织和管理应用程序的代码。它们都旨在实现将应用程序的不同部分分离开来,以提高代码的可维护性、可测试性和可扩展性。
MVC(Model-View-Controller)
- 模型(Model):负责应用程序的数据结构、逻辑和数据库交互等。
- 视图(View):负责应用程序的用户界面呈现,通常由 HTML 和 CSS 组成。
- 控制器(Controller):充当模型和视图之间的中介,处理用户输入并更新模型和视图。在 Web 应用中,通常是处理路由和 HTTP 请求的部分。
MVC 模式的核心思想是将应用程序分为三个不同的组件,每个组件有其专注的责任。这样的分离使得代码更易于管理和维护。
MVP(Model-View-Presenter)
- 模型(Model):同 MVC 中的模型,负责应用程序的数据。
- 视图(View):同 MVC 中的视图,负责用户界面呈现。
- 主持人(Presenter):取代了 MVC 中的控制器,负责处理用户输入、更新模型并在必要时更新视图。Presenter 的作用是解耦视图和模型,使其更容易进行单元测试。
MVP 模式在 MVC 的基础上进一步强调了视图和模型之间的分离,并引入了 Presenter 作为中介,使得视图更加 passively 与模型交互。
MVVM(Model-View-ViewModel)
- 模型(Model):同 MVC 和 MVP 中的模型,负责应用程序的数据。
- 视图(View):同 MVC 和 MVP 中的视图,负责用户界面呈现。
- 视图模型(ViewModel):MVVM 中最为核心的部分。视图模型是视图的抽象,负责从模型中获取数据并准备视图所需的数据。视图模型通常是纯粹的 JavaScript 对象,它实现了视图和模型之间的数据绑定。
MVVM 模式相比 MVC 和 MVP 更进一步,它引入了视图模型的概念,通过数据绑定实现了视图和模型之间的双向绑定。这样,当模型发生变化时,视图会自动更新,反之亦然。
观察者模式和发布订阅模式
发布订阅模式其实属于广义上的观察者模式。
在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。
而在发布订阅模式中,发布者和订阅者之间多了一个调度中心。调度中心一方面从发布者接收事件,另一方面向订阅者发布事件,订阅者需要在调度中心中订阅事件。通过调度中心实现了发布者和订阅者关系的解耦。使用发布订阅者模式更利于我们代码的可维护性。
Vue 双向数据绑定原理
Vue.js 中的双向数据绑定是其最显著的特性之一,它允许视图层(HTML)和数据模型之间的自动同步。这意味着当数据模型发生变化时,视图会自动更新,并且当用户在视图中输入内容时,数据模型也会相应地更新。
实现双向数据绑定的原理如下:
数据劫持(Data Observation): Vue.js 使用了一个名为“响应式系统(Reactivity System)”的机制来实现数据劫持。当你创建一个 Vue 实例时,Vue 会遍历数据对象的所有属性,使用
Object.defineProperty()
方法将它们转换为 getter 和 setter。通过这种方式,当属性被访问或修改时,Vue 能够捕获到,并执行相应的操作。在 Vue.js 2 中 使用
Object.defineProperty()
来进行数据劫持,有一些对属性的操作无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,Vue.js 2 内部通过重写函数解决了这个问题。在 Vue.js 3 中,数据劫持的实现方式与 Vue.js 2 有所不同,主要通过 Proxy 对象来实现。
Proxy 对象
在 Vue.js 3 中,使用了 ES6 中的 Proxy 对象来替代Object.defineProperty()
。Proxy 对象允许你创建一个代理,用于定义基本操作的自定义行为(例如属性查找、赋值等)。通过 Proxy 对象,Vue.js 能够劫持数据对象的操作,并在数据发生变化时进行相应的处理。Reflect API
为了与 Proxy 对象配合使用,Vue.js 3 还使用了 Reflect API。Reflect API 提供了一组用于操作对象的方法,例如Reflect.get()
、Reflect.set()
等。Vue.js 3 在数据劫持时使用 Reflect API 来执行实际的操作,而不是直接在目标对象上操作,这样可以更加灵活地处理各种情况。优势
- Proxy 对象更加强大和灵活,可以监听更多类型的操作,例如数组的变化。
- Proxy 对象可以监听整个对象的变化,而不仅仅是属性的变化。
- Proxy 对象的性能通常比
Object.defineProperty()
更好,特别是在大型数据对象上的性能表现更佳。
虚拟 DOM(Virtual DOM): Vue.js 使用虚拟 DOM 来提高性能。虚拟 DOM 是一个 JavaScript 对象,它对真实 DOM 进行抽象,然后在内存中对其进行操作。当数据发生变化时,Vue.js 会生成一个新的虚拟 DOM,并与之前的虚拟 DOM 进行比较,找出变化的部分,然后只更新变化的部分到真实 DOM 中,而不是重新渲染整个页面。
事件监听(Event Listening): Vue.js 会在模板编译时,为每个模板中的指令(例如
v-model
)或事件绑定(例如@input
)创建对应的监听器。当用户输入内容时,Vue.js 会通过这些监听器捕获到事件,并更新数据模型。数据绑定(Data Binding): 在 Vue.js 中,你可以使用
v-model
指令来实现双向数据绑定。v-model
指令在表单元素上创建双向数据绑定,将表单元素的值绑定到数据模型上,并且当表单元素的值发生变化时,数据模型也会更新。
综上所述,Vue.js 的双向数据绑定是通过数据劫持、虚拟 DOM、事件监听和数据绑定等机制来实现的。这些机制使得数据模型与视图之间的同步变得简单高效,极大地提高了开发效率。
虚拟 DOM(Virtual DOM)
虚拟 DOM 是一个 JavaScript 对象,它是对真实 DOM 的抽象。通过使用虚拟 DOM,Vue.js 能够在内存中维护一颗以 JavaScript 对象为节点的树,这颗树与真实 DOM 对应着页面的结构。
当数据发生变化时,Vue.js 首先会生成一个新的虚拟 DOM 树,然后通过 diff 算法比较新的虚拟 DOM 树与旧的虚拟 DOM 树的差异。通过比较,Vue.js 找出需要更新的部分,并只更新这些部分到真实 DOM 上,而不是重新渲染整个页面。这样可以大大提高页面的渲染性能和响应速度。
优势:
- 提高性能:由于操作虚拟 DOM 可以避免直接操作真实 DOM,减少了浏览器的重绘和回流,从而提高了页面的渲染性能。
- 简化操作:通过使用虚拟 DOM,开发者可以像操作普通 JavaScript 对象一样操作 DOM,简化了操作复杂 DOM 结构的过程。
- 跨平台兼容性:虚拟 DOM 不依赖于具体的浏览器平台,因此可以在不同的平台上运行,提高了跨平台兼容性。
diff 算法
在 Vue.js 中,diff 算法是用于比较虚拟 DOM 树的变化,并最小化 DOM 操作的一种算法。这种算法可以高效地找出虚拟 DOM 树中变化的部分,并只更新这些部分到真实 DOM,而不是重新渲染整个页面。
概念
diff 算法是一种通过比较两棵树的差异,找出最小的变更,并应用到真实 DOM 上的技术。在 Vue.js 中,这两棵树分别是当前虚拟 DOM 树和更新后的虚拟 DOM 树。算法流程
- 深度优先遍历:Vue.js 使用深度优先遍历算法来逐层比较虚拟 DOM 树的节点。
- 同级比较:在遍历过程中,Vue.js 会比较同级节点之间的差异,判断节点是否需要更新、新增或删除。
- 唯一 key:在比较过程中,Vue.js 会使用节点的唯一 key 来确定节点的身份,以确保正确地识别出节点的变化。
- 更新策略:根据节点的变化类型(新增、删除、移动、更新等),Vue.js 会采取不同的更新策略,最大程度地减少 DOM 操作的次数。
- 递归处理:如果节点发生变化,则递归处理其子节点,直到完成整个树的比较。
优化策略
- Key 的重要性:使用唯一 key 可以帮助 Vue.js 更准确地识别节点的变化,提高 diff 算法的效率。
- 移动操作的优化:在节点移动时,Vue.js 会尽可能地复用已存在的 DOM 元素,而不是直接删除和重新创建,以减少 DOM 操作。
- 批量处理操作:Vue.js 会将多个变更操作合并为一个批量操作,减少了对真实 DOM 的操作次数,提高了性能。
时间复杂度
Vue.js 中的 diff 算法的时间复杂度为 O(n),其中 n 表示节点的数量。这是由于 diff 算法会遍历整个虚拟 DOM 树,并比较节点之间的差异,因此其时间复杂度与节点的数量成正比。
Vue 2 的生命周期
Vue 的生命周期指的是组件从创建到销毁的一系列的过程,一共有 8 个阶段,分别是创建前、创建后、挂载前、挂载后、更新前、更新后、销毁前和销毁后,每个阶段对应了一个生命周期的钩子函数。开发者可以在这些钩子函数中执行自定义的操作。
创建阶段(Creation)
- beforeCreate:在实例初始化之后、数据观测(data observation)和 event/watcher 事件配置之前被调用。此时组件实例还未初始化,因此无法访问到 data 和 methods 等选项。
- created:在实例创建完成后被立即调用。这个阶段完成了数据的观测、属性和方法的运算,但是尚未挂载到 DOM 上。
挂载阶段(Mounting)
- beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。
- mounted:在挂载结束后被调用:el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用此钩子函数。此时组件已经挂载到 DOM 上,可以访问到 DOM 元素。(请求数据)
更新阶段(Updating)
- beforeUpdate:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中对数据进行修改,但是不会触发附加的重新渲染过程。
- updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。此时组件的 DOM 已经更新完毕,可以执行依赖于 DOM 的操作。
销毁阶段(Destruction)
- beforeDestroy:在实例销毁之前调用。在这一步,实例仍然完全可用。(清除定时器、解绑全局事件)
- destroyed:在实例销毁之后调用。此时组件已经从 DOM 中移除,所有的事件监听器和子实例也都被销毁。
错误处理阶段(Error Handling)
- errorCaptured:当捕获一个来自子孙组件的错误时被调用。可以返回 false 以阻止该错误继续向上传播。
当使用 keep-alive
组件的时候,还有两个钩子函数,分别是 activated
和 deactivated
。用 keep-alive
包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated
钩子函数,命中缓存渲染后会执行 actived
钩子函数。
Vue 3 的生命周期
Vue 3 的生命周期与 Vue 2 相比有了一些变化。在 Vue 3 中,生命周期钩子被分为两类:
- 选项式 API 生命周期钩子:这些钩子与 Vue 2 中的生命周期钩子相同,但它们是作为选项传递给 Vue 组件的。
- 组合式 API 生命周期钩子:这些钩子是新的,它们允许你在组件的任何地方使用它们。
选项式 API 生命周期钩子
- beforeCreate:在组件实例被创建之前被调用。
- created:在组件实例被创建之后被调用。
- beforeMount:在组件实例被挂载到 DOM 之前被调用。
- mounted:在组件实例被挂载到 DOM 之后被调用。
- beforeUpdate:在组件实例更新之前被调用。
- updated:在组件实例更新之后被调用。
- beforeDestroy:在组件实例被销毁之前被调用。
- destroyed:在组件实例被销毁之后被调用。
组合式 API 生命周期钩子
- onBeforeMount:与
beforeMount
钩子相同。 - onMounted:与
mounted
钩子相同。 - onBeforeUpdate:与
beforeUpdate
钩子相同。 - onUpdated:与
updated
钩子相同。 - onBeforeUnmount:与
beforeDestroy
钩子相同。 - onUnmounted:与
destroyed
钩子相同。 - onErrorCaptured:在组件中捕获到错误时被调用。
- onRenderTracked:在组件的渲染被追踪时被调用。
生命周期钩子的使用
以下是一些使用生命周期钩子的示例:
- 在
beforeCreate
钩子中初始化数据 - 在
mounted
钩子中订阅数据变化 - 在
beforeUpdate
钩子中执行 DOM 操作 - 在
updated
钩子中进行网络请求 - 在
onErrorCaptured
钩子中处理错误
keep-alive 组件
<keep-alive>
是 Vue.js 中的一个内置组件,用于保留组件状态或避免重新渲染。它可以将动态组件包裹起来,并缓存不活动的组件实例,而不会销毁它们。当组件再次被访问时,不需要重新创建,而是直接从缓存中取出并重新渲染,从而提高了性能和用户体验。
下面是 <keep-alive>
组件的主要特性和用法:
特性
- 包裹动态组件:
<keep-alive>
主要用于包裹动态组件,可以是通过<component>
标签动态渲染的组件,也可以是通过路由动态加载的组件。 - 缓存不活动组件:一旦组件被包裹在
<keep-alive>
内部,当组件不活动时(例如被切换到其他页面),它将被缓存起来而不是被销毁,从而保留其状态。 - 激活钩子:
<keep-alive>
组件提供了两个钩子函数activated
和deactivated
,用于在组件被激活和停用时进行操作。
- 包裹动态组件:
用法
html<keep-alive> <component :is="currentComponent"></component> </keep-alive>
在上面的示例中,
<component>
标签渲染的组件将被<keep-alive>
组件包裹起来,从而实现了组件的缓存。currentComponent
是一个动态绑定的组件名称或组件对象,根据实际需求动态切换不同的组件。钩子函数
- activated:在组件被激活时调用,即组件被插入到 DOM 中并进行显示。
- deactivated:在组件被停用时调用,即组件被从 DOM 中移除并隐藏起来。
使用 <keep-alive>
组件可以有效地提高页面性能,特别是在大型单页应用中,当页面切换频繁时能够减少不必要的组件销毁和重新创建,从而加快页面加载速度和提升用户体验。
Vue 组件通信
Props / Emit(父子组件通信)
- Props:父组件通过 props 向子组件传递数据。子组件通过 props 接收数据,并根据传递的数据进行渲染。
- Emit:子组件通过 $emit 方法触发事件,将需要传递给父组件的数据作为参数传递给父组件。父组件通过监听子组件的事件,从而获取子组件传递的数据。
Provide / Inject(祖先组件向后代组件传递数据)
- Provide:祖先组件通过 provide 选项向后代组件提供数据。提供的数据可以是基本数据类型或对象,后代组件通过 inject 选项接收提供的数据。
- Inject:后代组件通过 inject 选项接收来自祖先组件提供的数据。
$parent / $children
- $parent 属性可以让子组件访问其父组件。
- $children 属性可以让父组件访问其所有子组件。
$attrs / $listeners(透传属性和事件)
- $attrs:父组件通过 v-bind="$attrs" 将属性透传给子组件,子组件可以通过 $attrs 获取父组件传递的属性。
- $listeners:父组件通过 v-on="$listeners" 将事件透传给子组件,子组件可以通过 $listeners 获取父组件传递的事件。
Ref / Refs
- Ref:父组件通过 ref 属性获取子组件的引用,从而调用子组件中的方法或访问子组件的属性。
- Refs:在 Vue 3 中,可以通过 ref 函数创建一个响应式的引用,并通过 ref 绑定到子组件中,从而实现对子组件的访问和操作。
Event Bus(事件总线)
- 创建一个全局的 Vue 实例作为事件总线,在组件中通过该实例进行事件的发布和订阅,实现组件之间的通信。但是,事件总线在大型应用中会导致事件的管理和维护变得复杂,因此在小型应用中使用较为合适。
Vuex / Pinia(状态管理)
Vuex 和 Pinia 是 Vue.js 的官方状态管理库,用来实现全局状态管理,从而实现任意组件之间的数据共享。
computed 和 watch
在 Vue.js 中,computed
和 watch
是用于监听数据变化并执行相应逻辑的两种重要属性。它们都与响应式数据绑定相关,但用途和使用方式略有不同。
Computed(计算属性)
computed
是一个 Vue 实例的属性,用于声明一个计算属性。它的值是一个函数,该函数可以返回计算后的值,Vue.js 会自动将其缓存,只有在依赖的响应式数据发生改变时才会重新计算。computed
主要用于派生出一个新的数据,以供模板中使用,它具有以下特点:
- 自动缓存:当依赖的响应式数据没有发生变化时,
computed
属性会直接返回上一次的计算结果,而不会重新执行计算函数。 - 懒计算:只有当
computed
属性在模板中被引用时,计算函数才会被执行,这样可以避免不必要的计算。 - 响应式:
computed
属性会自动跟踪依赖的响应式数据,当这些数据发生变化时,computed
会重新计算其值。
// 示例
computed: {
fullName() {
return this.firstName + ' ' + this.lastName;
}
}
Watch(监听器)
watch
是一个 Vue 实例的选项,用于监听一个指定的数据,并在该数据变化时执行特定的逻辑。它的值可以是一个函数或者一个对象,对象中的属性对应着需要监听的数据,值为回调函数。watch
主要用于监听数据的变化并执行异步或复杂的操作,它具有以下特点:
- 可监听对象和深度监听:可以监听对象和数组的变化,并通过设置
deep: true
进行深度监听。 - 更灵活的操作:可以执行一些比较复杂的操作,例如异步请求、定时器等。
- 监听特定属性:可以通过字符串形式或函数形式指定需要监听的属性。
// 示例
watch: {
firstName(newVal, oldVal) {
console.log('firstName 改变了');
},
'obj.prop': {
handler(newVal, oldVal) {
console.log('obj.prop 改变了');
},
deep: true
}
}
区别和适用场景
computed
适用于派生出一个新的数据,通常是依赖于一个或多个响应式数据计算而来的数据,它具有自动缓存和懒计算的特性,适合用于模板中频繁使用的数据。watch
适用于监听一个指定的数据的变化,并执行一些复杂的逻辑,例如异步操作、定时器等。适合处理一些需要特定操作的情况,比如数据变化时发起网络请求等。
vue-router 中的钩子函数
Vue Router 中的钩子函数是用于在路由导航过程中执行特定逻辑的函数。它们允许您在路由发生变化前或后执行某些操作,例如验证导航、加载数据、修改页面标题等。
全局前置守卫
- beforeEach(to, from, next):在路由跳转之前被调用,可以用于进行全局的路由导航守卫逻辑。例如,验证用户是否登录或是否有权限访问该路由。
- afterEach(to, from):在路由跳转完成之后被调用,无论是跳转到相同的路由还是跳转到不同的路由。
- beforeResolve(to, from, next):在路由跳转之前被调用,但在路由组件内的
beforeRouteEnter
守卫之前调用。用于确保所有异步路由组件都被解析。
路由独享守卫
- beforeEnter(to, from, next):在路由独享守卫中定义,只在该路由生效。在路由被激活时调用,可用于对特定路由进行验证或逻辑处理。
组件内的守卫
- beforeRouteEnter(to, from, next):在路由进入该组件前被调用,此时组件实例尚未创建,因此无法访问
this
。可以通过next(vm => {})
获取组件实例。 - beforeRouteUpdate(to, from, next):在当前路由改变,但是该组件被复用时被调用。例如,从
/user/1
导航到/user/2
时,同一个组件实例被复用。 - beforeRouteLeave(to, from, next):在离开该路由前被调用,可以用于在离开当前页面时执行一些清理操作或提醒用户。如果确认离开,需要调用
next()
,否则可以调用next(false)
或者传递一个路由对象来重新定向。
- beforeRouteEnter(to, from, next):在路由进入该组件前被调用,此时组件实例尚未创建,因此无法访问
$route 和 $router
$route
是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。$router
是“路由实例”对象包括了路由的跳转方法,钩子函数等。
Vue 修饰符
在 Vue.js 中,修饰符是用于改变指令行为或者监听器行为的特殊标记,它们可以与指令一起使用,以便更灵活地控制指令的行为。以下是常用的 Vue 修饰符及其作用:
v-on 修饰符
- .stop:阻止事件继续传播。
- .prevent:阻止事件的默认行为。
- .capture:事件捕获模式,即在捕获阶段处理事件。
- .self:当事件是从触发元素自身触发时才触发回调。
- .once:只触发一次事件处理器,之后移除。
- .passive:告诉浏览器不要阻止默认事件的默认行为,用于提高滚动性能。
v-bind 修饰符:
- .prop:用于绑定 DOM 属性。
- .camel:将 kebab-case 特性名转换为 camelCase。
- .sync:用于实现子组件和父组件之间双向数据绑定。
v-model 修饰符:
- .lazy:在 input 事件中同步改变数据而不是在 change 事件中。
- .number:将输入值转为数值类型。
- .trim:自动过滤输入的首尾空白字符。
v-show 修饰符:
- .sync:用于实现子组件和父组件之间双向数据绑定。
v-text 和 v-html 修饰符:
- .once:只渲染一次,之后不再更新。
v-slot 修饰符:
- #default:默认插槽。
- #name:具名插槽。
事件修饰符:
- .stop:阻止事件继续传播。
- .prevent:阻止事件的默认行为。
- .capture:事件捕获模式。
- .self:当事件是从触发元素自身触发时才触发回调。
- .once:只触发一次事件处理器。
- .passive:告诉浏览器不要阻止默认事件的默认行为。
Vue 中 key 值的作用
在 Vue.js 中,key
是用于帮助 Vue 识别 VDOM 中的每个节点的特殊属性。当 Vue 用 v-for
正在更新已渲染过的元素列表时,它默认使用“就地复用”策略。如果数据项的顺序被改变,Vue 将不是移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
然而,这种默认的模式会导致一个问题:当一个数组中的某些元素的标识(即 key 值)发生改变时,Vue 不会知道哪个元素被修改了,导致页面的错误渲染。
因此,为了给 Vue 提供一个提示,以便它跟踪每个节点的身份,从而重用和重新排序现有元素,需要为每项提供一个唯一的 key 值。这个 key 值需要是每项数据在列表中唯一的标识符,通常是数据中的某个唯一值,比如数据项的 ID。
key
值的作用主要有以下几个方面:
- 跟踪节点身份:Vue 使用
key
属性来识别节点的身份,确保在数据改变时能够正确地重用和重新排序现有元素,而不会导致错误的渲染。 - 优化性能:通过正确使用
key
属性,可以提高 Vue 的性能,减少不必要的 DOM 操作,尤其是在处理大型列表或频繁更新的情况下。 - 避免重复渲染:使用
key
属性能够避免相同的数据项被重复渲染,确保每个节点的状态正确。
nextTick
在 Vue 中,nextTick
是一个异步方法,用于在 DOM 更新后执行回调函数。当你修改了 Vue 实例中的数据,Vue 并不会立即更新 DOM。相反,它会等待数据变化完成,然后批量异步更新 DOM。在这种情况下,如果你想在 DOM 更新完成后执行一些操作,就可以使用 nextTick
方法。
为什么 v-for 和 v-if 不建议结合使用
性能问题:当
v-for
和v-if
结合使用时,Vue 将会为每个循环项都执行v-if
条件判断。这意味着在每次渲染循环时,都会对条件进行判断,可能会导致不必要的性能开销,特别是在循环中有大量数据时。这可能会使页面变得很慢。可读性问题:当
v-for
和v-if
结合使用时,代码可能变得更难理解和维护。因为v-if
可能会隐藏或显示v-for
中的某些项目,这会使模板的逻辑变得复杂。潜在的 bug:在某些情况下,
v-if
和v-for
结合使用可能会导致意外行为。例如,当v-if
控制的元素在v-for
循环中被移动或重新排序时,可能会导致渲染结果不符合预期。
为了避免这些问题,Vue 官方文档建议尽量避免在同一个元素上同时使用 v-for
和 v-if
。可以通过以下几种方法来解决:
计算属性:将条件逻辑移至计算属性中,在模板中只使用
v-for
。这样可以使模板更简洁,也更易于理解和维护。过滤数据:在数据层面过滤掉不需要的项,然后在模板中只使用
v-for
渲染。这样可以避免在模板中进行条件判断,提高性能。使用
<template>
元素:在某些情况下,你可能需要将v-if
放在包裹元素上,而不是在循环的元素上。可以使用<template>
元素作为容器来实现这一点。html<ul> <template v-for="user in users" :key="user.id"> <li v-if="user.isActive">{{ user.name }}</li> </template> </ul>
Vue 2 和 Vue 3 的区别
Typescript 支持
- Vue 3 对 TypeScript 的支持更加完善。Vue 3 的源码是使用 TypeScript 编写的,并且提供了更好的类型推断和类型支持,使得开发者可以更轻松地使用 TypeScript 来开发 Vue 应用。
API 设计
- Vue 3 引入了 Composition API(组合 API),这是一个全新的 API 设计,使得组件的逻辑更加模块化。相比 Vue 2 中的 Options API(选项 API),Composition API 提供了更灵活的组织代码的方式,特别适合于大型应用程序的开发。
响应式原理
- 在 Vue.js 2 中 使用
Object.defineProperty()
来进行数据劫持,有一些对属性的操作无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,Vue.js 2 内部通过重写函数解决了这个问题。 - 在 Vue.js 3 中,使用了 ES6 中的 Proxy 对象来替代
Object.defineProperty()
。
- 在 Vue.js 2 中 使用
生命周期变化
Vue 2 Vue 3 beforeCreate
用 setup()
替代created
用 setup()
替代beforeMount
onBeforeMount
mounted
onMounted
beforeUpdate
onBeforeUpdate
updated
onUpdated
beforeDestroy
onBeforeUnmount
destroyed
onUnmounted
性能提升
特性 Vue 2 Vue 3 打包体积 全量引入(即使不用某些功能) Tree-shaking(按需引入) 虚拟 DOM 全量对比差异 静态标记 + 动态追踪(diff 更快) SSR 性能 一般 提升 2~3 倍 新增特性
Vue 3 新增 说明 Teleport 将组件渲染到 DOM 任意位置(如弹窗) Suspense 异步组件加载的等待状态处理 Fragment 支持组件多根节点(无需外层 div) 多个 v-model 同一组件支持多个 v-model
绑定Tree-shaking 改进
- Vue 3 在设计上更加友好于 Tree-shaking,在构建过程中能够更轻松地剔除未使用的代码,减少最终的包大小。
全局 API 的修改
- Vue 3 对一些全局 API 进行了修改。例如,Vue 3 中的
Vue.observable()
替代了 Vue 2 中的Vue.set()
和Vue.delete()
,使得在响应式对象上进行操作更加简洁明了。
- Vue 3 对一些全局 API 进行了修改。例如,Vue 3 中的
Vue 性能优化方案
组件优化
- 按需加载(懒加载):使用
defineAsyncComponent
或import()
进行异步组件加载,减少首屏加载时间。 - 避免不必要的组件重渲染:使用
v-once
(静态内容)、v-memo
(缓存计算)、computed
进行依赖追踪,减少渲染开销。 - 使用
key
提高 Diff 算法效率:v-for
遍历时指定唯一key
,减少 DOM 操作。
数据响应优化
- 合理使用
computed
代替methods
:避免重复计算,提高性能。 - 避免深度监听:使用
immediate: false
,或监听具体属性而不是整个对象。 - 使用
shallowRef
/shallowReactive
:减少 Vue 深层递归监听,适用于浅层数据变化。
路由优化
- 路由懒加载:使用
defineAsyncComponent()
或() => import('xxx.vue')
进行按需加载。 - 路由缓存(keep-alive):缓存页面组件,避免重复渲染,适用于多次切换的页面。
事件与 DOM 优化
- 使用事件委托:减少事件绑定,适用于列表或动态元素(
@click.stop
仅阻止当前元素)。 - 减少 DOM 操作:尽量通过 Vue 的响应式数据驱动 UI 变化,而非
document.querySelector()
手动操作 DOM。 - 使用虚拟滚动(Virtual Scrolling):处理大数据列表(如 Vue Virtual Scroller)。
网络与静态资源优化
- 开启 gzip 压缩:通过 Webpack
compression-webpack-plugin
或 Nginx 配置压缩。 - 使用 CDN 加速:将 Vue、Element-UI 等静态资源放入 CDN。
- 按需加载 UI 组件库:如
import { Button } from 'element-plus'
,避免全量引入。 - 图片优化:使用
webp
格式、懒加载(v-lazy
)、雪碧图(sprite)等。
Webpack/Vite 构建优化
- Tree Shaking:确保
sideEffects: false
,去除未使用代码。 - 代码分割(Code Splitting):
splitChunks
拆分代码,减少主包体积。 - 开启缓存:使用
cache-loader
,减少二次打包时间。 - 使用 Vite:更快的 HMR(热更新),优化开发体验。
SSR / 静态生成
- 使用 Nuxt.js 进行 SSR 或静态生成(SSG):提升首屏加载速度,优化 SEO。
- 开启预渲染:对于小型项目,使用
prerender-spa-plugin
生成静态 HTML。