首页 > 其他分享 >第四十七篇 vue - vue2 和 vue3 的对比

第四十七篇 vue - vue2 和 vue3 的对比

时间:2023-04-05 17:44:52浏览次数:46  
标签:Vue const Vue2 vue3 第四十七 API vue2 Vue3 节点

vue2 和 vue3 不同点汇总

1、生命周期

2、多根节点

3、Composition Api

4、异步组件

5、响应式原理

6、Teleport

7、虚拟 Dom

8、事件缓存

9、Diff 算法优化

10、打包优化

11、TypeScript 支持
生命周期
1、Vue3 生命周期 整体上变化不大,Vue3 在大部分生命周期钩子名称上 + “on”,功能上是类似的

2、Vue3 在组合式 API( Composition API )中使用 生命周期钩子 时需要先引入,Vue2 在选项 API(Options API)中可以直接调用生命周期钩子

常用的生命周期对比

Vue2 生命周期 Vue3 生命周期
beforeCreate
created
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
说明

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

1、 vue3

<script setup>  

import { onMounted } from 'vue';   // 使用前需引入生命周期钩子
 
onMounted(() => {
  // ...
});
 
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖
onMounted(() => {
  // ...
});

</script>
 
2、vue2

<script>     
export default {         
  mounted() {   // 直接调用生命周期钩子            
    // ...         
  },           
}
</script> 
多根节点
1、Vue2 在模板中如果使用多个根节点时会报错

2、Vue3 支持多个根节点,也就是 fragment

原因

  1、在 vue2 中
  
     1、因为 vdom 是一个 单根树形结构 描述当前视图结构,patch 方法在遍历的时候从根节点开始遍历,它要求只有一个根节点
     
     2、组件也是会转换成 vdom,所以也必须满足单根节点要求
     
  2、在 vue3 中 
  
     1、因为 vue3 引入了 fragment 概念,这是一个抽象的节点,如果发现组件是多根的会自动创建一个 fragment 节点,把多根节点视为自己的 children
    
     2、如果发现这是一个fragment节点,则直接遍历children创建或更新
Composition API
1、Vue2 是 选项 API(Options API),一个逻辑会散乱在文件不同位置( data、props、computed、watch、生命周期钩子等 ),导致代码的可读性变差。当需要修改某个逻辑时,需要上下来回跳转文件位置。

2、Vue3 组合式 API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案
异步组件( Suspense )
1、Vue3 提供 Suspense 组件,允许程序在等待异步组件加载完成前渲染兜底的内容,如 loading,使用户的体验更平滑

2、使用它,需在模板中声明,并包括两个命名插槽:default 和 fallback

   Suspense 确保加载完异步内容时显示默认插槽,并将 fallback 插槽用作加载状态
代码示例

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

在 List 组件(有可能是异步组件,也有可能是组件内部处理逻辑或查找操作过多导致加载过慢等)未加载完成前,显示 Loading...(即 fallback 插槽内容),加载完成时显示自身(即 default 插槽内容)
Teleport
Vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。比如项目中常见的 Dialog 弹窗

<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
  <div class="dialog" v-if="dialogVisible">
    我是弹窗,我直接移动到了body标签下
  </div>
</teleport>
响应式原理
1、Vue2 响应式原理基础是 Object.defineProperty

2、Vue3 响应式原理基础是 Proxy
vue2 响应式基础
Object.defineProperty 基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象

let obj = {};
let name = 'leo';
Object.defineProperty(obj, 'name', {
  enumerable: true,   // 可枚举(是否可通过 for...in 或 Object.keys() 进行访问)
  configurable: true,   // 可配置(是否可使用 delete 删除,是否可再次设置属性)
  // value: '',   // 任意类型的值,默认undefined
  // writable: true,   // 可重写
  get() {
    return name;
  },
  set(value) {
    name = value;
  }
});

提示  【 writable 和 value 】 与 【 getter 和 setter 】 不共存
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()
  }
}
vue3 响应式基础
1、Vue3 为何会抛弃 vue2 的 Object.defineProperty 

   主要原因:无法监听 对象 或 数组 新增、删除 的元素

   Vue2 相应解决方案

      1、针对常用数组原型方法 push、pop、shift、unshift、splice、sort、reverse 进行了 hack 处理;提供 Vue.set 监听对象/数组新增属性
   
      2、对象的新增/删除响应,还可以 new 个新对象,新增则合并新属性和入旧对象;删除则将删除属性后的对象深拷贝给新对象。

2、Proxy

  1、Proxy 是 ES6 新特性,通过第2个参数 handler 拦截目标对象的行为
  
  2、相较于 Object.defineProperty 提供语言全范围的响应能力,消除了局限性

    1、支持 对象/数组的 新增、删除
    
    2、监测 .length 修改
    
    3、Map、Set、WeakMap、WeakSet 的支持
基本用法:创建对象的代理,从而实现基本操作的 拦截 和 自定义 操作

let handler = {
  get(obj, prop) {
    return prop in obj ? obj[prop] : '';
  },
  set() {
    // ...
  },
  ...
};
部分 vue3 的源码 reactive.ts 

function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) {
  ...
  // collectionHandlers: 处理Map、Set、WeakMap、WeakSet
  // baseHandlers: 处理数组、对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}
虚拟 Dom
Vue3 相比于 Vue2,虚拟 DOM 上增加 patchFlag 字段
我们借助 Vue3 Template Explorer 来看

<div id="app">
  <h1>vue3虚拟DOM讲解</h1>
  <p>今天天气真不错</p>
  <div>{{name}}</div>
</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虚拟DOM讲解, -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode(p, null, 今天天气真不错, -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 */)
  ]))
}

第 3 个 _createElementVNode 的第4个参数即 patchFlag 字段类型
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 应该结束
}
事件缓存
1、Vue3 的cacheHandler可在第一次渲染后缓存我们的事件

2、相比于 Vue2 无需每次渲染都传递一个新函数,加一个 click 事件
<div id="app">
  <h1>vue3事件缓存讲解</h1>
  <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, 今天天气真不错, -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),即不需要每次重新渲染
Diff 算法优化
1、vue2.x 的 虚拟 DOM 是进行全量比较

2、vue3 新增了 静态标记 PatchFlag

3、搬运 Vue3 patchChildren 源码

   结合上文与源码 patchFlag 帮助 diff 时区分静态节点,以及不同类型的动态节点,一定程度地减少节点本身及其属性的比对
function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
  // 获取新老孩子节点
  const c1 = n1 && n1.children
  const c2 = n2.children
  const prevShapeFlag = n1 ? n1.shapeFlag : 0
  const { patchFlag, shapeFlag } = n2
  
  // 处理 patchFlag 大于 0 
  if(patchFlag > 0) {
    if(patchFlag && PatchFlags.KEYED_FRAGMENT) {
      // 存在 key
      patchKeyedChildren()
      return
    } els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) {
      // 不存在 key
      patchUnkeyedChildren()
      return
    }
  }
  
  // 匹配是文本节点(静态):移除老节点,设置文本节点
  if(shapeFlag && ShapeFlags.TEXT_CHILDREN) {
    if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
    }
    if (c2 !== c1) {
      hostSetElementText(container, c2 as string)
    }
  } else {
    // 匹配新老 Vnode 是数组,则全量比较;否则移除当前所有的节点
    if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...)
      } else {
        unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)
      }
    } else {
      
      if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
        hostSetElementText(container, '')
      } 
      if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        mountChildren(c2 as VNodeArrayChildren, container,anchor,parentComponent,...)
      }
    }
  }
}
patchUnkeyedChildren 源码如下所示

function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
  c1 = c1 || EMPTY_ARR
  c2 = c2 || EMPTY_ARR
  const oldLength = c1.length
  const newLength = c2.length
  const commonLength = Math.min(oldLength, newLength)
  let i
  for(i = 0; i < commonLength; i++) {
    // 如果新 Vnode 已经挂载,则直接 clone 一份,否则新建一个节点
    const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i])
    patch()
  }
  if(oldLength > newLength) {
    // 移除多余的节点
    unmountedChildren()
  } else {
    // 创建新的节点
    mountChildren()
  }
}
打包优化
1、Tree-shaking

   1、模块打包 webpack、rollup 等中的概念
   
   2、移除 JavaScript 上下文中未引用的代码
   
   3、主要依赖于 import 和 export 语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用
   
2、以 nextTick 为例子,在 Vue2 中,全局 API 暴露在 Vue 实例上,即使未使用,也无法通过 tree-shaking 进行消除   

import Vue from 'vue';
 
Vue.nextTick(() => {
  // 一些和DOM有关的东西
});

3、Vue3 中针对全局和内部的 API 进行了重构,并考虑到 tree-shaking 的支持。因此,全局 API现在只能作为 ES模块 构建的命名导出进行访问

import { nextTick } from 'vue';   // 显式导入
 
nextTick(() => {
  // 一些和DOM有关的东西
});
1、通过这一更改,只要模块绑定器支持 tree-shaking,则 Vue 应用程序中未使用的 api 将从最终的捆绑包中消除,获得最佳文件大小

2、受此更改影响的全局API如下所示。

   Vue.nextTick
   
   Vue.observable (用 Vue.reactive 替换)
   
   Vue.version
   
   Vue.compile (仅全构建)
   
   Vue.set (仅兼容构建)
   
   Vue.delete (仅兼容构建)

3、内部 API 也有诸如 transition、v-model 等标签或者指令被命名导出

   只有在程序真正使用才会被捆绑打包
   
4、Vue3 将所有运行功能打包也只有约 22.5kb,比 Vue2 轻量很多。
TypeScript 支持
Vue3 由 TypeScript 重写,相对于 Vue2 有更好的 TypeScript 支持

   1、Vue2 Options API 中 option 是个简单对象,而 TypeScript 是一种类型系统,面向对象的语法,不是特别匹配。

   2、Vue2 需要 vue-class-component 强化 vue 原生组件,也需要 vue-property-decorator 增加更多结合Vue特性的装饰器,写法比较繁琐
其他变化
1、移除了 Event Bus

  1、Vue3 从实例中移除了 o n 、 on、 on、off 和 $once 方法
  
  2、如果希望继续使用全局事件总线的话,就需要通过第三方库
  
     Vue3 官方有推荐一些库,例如 mitt 或 tiny-emitter
     
2、vue3 取消了 vue2 中的过滤器  

   过滤器打破了大括号内的表达式 “只是 JavaScript” 的假设

   建议使用
   
     1、在双括号表达式中调用方法 实现
     
     2、计算属性来替换 过滤器

Options API 与 Composition API

Vue 组件可以用两种不同的 API 风格编写

   1、Options API  【 选项 Api 】
   
   2、Composition API   【 组合式 Api 】
Options API 【 选项 Api 】
1、使用 Options API,我们使用选项对象定义组件的逻辑

  1、例如 data、methods 和 mounted
  
  2、由选项定义的属性在 this 内部函数中公开,指向组件实例
  
2、代码示例

<template>
  <button @click="increment">count is: {{ count }}</button>
</template>
 
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  },
  mounted() {
    console.log(`The initial count is ${this.count}.`);
  }
}
</script>
Composition API 【 组合式 Api 】
使用 Composition API,我们使用导入的 API 函数定义组件的逻辑。

在 SFC 中  通常使用 Composition API 

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>
 
<script setup>
import { ref, onMounted } from 'vue';
 
const count = ref(0);
 
function increment() {
  count.value++;
}
 
onMounted(() => {
  console.log(`The initial count is ${count.value}.`);
})
</script>

标签:Vue,const,Vue2,vue3,第四十七,API,vue2,Vue3,节点
From: https://www.cnblogs.com/caix-1987/p/17290009.html

相关文章

  • springboot +vue2.x实现音乐网站
    1pom文件<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache......
  • 如何在vue3获取 DOM 元素
    获取dom的ref元素名称,要对应暴露的名称,不然会出现无效的dom报错,也就是拿到的是null在setup中,使用ref(null)获取dom不能直接在setup里面拿到dom的值,因为setup对应的生命周期是created,所以必须在后续的生命周期钩子里面拿到,比如onMounted注意:ref不要加冒号,直接写dom元素名称......
  • Vue3——使用deep进行样式穿透的时候发出v-deep警告
    前言其实只是一个警告,如果你不在意也是可以的,不过有点强迫症就按着提示说的改了,然后又去官网看了下对应的文档;!>::v-deepusageasacombinatorhasbeendeprecated.Use:deep()instead.这里没啥好说的,因为Vue3的文档中有对应的解释组件作用域CSS了,而且就算不看文档根据提......
  • 项目实践后的图片压缩完整使用过程【vue3+js】
    van-uploader+图片压缩+图片base64转成file compressImage.jsconstACCEPT=['image/jpg','image/png','image/jpeg']constMAXSIZE=1024*1024*2;constMAXTIP="4"//压缩算法函数/*1.首先拿到了base64的图片字符串2.创建一个image对象,获......
  • vue3+vite 解决本地调用时跨域请求
    1、config配置文件中,axios默认请求地址;改为"/api",否则还是请求环境变量中的地址; 2、vite.config.ts文件中做如下配置:  server:{  open:true,//启动项目自动弹出浏览器  port:8081,//启动端口  cors:true,  proxy:{   "/api":{  ......
  • 【vue】vue3中的动画
    vue版本:vue3不过是控制动画放到了标签内控制在上图中,是绑定到了stylevue中关于动画的封装入场出场动画进入开始状态(时间点).v-enter-from{}动态效果(时间段).v-enter-active{}终止状态(时间点).v-enter-to{}.v-leave-from{}.v-leave-active{}.v-lea......
  • vue3适配移动端的登录实现
    <scriptlang="ts"setup>import{ref}from'vue'constPHONE_NUMBER_REGEX=/^1[0-9]{10}$/constVERIFICATION_CODE_REGEX=/^[0-9]{6}$/constLOGIN_ERROR_MESSAGE='登录失败,请检查网络连接并重试'constGENERATE_CODE_ERROR_MESSAGE......
  • Vue3 v-drag 拖拽指令的简单使用
    文档官网文档:https://www.npmjs.com/package/v-drag使用安装、引入npminstallv-drag--saveimportdragfrom"v-drag"使用直接使用:<divv-drag>Dragme!</div>注意:对原本绝对定位水平居中的div,其居中的实现方式应为:div{ position:absolute; left:50%; trans......
  • Vue2.0 学习 第二组 语法模板
    本笔记主要参考菜鸟教程和官方文档编写。1.文本绑定 一般在dom中用{{}}标时,并且在vue构造体内的data中定义文本内容 <divid="app">   <p>{{message}}</p> </div> <script> newVue({  el:'#app',   data:{   message:'HelloVue.js!' ......
  • Vue3 watch 监听函数
    1、watch函数(既要指明监视的属性,也要指明监视的回调)坑:1)监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)2)监视reactive定义的响应式数据中某个属性时:deep配置有效setup(){letsum=ref(0)letmsg=ref('ABCD')letp......