API层面
Composition API
Vue3 的 Composition API 相较于 Vue2 的 Options API 有两大显著的好处
- 更好的逻辑复用: Composition API 中的逻辑可以通过函数的方式导出和导入,这使得可以将通用逻辑抽取到单独的文件中,然后在不同组件中复用,混入(
mixin
) 将不再作为推荐使用,Composition API
可以实现更灵活且无副作用的复用代码 - 更好的关注分离:
Composition API
允许将组件的相关状态、方法,和生命周期逻辑写在一起,而不需要将它们分散在不同的配置选项中,定位问题时不再需要在data、method、mounted之间来回看,这使得代码更具可读性和可维护性,特别是对于复杂的组件
script setup
在 setup()
内部,this不再指向当前的组件实例而是undefined
,如果一定要访问当前组件实例,可以用const { ctx } = getCurrentInstance()
获取
<script setup>
是 Vue3 Composition API 中的一个语法糖(syntactic sugar),它用于更简洁地编写 setup
函数,使用 <script setup>
无需显式地编写 setup
函数去返回状态和方法,而是可以在模板中直接使用这些状态和方法,显著减少重复的代码,让开发者更专注于组件的逻辑
Fragment类型节点
在编写Vue2 SFC时,强制要求必须只包含一个根节点不然会报错,因为组件的挂载逻辑直接就是patch(vm.$el, vnode)
,逻辑上就默认一个组件只有一个根节点,而Vue3新增了Fragment的vnode类型,编译SFC时会直接生成Fragment类型的vnode,会先遍历children数组再去渲染子vnode,所以支持在模板中使用多根节点,不需要再用div包裹,有助于提高代码的可读性,并消除不必要的包裹元素
Teleport内置组件
在模板中将内容渲染到DOM树中的不同位置,而无需改变组件的父子关系或使用CSS样式来控制渲染位置,Teleport通常用于创建弹出框、模态框、菜单、通知栏等需要在 DOM 中的不同位置显示的组件
<template>
<div>
<button @click="showModal = !showModal">Toggle Modal</button>
<teleport to="body">
<div v-if="showModal" class="modal">
<h2>Modal Content</h2>
<button @click="closeModal">Close Modal</button>
</div>
</teleport>
</div>
</template>
<script setup>
import { ref } from 'vue';
const showModal = ref(false);
const closeModal = () => {
showModal.value = false;
};
</script>
<style>
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
</style>
Suspense内置组件
用于处理异步组件渲染和异步操作的加载状态。它的主要作用是在异步操作未完成之前,显示一个兜底内容(loading 状态),当异步操作完成后,再渲染实际组件内容
<template>
<Suspense>
<AsyncComp />
<template #fallback> Loading.... </template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const sleep = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
const AsyncComp = defineAsyncComponent(async () => {
await sleep(2000);
return import("./AsyncComp.vue");
});
</script>
生命周期
CSS v-bind
Vue3现在可以在style标签里直接给CSS绑定JS变量
<template>
<p>hello</p>
</template>
<script setup>
const theme = {
color: "red",
};
</script>
<style scoped>
p {
color: v-bind("theme.color");
}
</style>
v-memo指令
v-memo的表达式值不变,则不会重新渲染这个vnode的子树,相当于条件版的v-once
<template>
// 两者等价
<p v-once>{{ msg }}</p>
<p v-memo="[]">{{ msg }}</p>
</template>
watch新配置参数
调度时机flush
:用来控制回调函数的执行时机:DOM更新前、DOM更新后、同步onCleanup函数
:用来注册一个函数,在下次回调函数执行之前执行
let finalData;
watch(obj, async (newValue, oldValue, onInvalidate) => {
let expired = false;
onInvalidate(() => {
expired = true;
});
const res = await fetch("/path/to/request");
if (!expired) {
finalData = res;
}
});
v-if、v-for优先级
在Vue2里v-for优先级比v-if高,而在Vue3里则反过来
移除了一些API
$children
:推荐使用 template refs$on
、$off
、$once
:以后Vue3不能被实例化当作自定义事件订阅派发中心了filters
:推荐使用 method calls 或者 computed properties
性能优化层面
响应式
响应式可以理解为数据与函数的绑定:数据变更会导致函数重新执行
数据:ref、reactive、computed(ref)、props(reactive)
函数:computed watcher/effect、user watcher/effect、render watcher/effect
Vue的响应式涉及到3个环节:依赖收集、派发更新、依赖清除,这个收集的依赖在Vue2里面叫watcher,在Vue3里面叫effect
依赖清除在Vue2中是发生在依赖收集后,派发更新前,是一个叫cleanupDeps的函数做的,遍历老deps,根据newDepIds.has(dep.id) 来判断是否该清除老依赖;而在Vue3中,依赖清除发生在派发更新后,依赖清除前,在finalizeDepMarkers函数里做的,主要是通过二进制位+位运算
来实现的,通过二进制位来判断依赖是已收集的还是新收集的,如果wasTracked(dep) && !newTracked(dep) 那么就会清除这个依赖dep.delete(effect)
Vue2使用的是Object.defineProperty
实现响应式,有四个缺陷:
- data新增和删除属性时不会触发render watcher的重新执行
- 通过数组下标更新原始值数据时不会触发render watcher的重新执行
- data初始化时的所有嵌套属性都会被响应式化不管用没用到
- 不支持Map和Set等数据结构的响应式
Vue3则是用Proxy + Reflect
实现的响应式,解决了这四个缺陷
虚拟DOM
Vue3给vnode新增了patchFlag属性,patchFlag值为1代表该节点有动态的内容,值为2代表该节点只有动态的class绑定,值为3代表该节点只有动态的style绑定等等,这样Vue3可以更快速地决策,跳过不必要的diff从而提高性能
Vue3给vnode新增了dynamicChildren数组属性,里面收集的是所有动态的子代vnode,下次diff到这个vnode(Block)的时候就可以跳过其children数组,直接diff这个dynamicChildren动态子vnode的数组,减少diff的次数
一些静态的节点或属性会被提升到渲染函数之外,经过静态提升后,在渲染函数内只会持有对静态节点的引用。当响应式数据发生变化,并使得渲染函数重新执行时,并不会重新创建静态的虚拟节点,从而避免了额外的性能开销,大量连续的静态节点会直接被序列化为字符串,减少内存占用
// 把静态节点提升到渲染函数之外
const hoist1 = createVNode('p', null, 'text')
const hoistStatic = createStaticVNode('<p></p>...<p></p>')
function render() {
return (openBlock(), createBlock('div', null, [
hoist1, // 静态节点引用
createVNode('p', null, ctx.title, 1 /* TEXT */),
hoistStatic //预字符串化
]))
}
事件监听器不再是每次渲染时都创建新的,而是在首次创建后进行缓存,并在后续渲染中重用,这意味着当组件重新渲染时,不会为相同的事件创建新的监听器,从而减少内存开销,提高组件的性能和效率
Diff算法
Vue2里叫双端Diff,Vue3重写为快速Diff,能更高效地操作DOM
TreeShaking
Vue3重写了全局API,支持局部引入API,可以被更好地被TreeShaking,让项目打包产物体积减少
类型检查
Vue2使用Flow来做类型检查,Vue3使用TypeScript来做类型检查
Monorepo架构
Vue3采用Monorepo的方式管理项目代码,Vue2采用Multirepo的方式管理项目代码