Vue3和Vue2的区别?给面试官倒一杯卡布奇诺!

139 阅读4分钟

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>

生命周期

image.png

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实现响应式,有四个缺陷:

  1. data新增和删除属性时不会触发render watcher的重新执行
  2. 通过数组下标更新原始值数据时不会触发render watcher的重新执行
  3. data初始化时的所有嵌套属性都会被响应式化不管用没用到
  4. 不支持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的方式管理项目代码

image.png