Vue2与Vue3区别
文章目录
- Vue2与Vue3区别
-
- 1.性能优化
- 2.组件的创建方式(组合式API)
- 3.生命周期
- 4.全局 API 的变化
- 5.Teleport(传送门)的引入
- 6.多个根节点的支持
- 7.异步组件加载(Suspense 组件)
- 8.全局状态管理
- 9.自定义指令的变化
- 10.编译器的变化
- 11.Typescript 支持
- 12.事件处理的变化
- 13.TSX 支持
- 14.Emits 选项
- 15.v-model 的改进
- 16.打包优化
- 17.动态注入(Provide/Inject)的改进
- 18.弃用和移除的功能
- 19.自定义渲染器 API 的改进
- 20.新增patchFlag字段
- 21.错误处理的改进
- 22.事件缓存
- 23.响应式原理
1.性能优化
- Vue3: Vue3 在性能方面有很大的改进。它引入了响应式系统的重写,使用 Proxy 代替 Object.defineProperty,从而提高了性能。另外,编译器优化、懒加载等新特性也有助于提高应用程序性能。
- Vue2: Vue2 使用 Object.defineProperty 来实现响应式数据,相比 Vue3,它的性能稍显滞后。
2.组件的创建方式(组合式API)
- Vue3: Vue3 支持组合式API,这是一种新的组件设计方式,可以更灵活地组织代码,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。
- Vue2: Vue2 使用选项式 API,较为传统,有时在大型应用中难以维护。一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期钩子等),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。
// Vue3 中的组件
<script>
import {
ref } from 'vue';
export default {
setup() {
const count = ref(0);
return {
count,
increment: () => {
count.value++ },
};
},
};
</script>
// Vue2 中的组件
<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count++;
},
},
};
</script>
3.生命周期
Vue3中取消了beforeCreated和created,添加了setup函数,其负责设置组件的初始状态、响应式数据和执行其他逻辑。它在组件实例被创建之前被调用,并且接收两个参数:props
和 context
:
props
参数表示组件接收到的属性(props
),并且是只读的。context
参数包含了一些常用的属性和方法,如attrs
、slots
、emit
等。
由于setup就是初始化时执行的函数,所以可以替代beforeCreated和created的效用
常用生命周期对比:
vue2 | vue3 |
---|---|
beforeCreate | |
created | |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
并且再Vue3中提供了onServerPrefetch钩子,若在其中返回了一个Promise,服务端就可以等待Promise完成后再对该组件进行渲染
4.全局 API 的变化
- Vue3: Vue3 中一些全局 API 发生了变化,例如
Vue.component
变成了app.component
。 - Vue2: 在 Vue2 中,这些全局 API 使用 Vue 实例进行访问。
// Vue3 中的全局组件注册
const app = createApp(App);
app.component('my-component', MyComponent);
app.mount('#app');
// Vue2 中的全局组件注册
Vue.component('my-component', MyComponent);
new Vue({
el: '#app',
render: h => h(App),
});
5.Teleport(传送门)的引入
传统的 Vue 组件会被渲染到其父组件的 DOM 结构内,但是有时我们希望将组件渲染到 DOM 结构的不同位置,而不需要改变其父组件的布局。这就是 Teleport
的作用所在。
- Vue3: Vue3 引入了 Teleport,用于在组件内部的任何地方渲染内容到 DOM 中的其他位置,这在处理模态框等场景时非常有用。
- Vue2: Vue2 中没有 Teleport 的概念。
<!-- Vue3 中的 Teleport -->
<teleport to="body">
<Modal />
</teleport>
Teleport
组件将 Modal
组件渲染到了页面的 body
元素下。无论在组件内部的哪个位置使用 Teleport
,最终 Modal
组件都会被渲染到 body
元素下,而不是组件的父容器内部。
通过使用 Teleport
,你可以将组件渲染到任何合法的 DOM 元素位置,而不局限于其父组件的 DOM 结构。这对于实现全局的弹窗、模态框或通知等功能非常有用。
需要注意的是,Teleport
需要配合 to
属性来指定组件渲染的目标位置,该位置可以是一个有效的 CSS 选择器,或者是特殊的预定义值,如 "body"
、"#app"
等。
另外,需要确保目标位置在组件被挂载的时候是存在的,否则 Teleport
会抛出警告并在渲染期间失效。
总结来说,Teleport
组件使你能够非常灵活地将组件渲染到任意位置,而不受其父组件的限制。这为处理全局弹窗、模态框等提供了方便的解决方案。
6.多个根节点的支持
- Vue3: Vue3 支持组件有多个根节点。在vue3中支持了多个根组件的写法,原理是在编译阶段新增了判断,如果当前组件不只一个根元素,就添加一个
fragment
组件把这个多根组件的结构给包起来,相当于这个组件还是只有一个根节点 - Vue2: Vue2 中组件只能有一个根节点。vue2模板最后会被转换为VDOM,而VDOM是一个树形结构,所以需要唯一的根元素
<!-- Vue3 中的多个根节点 -->
<template>
<div></div>
<div></div>
</template>
<!-- Vue2 中只能有一个根节点 -->
<template>
<div>
<p></p>
</div>
</template>
7.异步组件加载(Suspense 组件)
- Vue3: Vue3 引入了
Suspense
组件,用于处理异步组件的加载状态,使得在异步操作完成之前可以展示备用内容。 - Vue2: Vue2 中没有专门处理异步组件加载状态的机制。
在 Vue 3 中,异步组件加载可以使用 Suspense
组件和 async setup
功能来实现。Suspense
组件可以用于处理异步组件的加载和显示占位符内容,而 async setup
可以在异步组件加载完成后执行相关逻辑。
下面是使用 Suspense
和 async setup
的示例:
首先,在父组件中使用 Suspense
包裹需要异步加载的子组件,并设置一个占位符(fallback)内容:
<template>
<Suspense>
<template #default>
<!-- 异步加载的子组件 -->
<AsyncComponent />
</template>
<template #fallback>
<!-- 加载时的占位符内容 -->
<div>Loading...</div>
</template>
</Suspense>
</template>
然后,在异步加载的子组件中使用 async setup
来定义组件的逻辑:
export default {
async setup() {
// 异步加载资源或执行其他异步操作
await someAsyncOperation();
// 返回组件的响应式数据和方法
return {
data: reactive({
}),
method() {
// ...
}
};
}
};
</script>
当父组件渲染时,Suspense
组件会检测到子组件的异步加载,并显示占位符内容(“Loading…”)。一旦异步加载完成,Suspense
组件会自动切换到子组件的内容,并显示子组件的内容。
通过使用 Suspense
和 async setup
,你可以更方便地处理异步组件加载过程中的状态,并在加载完成后执行相关逻辑。
8.全局状态管理
- Vue3: Vue3 中引入了
reactive
和ref
函数,使得在应用程序中管理全局状态变得更加简单。新的响应式系统使得 Vuex(Vue 的状态管理库)的需求降低。 - Vue2: 在 Vue2 中,通常需要使用 Vuex 来管理全局状态,但在较小的应用中可能会显得繁琐。
// Vue3 中的全局状态管理
import {
reactive } from 'vue';
const state = reactive({
count: 0,
});
9.自定义指令的变化
- Vue3: Vue3 中自定义指令的 API 发生了变化,使用
beforeMount
替代了 Vue2 中的bind
和inserted
钩子。 - Vue2: Vue2 中的自定义指令有
bind
和inserted
两个钩子。
// Vue3 中的自定义指令
app.directive('自定义指令', {
beforeMount(el, binding) {
// ...
},
});
// Vue2 中的自定义指令
Vue.directive('自定义指令', {
bind(el, binding) {
// ...
},
inserted(el, binding) {
// ...
},
});
10.编译器的变化
- Vue3: Vue3 的编译器进行了改进,支持更好的 Tree-shaking,减小了应用的体积。
- Vue2: Vue2 中的编译器不够灵活,难以实现有效的 Tree-shaking。
11.Typescript 支持
- Vue3: Vue3 对 Typescript 提供了更好的支持,使得在项目中使用 Typescript 更加方便。
- Vue2: Vue2 也支持 Typescript,但在一些方面可能不如 Vue3 灵活。
// Vue3 中的组件使用 Typescript
<script lang="ts">
import {
defineComponent, ref } from 'vue';
export default defineComponent({
data() {
return {
count: ref(0),
};
},
});
</script>
12.事件处理的变化
- Vue3: Vue3 中事件处理的参数变得更加一致,例如,事件参数现在总是作为第一个参数传递给处理函数。
- Vue2: 在 Vue2 中,事件处理的参数可能会有所不同,有时是事件对象,有时是自定义参数。
// Vue3 中的事件处理
<template>
<button @click="handleClick">Click me</button>
</template>
<script>
export default {
methods: {
handleClick(event,other) {
// event 是事件对象
},
},
};
</script>
// Vue2 中的事件处理
<template>
<button @click="handleClick">Click me</button>
</template>
<script>
export default {
methods: {
handleClick(customArg, event) {
// event 是事件对象
},
},
};
</script>
13.TSX 支持
- Vue3: Vue3 对 TSX(TypeScript JSX)提供了更好的支持,允许你使用 JSX 语法编写组件。
- Vue2: Vue2 不太友好地支持 TSX,使用的时候可能会遇到一些问题。
// Vue3 中的 TSX 支持
<script lang="tsx">
import {
defineComponent } from 'vue';
export default defineComponent({
render() {
return <div>Hello, TSX!</div>;
},
});
</script>
14.Emits 选项
- Vue3: Vue3 引入了
emits
选项,用于声明组件可以触发的事件,提高了事件的类型安全性。 - Vue2: Vue2 中并没有显式的方式来声明组件可以触发的事件。
// Vue3 中的 emits 选项
<script>
export default {
emits: ['custom-event'],
methods: {
handleClick() {
this.$emit('custom-event', /* data */);
},
},
};
</script>
15.v-model 的改进
- Vue3: Vue3 对
v-model
进行了改进,允许你自定义属性和事件,使得在使用v-model
时更加灵活。 - Vue2: 在 Vue2 中,
v-model
的定制性相对较低。
<!-- Vue3 中的 v-model 定制 -->
<script>
export default {
model: {
prop: 'checkedValue',
event: 'change',
},
props: {
checkedValue: Boolean,
},
};
</script>
16.打包优化
Tree-shaking:模块打包 webpack、rollup 等中的概念。移除 JavaScript 上下文中未引用的代码。主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。
以 nextTick 为例子,在 Vue2 中,全局API暴露在Vue实例上,即使未使用,也无法通过 tree-shaking 进行消除。
import Vue from 'vue';
Vue.nextTick(() => {
// 一些和DOM有关的东西
});
vue3:
import {
nextTick } from 'vue'; // 显式导入
nextTick(() => {
// 一些和DOM有关的东西
});
通过这一更改,只要模块绑定器支持 tree-shaking,则Vue应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小。
受此更改影响的全局API如下所示。
- Vue.nextTick
- Vue.observable (用 Vue.reactive 替换)
- Vue.version
- Vue.compile (仅全构建)
- Vue.set (仅兼容构建)
- Vue.delete (仅兼容构建)
内部API也有诸如 transition、v-model 等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。Vue3 将所有运行功能打包也只有约22.5kb,比 Vue2 轻量很多。
17.动态注入(Provide/Inject)的改进
- Vue3: Vue3 改进了动态注入的性能,并提供了更好的 TypeScript 支持。
- Vue2: 在 Vue2 中,动态注入可能会在性能方面有一些问题,而且在 TypeScript 中的类型推断相对较弱。
// Vue3 中的动态注入
// Parent.vue
<script>
import {
provide } from 'vue';
export default {
setup() {
provide('message', 'Hello from parent!');
},
};
</script>
// Child.vue
<script>
import {
inject } from 'vue';
export default {
setup() {
const message = inject('message');
// ...
},
};
</script>
18.弃用和移除的功能
- Vue3: Vue3 引入了一些新的 API 并弃用了一些在 Vue2 中存在的功能,例如
$on
,$off
,$once
等方法被弃用,使用on
,off
,once
替代。 - Vue2: 在 Vue2 中这些弃用的方法仍然是可用的。
// Vue3 中的事件处理
const emitter = createEmitter();
const off = emitter.on('event', () => {
// ...
});
// 取消监听事件
off();
19.自定义渲染器 API 的改进
- Vue3: Vue3 的自定义渲染器 API 更加灵活,并引入了
createRenderer
函数,使得可以更方便地在不同平台上进行渲染。 - Vue2: Vue2 中的自定义渲染器相对较为有限。
// Vue3 中的自定义渲染器 API
import {
createRenderer } from 'vue';
const renderer = createRenderer({
// ...
});
20.新增patchFlag字段
patchFlag
字段是一个位掩码,通过对不同位进行组合来表示不同的变化类型。它可以帮助 Vue 3 在进行 diff 运算时更高效地定位和处理需要更新的部分,避免不必要的比较和操作,从而提升性能。
具体作用如下:
- 快速判断节点类型变化:
patchFlag
可以告诉 Vue 3 虚拟 DOM 的变化类型,包括节点的类型、文本内容、元素的动态属性等。通过检查patchFlag
的不同位,可以快速判断节点类型是否发生了变化,避免进行不必要的深度比较。 - 提高 diff 粒度:通过细分不同的
patchFlag
值,可以将节点的比较和更新粒度调整得更加准确。例如,如果只有属性发生了变化,可以仅针对属性进行比较,而无需再对子节点进行深度比较。 - 优化响应式更新:通过
patchFlag
标记节点的变化,Vue 3 可以针对性地处理不同类型的更新。例如,当一个组件的props
发生变化时,只需针对props
进行更新,而无需重新渲染整个组件。
类型列举:
// patchFlags 字段类型列举
export const enum PatchFlags {
TEXT = 1, // 动态文本内容
CLASS = 1 << 1, // 动态类名
STYLE = 1 << 2, // 动态样式
PROPS = 1 << 3, // 动态属性,不包含类名和样式
FULL_PROPS = 1 << 4, // 具有动态 key 属性,当 key 改变,需要进行完整的 diff 比较
HYDRATE_EVENTS = 1 << 5, // 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 不会改变子节点顺序的 fragment
KEYED_FRAGMENT = 1 << 7, // 带有 key 属性的 fragment 或部分子节点
UNKEYED_FRAGMENT = 1 << 8, // 子节点没有 key 的fragment
NEED_PATCH = 1 << 9, // 只会进行非 props 的比较
DYNAMIC_SLOTS = 1 << 10, // 动态的插槽
HOISTED = -1, // 静态节点,diff阶段忽略其子节点
BAIL = -2 // 代表 diff 应该结束
}
21.错误处理的改进
- Vue3: Vue3 引入了
errorCaptured
生命周期钩子,用于捕获子组件的错误,并提供了更好的错误处理机制。 - Vue2: 在 Vue2 中,错误处理相对较为有限。
// Vue3 中的错误处理
<script>
export default {
errorCaptured(err, vm, info) {
// 处理错误
},
};
</script>
22.事件缓存
Vue3 的cacheHandler
可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数。
<div id="app">
<h1>vue3事件缓存</h1>
<p>这是一个p标签</p>
<div>{
{name}}</div>
<span onCLick=() => {}><span>
</div>
渲染函数如下所示。
import {
createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from vue
const _withScopeId = n => (_pushScopeId(scope-id),n=n(),_popScopeId(),n)
const _hoisted_1 = {
id: app }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(h1, null, vue3事件缓存, -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(p, null, 这是一个p标签, -1 /* HOISTED */))
const _hoisted_4 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(span, {
onCLick: () => {
} }, [
/*#__PURE__*/_createElementVNode(span)
], -1 /* HOISTED */))
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(div, _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode(div, null, _toDisplayString(_ctx.name), 1 /* TEXT */),
_hoisted_4
]))
}
观察以上渲染函数,你会发现 click 事件节点为静态节点(HOISTED 为 -1),即不需要每次重新渲染。
23.响应式原理
Proxy与Object.defineProperty:
Vue 2: Vue 2使用
Object.defineProperty
来实现响应式。该方法通过劫持对象的get
和set
操作,在数据发生变化时触发视图更新。但这种方式存在一些限制,例如不能监听数组的变化。Vue 3: Vue 3采用了ES6中引入的
Proxy
对象。Proxy
提供了更强大和灵活的拦截器,可以覆盖目标对象的各种操作,包括get
、set
、delete
等。这使得Vue 3的响应式系统更加强大且性能更优。Proxy 对象:Vue 3 使用了 ES6 中的 Proxy 对象来实现响应式。Proxy 对象可以拦截 JavaScript 对象上的各种操作,比如读取属性、设置属性、删除属性等。通过使用 Proxy 对象,Vue 3 可以在属性发生变化时捕获到对应的操作,并触发更新。
依赖追踪:Vue 3 使用了依赖追踪的机制来追踪属性之间的依赖关系。每个响应式对象都有一个关联的依赖追踪器,当访问响应式对象的某个属性时,会将当前的依赖关系添加到依赖追踪器中。这样,在属性发生变化时,可以找到所有依赖于该属性的地方,并触发相应的更新。
更高效的触发更新机制:
- Vue 2: 在Vue 2中,每个组件实例都有一个
Watcher
实例,用于追踪数据的变化。当数据变化时,会触发Watcher
,进而导致视图更新。 - Vue 3: Vue 3引入了基于
scheduler
的新的更新机制。通过scheduler
,Vue 3可以更灵活地控制何时执行更新,以提高性能。这种机制使得Vue 3在处理大规模数据变化时更加高效。
组合式API的影响:
- Vue 2: Vue 2的开发主要基于选项式 API,这在大型组件和复杂逻辑下可能导致代码难以维护和组织。
- Vue 3: 引入了API,这是一种基于函数的组合式API,允许开发者更灵活地组织组件逻辑。组合式API与Vue 3的响应式系统密切结合,使得组件逻辑更容易复用和组织。
Tree-shakable:
- Vue 2: Vue 2的体积相对较大,无法很好地支持Tree-shaking,导致在项目中引入整个Vue库,即使只使用了其中的一部分功能。
- Vue 3: Vue 3采用了模块化设计,使得库更容易被Tree-shaking优化,可以更细粒度地按需引入和使用。