vue2与vue3对比

本文对比分析了Vue2与Vue3的主要差异,包括创建实例方式、响应式原理、生命周期函数、全局API的变化、API风格的转变、根标签的差异、其他常见改动以及Vue3新增特性。Vue3引入了Proxy实现响应式,生命周期函数采用Composition API,移除了部分全局API,组件可无根标签,新增了Teleport和Suspense等特性,提升了性能并提供了更好的组织代码方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

创建实例

  • vue2 :引入vue构造函数,主要依靠render( (creatElement) => {})创建app实例对象

    //引入vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    
    //创建vm
    new Vue({
      el:'#app',
      render: *h* *=>* h(App),
      router:router
    })//.$mount('#app')
    
  • vue3:按需引入,直接用引入的createApp创建实例对象

    //引入的不再是vue构造函数,而是叫createApp的工厂函数
    import { createApp } from 'vue'
    import App from './App.vue'
    
    //创建应用实例对象-app(类似于v2中的vm,但app比vm更轻)并挂载
    createApp(App).mount('#app')
    

在这里插入图片描述

响应式原理

  • Vue2:

    实现原理:

    • 对象类型:通过Object.definrProperty()对属性的读取、修改进行拦截(数据劫持)。
    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)

    存在的问题:

    • 新增属性、删除属性,界面不会更新

      新增属性:this.person.sex=‘女’ 界面不更新的,如果想让它自动更新 那么需要写成如下:

      this.$set(this.person,‘sex’,‘女’) 或者 引入Vue,然后 Vue.set(this.person,‘sex’,‘女’)

      删除属性:delete this.person.sex 属性可以删除,界面不更新,也是需要改变写法:

      this.$delete(this.person,‘sex’) 或者 引入Vue, Vue.delete(this.person,‘sex’)

    • 直接通过更改下标修改数组,界面不会自动更新

      修改数组同理:this.person.hobby[0]=‘学习’ ,不更新,同样的方法:

      this.$set(this.person.hobby,0,‘学习’) 或者 引入Vue,然后 Vue.set(this.person.hobby,0,‘学习’) 或者 this.person.hobby.splice(0,1,‘学习’)

    模拟Vue2响应式实现原理:

    /* let p = {}
    			Object.defineProperty(p,'name',{
    				configurable:true,
    				get(){ //有人读取name时调用
    					return person.name
    				},
    				set(value){ //有人修改name时调用
    					console.log('有人修改了name属性,我发现了,我要去更新界面!')
    					person.name = value
    				}
    			})
    			Object.defineProperty(p,'age',{
    				get(){ //有人读取age时调用
    					return person.age
    				},
    				set(value){ //有人修改age时调用
    					console.log('有人修改了age属性,我发现了,我要去更新界面!')
    					person.age = value
    				}
    			}) 
    
  • Vue3:

    实现原理:

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括增删改查
    • 通过Reflect(反射):对源对象的属性进行操作

    模拟Vue3响应式实现原理:

    const p = new Proxy(person,{
    				//有人读取p的某个属性时调用
    				get(target,propName){
    					console.log(`有人读取了p身上的${propName}属性`)
    					return Reflect.get(target,propName)
    				},
    				//有人修改p的某个属性、或给p追加某个属性时调用
    				set(target,propName,value){
    					console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
    					Reflect.set(target,propName,value)
    				},
    				//有人删除p的某个属性时调用
    				deleteProperty(target,propName){
    					console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
    					return Reflect.deleteProperty(target,propName)
    				}
    			})
    

一个小知识:如果我们用Object.defineProperty去给一个对象添加属性,如果重复添加,那么代码就会直接崩了;然而用映射Reflect.defineProperty去添加,不会报错,只是通过返回值true或者false来告诉是否操作成功,后面的代码依然能继续执行

生命周期函数

  • vue2:

    1. 有了Vue实例对象就先beforeCreate
    2. created完之后去判断有没有绑定根组件,有没有template模板,如果有就继续走挂载流程beforeMount和mouted,如果没有就停滞了,等到$mount()了再走
    3. 组件销毁要走beforeDistroy和distroyed
  • vue3:

    1. 创建实例对象之前就要去检查有没有创建const app = createApp(App),有没有挂载app.mount(’#app’),如果都符合,这时候才开始创建实例对象

    2. 后面就无需再去判断挂载了,创建完直接判断有没有template模板就进入挂载流程

    3. 组件不销毁了,变成了卸载beforeUnmount和unmounted

    4. .Vue3.0也提供了Composition API形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

      如果要用组合式API,是需要import引入的。也就是挂载、更新、卸载的钩子前都加一个on

      • beforeCreate===>setup()

      • created =======> setup()

        如果把beforeCreate当作一个配置项写,那么setup的执行是先于beforeCreate的,但是如果想把它当成组合式API写进setup里,则setup里没有这两个API

      • beforeMount ===> onBeforeMount

      • mounted =======> onMounted

      • beforeUpdate===> onBeforeUpdate

      • updated =======> onUpdated

      • beforeUnmount ==> onBeforeUnmount

      • unmounted =====> onUnmounted

全局API的转移


  • Vue 2.x有许多全局API和配置。
    例如:注册全局组件、注册全局指令等。

    //注册全局组件
    Vue.component('MyButton', {
    	data: () => ({
    	count: 0
    	}),
    	template: ' <button @click-"count++">Clicked {{count}} times</button>
    })
    //注册全局指令
    Vue.directive('focus', {
    	inserted: el => el.focus()
    }
    
  • Vue3中对这些API做出了调整,将全局的API调整到应用实例app上

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rin2E2EN-1650199822736)(E:\markdown\前端\csdn\20220416_1.png)]

API风格的改变

  • vue2 Options API(选项式API)存在的问题:

    使用传统的Options API时,新增或者修改一个需求,就需要分别在data、methods、computed、watch等里面不断地寻找、修改。如果代码量大的时候,修改起来就很痛苦。

  • vue3中 Composition API(组合式API)的优势:

    更加优雅的组织我们的代码、函数,让相关功能的代码更加有序的组织在一起,都写在setup里面。甚至可以把他们都变成一个个hook函数,修改那部分就可以直接去对应的文件里修改。把定义数据、方法这部分也添加了模块化封装的思想


根标签

  • vue2中组件必须有一个根标签
  • Vue3中组件可以没有根标签,内部会自动将多个标签包含在一个Fragment虚拟元素中。

较常用的其他改变

  • data选项应始终被声明为一个函数

  • 过度类名的更改,就是在开始的那个位置上加了一个-from

    • Vue2.x写法
    .v-enter,
    .v-leave-to {
    	opacity: 0;
    }
    .v-leave,
    .v-enter-to {
    	opacity: 1;
    }
    
    • Vue3.x写法
    v-enter-from, 
    .v-leave-to {
    	opacity: e;
    }
    .v-leave-from,
    .v-enter-to {
    	opacity: 1;
    }
    
  • 移除了keyCode作为v-on的修饰符,同时也不支持config.keyCodes了(自定义按键别名,别名的定义也是依赖keyCode的,所以一并移除了)

  • 移除了v-on.native修饰符

    父组件中绑定事件:

    <my- component
    	v-on:close="handleComponentEvent"
    	v-on:click="handleNativeClickEvent"
    />
    

    子组件中声明自定义事件:子组件emits里面接收的就是自定义事件,不接收就默认是原生事件

    <script>
    export default {
    	emits: ['close']
    }
    </script>
    

上述为讲课中所整理的部分内容,下面再自己去看看官方文档,来总结一些其他的。

从头到尾读文档

1. 创建实例对象
  • vue2:引入Vue,创建一个实例对象并挂载,渲染的时候render函数若存在,则 Vue 构造函数不会从template选项或通过el选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。

    在脚手架中没有render是运行不下去的。因为脚手架里面引入的vue是残缺版的vue。而且我在里面试了,template要搭配el一起使用,而el绑定的标签中写了内容是可以不需要template的。但是有template优先显示template的内容

  • vue3:每个 Vue 应用都是通过 createApp 函数创建一个新的 应用实例,传入根组件,挂载然后渲染

    二者的实例对象的一个对比

    vue2:

在这里插入图片描述

vue3:

在这里插入图片描述

从图上我们可以看出,vue2是一个Vue的实例对象,而vue3是一个经过Proxy数据劫持过的对象,而且里面只有hanler和target两个属性,里面分别包含着其他。vue3看起来更优雅且简洁了。vue2里面的数据等都是经过加工变成_开头的来赋予get和set,数据表面看经过了加工复制,而vue3通过数据劫持直接就加好了get与set。

这部分都是自己分析的,有不对的地方欢迎指正。

2.数据响应式
  • vue2中,data中写的数据都是响应式的,后追加的数据不是响应式的,可以通过数据代理使后添加的数据为响应式。通过getter和setter实现响应式。

    Object.defineProperty(obj2,'x',{
        get(){
            return obj.x
        },
        set(value){
            obj.x = value
        }
    })
    
  • vue3中,只有通过ref()定义的基本类型数据(对象使用ref定义也是响应式的,但这是不规范的)和通过reactive()定义的对象才是响应式的。所以后期直接往对象里添加属性还是响应式的。在组件挂载后才能访问 ref。通过proxy实现响应式。

3.生命周期函数
  • vue2中new了一个Vue(),那么就开始创建了,判断没有绑定el,那就不能走下去进行挂载了。只有绑定了el才可以往下走。

    • 最后两个生命周期函数为销毁:beforeDestroy和destroyed。

    • 生命周期函数的位置和data,methods等平级。

  • vue3中要先判断有没有创建实例对象并绑定,只有都完成了才会开始创建,然后挂载。

    • 最后两个生命周期函数改成了卸载:beforeUnmount和unmounted

    • 官网最新的写法是<script setup>,这样把生命周期函数写在script里面就需要在每个前面加on,比如onMounted 。也可以把setup写在script里面,那么这样就有两种写法:写在setup里加on,或者和setup平级,写法和以前一样。

4.侦听器
  • vue2中侦听器默认只进行浅层次监听,只监听传入的那一层,深层监听和立即监听要配置deep和immediate
  • vue3中如果给侦听器传入响应式对象,就默认对里面的属性进行了深层次监听。但是如果是属性里面还是对象,那么就需要自己配置第三个参数{deep:true}了。第三个参数为配置项。

在事件处理中,vue3废弃了按键码,vue2的后面版本应该也废弃了的

5.组件
  • vue2中要求每个组件必须只有一个根元素,否则报错
  • vue3中可以写多个元素,他们会被自动包裹在一个Fragment标签中。
6.组件注册
  • vue2中全局注册使用Vue.component('component-a', { /* ... */ })
  • vue3中全局注册使用app.component('MyComponent', MyComponent)可链式调用
7.自定义事件
  • vue2中如果在组件的根元素上监听原生事件,需要.native修饰符
  • vue3中,只有emits接收的事件才被认为是自定义事件,所以不再需要.native
8.过渡——transition的类名改变。
  • vue2中,从开始位置直接是v-enter / v-leave

    在这里插入图片描述

  • vue3中,开始位置改成了v-enter-from / v-leave-from

    在这里插入图片描述


Vue3新增
1.由于多了<script setup>而产生的新写法
  • props:组件内多出了一种接收方法:const props = defineProps(['foo']),这样接收和直接写在props里是一样的。

  • emits:组件要触发的事件可以显式地通过 defineEmits() 宏来声明。const emit = defineEmits(['inFocus', 'submit'])emits: ['inFocus', 'submit']相同

  • useAttrs:使用 useAttrs() API 来访问一个组件的所有透传 attribute。useAttrs需要引入。

2.Attribute 继承

​ “透传 attribute”是传递给组件的 attribute 或者 v-on 事件监听器,但并没有显式地声明在所接收组件的 propsemits 上。个人理解就是在父组件使用子组件时,给组件添加了class、id、style等,这些会和组件原有的合并。

  1. 透传的 attribute 不会包含 <MyButton> 上声明过的 props 或是针对 emits 声明事件的 v-on 侦听函数,换句话说,声明过的 props 和侦听函数被 <MyButton>“消费”了。
  2. 透传的 attribute 若符合声明,也可以作为 props 传入 <BaseButton>

​ 上面是官方给的注意点,结构应该是这样的 App —— MyButton —— BaseButton。也就是说,App在内部使用MyButton 这个组件时,给他传递了一些class、v-on等,如果MyButton 在 props和 emits 中没接收的部分,也会传递给BaseButton孙组件,当然BaseButton里面也可以使用props去接收。

如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false

应用场景:最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs 选项为 false,你可以完全控制透传进来的 attribute 如何应用。

​ 这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。(这个类似于vue2,接收的在props里面取,没接收的在attrs里面取,但是我不知道vue2能不能传给第三代,所以暂且先放在这里。如果有人指正就修改)

  • 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问。
  • @click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick

​ 当有多个根节点时候,那么就会不知道透传给哪一个节点,会有警告,这时要通过$attrs显式接收,警告才消失

<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
3.依赖注入(跨级通信)
  • vue2中隔代通信有几种办法:通过props接收,层层传递;通过Vuex,这应该是最常用的方法。在vue2的处理边界情况中,也提到了provide和inject方法,但是我看过的视频里都没有提到过,所以我也没有用过。到vue3这边郑重的提出来了。

  • vue3中:

    provide供给:要为组件后代供给数据,需要使用到 provide() 函数,provide要写在setup里面。provide() 函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会注入名用来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。第二个参数是供给的值,值可以是任意类型,包括响应式的状态,比如一个 ref。除了供给一个组件的数据,我们还可以在整个应用层面做供给,即app.provide()

    inject注入:要注入祖先组件供给的数据,需使用 inject() 函数,如果供给的值是一个 ref,注入进来的就是它本身,而不会自动解包。这使得被注入的组件保持了和供给者的响应性链接。注入时第一个参数是注入名,第二个参数是默认值,没有提供数据时使用

4.异步组件
  • vue2中:为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用。

    Vue.component('async-webpack-example', function (resolve) {
     // 这个特殊的 `require` 语法将会告诉 webpack
    // 自动将你的构建代码切割成多个包,这些包
    // 会通过 Ajax 请求加载
    require(['./my-async-component'], resolve)
    })
    
  • vue3中:提供了一个 defineAsyncComponent 方法,但是要搭配内置的 <Suspense> 组件一起使用。

    defineAsyncComponent 方法接收一个返回 Promise 的加载函数。这个 Promise 的 resolve 回调方法应该在从服务器获得组件定义时调用。你也可以调用 reject(reason) 表明加载失败。

    import { defineAsyncComponent } from 'vue'
    
    const AsyncComp = defineAsyncComponent(() => {
      return new Promise((resolve, reject) => {
        // ...从服务器获取组件
        resolve(/* 获取到的组件 */)
      })
    })
    // ... 像使用其他一般组件一样使用 `AsyncComp`
    

    vue2和vue3都支持的就是在局部注册的时候可以直接在里面动态导入一个模块,返回promise对象

    Vue.component(
    'async-webpack-example',
    // 这个动态导入会返回一个 `Promise` 对象。
    () => import('./my-async-component')
    )
    
    import { defineAsyncComponent } from 'vue'
    
    const AsyncComp = defineAsyncComponent(() =>
      import('./components/MyComponent.vue')
    )
    
5. Teleport·传送门

<Teleport> 是一个内置组件,使我们可以将一个组件的一部分模板“传送”到该组件的 DOM 层次结构之外的 DOM 节点中。就是to属性,to=‘传送位置’, :disabled=“isMobile”,disabled禁用

<button @click="open = true">Open Modal</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>
6.Suspense
  • 配合异步使用。可以等待的异步依赖有两种:
  1. 带有异步 setup() 钩子的组件。这也包含了使用 <script setup> 时有顶层 await 表达式的组件。

    export default {
      async setup() {
        const res = await fetch(...)
        const posts = await res.json()
        return {
          posts
        }
      }
    }
    
  2. 异步组件。就是先加载,可以不等该组件加载完先进行显示,加载完后自动显示。

  • <Suspense> 组件有两个插槽:#default#fallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。

    就是说,这个组件内部是封装了两个具名插槽的。default里面应该是要展示的内容,fallback是展示内容未加载完成时显示的内容。

7.性能优化
v-memo

v-memo 是一个内置指令,可以用来有条件地跳过某些大型子树或者 v-for 列表的更新。可以提高性能。可以在元素和组件上使用。该指令需要一个固定长度的依赖值数组来比较记忆。如果数组中的每个值都与上次渲染的值相同,那么将跳过对整个子树的更新。例如:

<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
  <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
  <p>...more child nodes</p>
</div>

​ 当组件的选定状态发生变化时,即使大多数项保持完全相同,也会创建大量的VNodes。这里的v-memo用法本质上是说“只有当这个项目从非选中变为选中时才更新它,反之亦然”。这允许每个未受影响的项目重用其先前的VNode并完全跳过差异。注意我们不需要在memo依赖数组中包含item.id,因为Vue会自动从item的:key中推断出它。

浅层次响应式—— shallowRef() 和 shallowReactive()

​ 通过使用 shallowRef()shallowReactive() 来选择退出深度响应。浅层式 API 创建的状态只在其顶层是响应式的,并原封不动地显示所有下面层级的对象。这使得对深层级属性的访问变得更快,但代价是,我们现在必须将所有深层级对象视为不可变的,并且只能通过替换整个根状态来触发更新

8.组合式API

用法本质上是说“只有当这个项目从非选中变为选中时才更新它,反之亦然”。这允许每个未受影响的项目重用其先前的VNode并完全跳过差异。注意我们不需要在memo依赖数组中包含item.id,因为Vue会自动从item的:key中推断出它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值