vue3 和vue2 的比较

文章目录

  • 生命周期
  • 多根节点
  • Composition API组合式API
    • Options API与composition API对比
      • 优化逻辑组织
      • 优化逻辑复用
  • 异步组件(Suspense)
    • Suspense组件
  • 响应式原理
  • 性能
    • 体积优化
    • 编译优化
      • diff算法优化
      • 静态提升
      • 数据劫持(响应式系统)优化

生命周期

vue3在组合API中使用生命周期钩子时需要先引入,而vue2在选项API中可以直接调用钩子
vue3 和vue2 的比较_第1张图片

setup是围绕beforeCreate和created生命周期钩子运行的,所以不需要显式地去定义

多根节点

vue2中,在模板中如果使用多个根节点时会报错,而vue3 支持多个根节点

Composition API组合式API

vue2 是选项式API(Options API),一个逻辑灰散乱在文件的不同位置(data、props、computed、watch、生命周期等),导致代码地可读性变差。
vue3组合式API(Composition PI)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。

Options API与composition API对比

优化逻辑组织

Options API 碎片化使得理解和维护复杂组件变得困难, 选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块
Compositon API 将某个逻辑关注点相关的代码全都放在一个函数里,这样当需要修改一个功能时,就不再需要在文件中跳来跳去

function useCount() {
    let count = ref(10);
    let double = computed(() => {
        return count.value * 2;
    });

    const handleConut = () => {
        count.value = count.value * 2;
    };

    console.log(count);

    return {
        count,
        double,
        handleConut,
    };
}

组件上使用count

export default defineComponent({
    setup() {
        const { count, double, handleConut } = useCount();
        return {
            count,
            double,
            handleConut
        }
    },
});

优化逻辑复用

在vue2中,我们是通过mixin实现功能混合,如果多个mixin混合,会存在两个非常明显的问题:命名冲突和数据来源不清晰

而通过composition这种形式,可以将一些复用的代码抽离出来作为一个函数,只要的使用的地方直接进行调用即可

在vue2中,通过mixin实现功能混合的缺陷

1.命名冲突:如果多个 Mixin 中包含了相同的选项,会导致命名冲突,这可能会导致不可预期的行为。
2.隐式依赖:当多个 Mixin 依赖于相同的属性或方法时,可能会出现隐式的依赖关系,使得代码难以维护和理解。
3.耦合性增强:Mixin 可能会导致组件与多个不同的代码片段紧密耦合,这会增加代码的复杂度和维护难度。
4.全局污染:如果一个 Mixin 修改了全局对象或其他共享资源,可能会影响到整个应用程序。
5.不可追踪性:当一个组件使用了多个 Mixin 时,很难追踪每一个 Mixin 对组件行为的影响。
6.调试困难:当出现问题时,定位问题所在可能会比较困难,特别是当多个 Mixin 交叉影响时。
7.组件复用性:过度使用 Mixin 可能会导致组件的复用性降低,因为组件的功能散布在多个 Mixin 中。
8.组件难以理解:Mixin 可能会导致组件的行为变得复杂,特别是当多个 Mixin 互相影响时,会使得组件的行为难以理解。
因此,在使用 Vue 2 中的 Mixin 时,需要谨慎考虑,尽量避免上述问题的出现。同时,也可以考虑使用其他方式,如使用组件复合、插槽(slot)、高阶组件(Higher Order Component)等来达到类似的功能复用效果,但更加灵活和可控。vue3 的组合式API很好解决了这种问题

在 Vue 3 中,提供了 Composition API(组合式 API)作为一种新的组件组织方式,它可以更好地解决了在 Vue 2 中使用 Mixin 可能出现的一些问题:
命名冲突问题减少:Composition API 使用函数组合的方式,可以将相关的逻辑组织在一个函数内部,减少了命名冲突的可能性。
1.明确的依赖关系:Composition API 允许你在组件内部明确地声明你的依赖关系,这使得组件的依赖关系更加清晰。
2.提供了更强大的逻辑封装:通过 Composition API,可以更灵活地组合和封装逻辑,使得代码更易于理解和维护。
3.模块化和可组合性:Composition API 鼓励将逻辑以函数的形式封装,这使得逻辑可以更容易地进行模块化和复用。
4.代码的可追踪性:由于逻辑被封装在函数内部,所以在组件内部可以更容易地追踪逻辑的来源和影响。
5.更好的代码隔离:Composition API 允许你将逻辑按功能组织在不同的函数中,从而实现更好的代码隔离,减少了耦合性。
总的来说,Composition API 提供了一种更灵活、可组合、可控的方式来组织组件的逻辑,相比于 Vue 2 的 Mixin,可以更好地解决了命名冲突、依赖关系不清晰等问题,使得组件的代码更容易理解、维护和复用。

异步组件(Suspense)

Vue3提供Suspense组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如loading,使用户的体验更加平滑。使用它,需要在模板中声明,并包括两个名命插槽:default和fallback。Suspense确保加载完异步内容时显示默认插槽,并将fallback插槽用作加载状态

<tempalte>
  <suspense>
    <template #default>
      <List />
    </template>
    <template #fallback>
      <div>
        Loading...
      </div>
    </template>
  </suspense>
</template>

Suspense组件

vue3中增加了一个名为Suspense的内置组件,用于在异步组件加载完成前显示占位符,避免页面空白或显示错误信息,提高用户体验。
注意:是一项实验性功能,它不一定灰最终成为稳定功能,并且在稳定之前相关API也可能会发生变化。

<template>
  <div>
    <Suspense>
      <template #default>
        <AsyncComponent />
      </template>
      <template #fallback>
        <div>Loading...</div>
      </template>
    </Suspense>
  </div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() => import('./AsyncComponent.vue'))
</script>

注意事项:

  1. Suspense组件只能包含一个异步组件,否则会抛出错误
  2. fallback插槽中的内容只能是一个单独的元素,否则会抛出错误
  3. 异步组件必须通过defineAsynsComponent方法定义,否则无法使用Suspense组件。
  4. 为了提高用户体验,建议在fallback插槽中显示一个占位符,如Loading

响应式原理

Vue2 响应式原理的基础是Object.defineProperty
Vue3的响应式原理是Proxy

Object.defineProperty基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象

// 创建一个空对象
const person = {};

// 使用 Object.defineProperty 定义属性
Object.defineProperty(person, 'name', {
  value: 'John',      // 属性值
  writable: false,     // 不可写
  enumerable: true,    // 可枚举
  configurable: false  // 不可配置
});

// 尝试修改属性值会失败,因为属性是不可写的
person.name = 'Mike';

// 遍历对象的属性
for (let key in person) {
  console.log(key); // 输出 'name',因为属性是可枚举的
}

// 删除属性会失败,因为属性是不可配置的
delete person.name;

// 重新定义属性为可配置
Object.defineProperty(person, 'name', {
  configurable: true
});

// 现在可以成功删除属性
delete person.name;


vue2核心源码:

function defineReactive(obj, key, val) {
//创建一个依赖实例,每个属性对象一个依赖
  // 一 key 一个 dep
  const dep = new Dep()
  
  // 获取 key 的属性描述符,发现它是不可配置对象的话直接 return
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) { return }
  
  // 获取 getter 和 setter,并获取 val 值(以及初始化值)
  const getter = property && property.get
  const setter = property && property.set
  if((!getter || setter) && arguments.length === 2) { val = obj[key] }
  
  // 递归处理,保证对象中所有 key 被观察
  let childOb = observe(val)
  
  Object.defineProperty(obj, key, {
  //属性可枚举
    enumerable: true,
    //属性可配置
    configurable: true,
    // get 劫持 obj[key] 的 进行依赖收集
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      if(Dep.target) {
        // 依赖收集,将当前依赖(观察者)添加到属性的依赖列表中
        dep.depend()
        if(childOb) {
          // 针对嵌套对象,依赖收集
          childOb.dep.depend()
          // 如果属性值是数组 还需要触发数组响应式
          if(Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
    }
    //返回属性值
    return value
  })
  // set 派发更新 obj[key]
  set: function reactiveSetter(newVal) {
  //在这里实现属性的设置,包括触发响应式更新
    ...
    if(setter) {
      setter.call(obj, newVal)
    } else {
      val = newVal
    }
    // 新值设置响应式
    childOb = observe(val)
    // 依赖通知更新
    dep.notify()
  }
}

这段代码的主要功能是将对象的属性变成响应式的,当属性的值来发生变化时,可以自动通通知相关的视图进行更新。它通过Object.defineProperty来定义属性的get和set方法,以及属性的特性(例如可枚举、可配置等)来实现这一功能。同时它还支持嵌套对象和数组的响应式处理。

性能

体积优化

相比于Vue2,Vue3整体体积变小了,移除了一些不常用的API,任何一个函数,比方说ref、reavived、computed等,仅仅在用到的时候才打包,没用到的模块都去掉,打包整体体积变小,最重要的是Tree shanking

Tree shanking是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫Dead code elimination

Tree shaking是基于ES6模板语法(import与exports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量

Tree shaking做了两件事:

  • 编译阶段利用ES6 Module判断哪些模块已经加载
  • 判断那些模块和变量未被使用或者引用,进而删除对应代码

编译优化

diff算法优化

vue3在diff算法中相比vue2增加了静态标记, 其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较,提高性能。

静态提升

Vue3对不参与更新的元素,会做静态提升只会被创建一次,在渲染时直接复用,这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用
没有做静态提升之前

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _createVNode("span", null, "你好"),
    _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ], 64 /* STABLE_FRAGMENT */))
}

做了静态提升之后

const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "你好", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock(_Fragment, null, [
    _hoisted_1,
    _createVNode("div", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ], 64 /* STABLE_FRAGMENT */))
}

// Check the console for the AST

静态内容_hoisted_1被放置在render 函数外,每次渲染的时候只要取 _hoisted_1 即可

同时 _hoisted_1 被打上了 PatchFlag ,静态标记值为 -1 ,特殊标志是负整数表示永远不会用于 Diff

数据劫持(响应式系统)优化

在Vue2 中,数据劫持就是通过Object.defineProperty,这个API有一些缺陷:检测不到对象属性的添加和删除
数组Api方法无法监听到,需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题

Object.defineProperty(data, 'a',{
  get(){
    // track
  },
  set(){
    // trigger
  }
})

Proxy直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    // Proxy相当于在对象外层加拦截
     const observed = new Proxy(obj, {
        get(target, key, receiver) {
            const res = Reflect.get(target, key, receiver)
            console.log(`获取${key}:${res}`)
            return isObject(res) ? reactive(res) : res
        }
        set(target, key, value, receiver) {
            const res = Reflect.set(target, key, value, receiver)
            console.log(`设置${key}:${value}`)
            return res
        },
        deleteProperty(target, key) {
            const res = Reflect.deleteProperty(target, key)
            console.log(`删除${key}:${res}`)
            return res
        }
    })
    return observed
}

同时Proxy 并不能监听到内部深层次的对象变化,而 Vue3 的处理方式是在 getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归
Proxy的作用
在vue中 Proxy主要用于监听对象的变化,以便在对象属性发生变化时,自动更新相关的视图。这是Vue实现响应式数据绑定的核心机制之一

  1. 实现响应式数据的绑定
    vue使Proxy对象来监听数据变化,当数据发生变化时,会自动触发相关视图更新。这使得开发者无需手动去追踪数据变化并更新视图,大大简化了前端开发工作
  2. 监听数据的读取和写入
    Proxy可以拦截对象属性的读取和写入操作。这意味着在读取或写入数据时可以执行自定义的逻辑,比如在写入时进行数据验证
  3. 递归监听对象嵌套
    Vue中的Proxy不仅可以监听对象本身,还可以递归地监听对象内部的所有属性和嵌套对象。这使得整个数据结构都可以实现响应式
  4. 数组变化的监听
    对于数组,Proxy 会拦截数组的变化操作(如 push、pop、splice 等),从而实现数组变化的响应式更新。
  5. 动态添加属性:
    使用 Proxy 可以动态地向对象添加属性,并且这个新属性也会被监听,从而保证了新添加的属性也能实现响应式。
  6. 防止非预期的属性访问:
    通过 Proxy 可以限制对象属性的访问,防止对某些属性的非法访问或修改。
  7. 监听对象的删除:
    Proxy 还可以监听对象属性的删除操作,当属性被删除时,可以执行相应的逻辑。

你可能感兴趣的:(vue.js,javascript,前端)