0


【全方位对比】前端vue2、vue3、vue3语法糖三种写法

Vue


前言

从Vue3发布以来,陆陆续续也有很多项目慢慢的从vue2往vue3转变,特别是vue3.2退出setup语法糖之后,很大层度的简化了vue3的书写,下面我们来看一下,vue2、vue3、和vue3setup语法糖三种写法的区别。


1、点击切换页面数据实例对比

1.1、vue2

<template><div @click="changeMsg">{{msg}}</div></template><script>
export default{data(){return{
     msg:'hello world'}},
  methods:{changeMsg(){
      this.msg ='hello juejin'}}}</script>

1.2、vue3

<template><div @click="changeMsg">{{msg}}</div></template><script>
import { ref,defineComponent } from "vue";
export defaultdefineComponent({setup(){const msg =ref('hello world')const changeMsg =()=>{
      msg.value ='hello juejin'}return{
  msg,
  changeMsg
};},});</script>

1.3、vue3 setup语法糖

<template><div @click="changeMsg">{{ msg }}</div></template><script setup>
import { ref } from "vue";const msg =ref('hello world')const changeMsg =()=>{
  msg.value ='hello juejin'}</script>

总结:
vue2是将data和methods包括后面的watch,computed等分开管理;
vue3是将相关逻辑放到了一起(类似于原生js开发);
setup语法糖则可以让变量方法不用再写return,后面的组件甚至是自定义指令也可以在我们的template中自动获得。

2、ref 和 reactive

vue2中data函数中的数据都具有响应式,页面会随着data中的数据变化而变化;而vue3不存在data函数,所以Vue3引入了ref和reactive函数来将使得变量成为响应式的数据

2.1、vue2

<script>
export default{data(){return{
     msg:'hello world'
     obj:{
         name:'juejin',
        age:3}}},
  methods:{changeData(){
      this.msg ='hello juejin',
      this.obj.name ='hello world'}}}</script>

2.2、vue3

<script>
import { ref,reactive,defineComponent } from "vue";
export defaultdefineComponent({setup(){
let msg =ref('hello world')
let obj =reactive({
    name:'juejin',
    age:3})const changeData =()=>{
  msg.value ='hello juejin'
  obj.name ='hello world'}return{
msg,
obj,
changeData
};},});</script>

2.3、vue3 setup语法糖

<script setup>
import { ref,reactive } from "vue";
let msg =ref('hello world')
let obj =reactive({
    name:'juejin',
    age:3})const changeData =()=>{
  msg.value ='hello juejin'
  obj.name ='hello world'}</script>

总结:

使用ref的时候在js中取值的时候需要加上.value。

reactive更推荐去定义复杂的数据类型 ref 更推荐定义基本类型

3、生命周期

Vue2和Vue3生命周期的差异
Vue2Vue3(setup)描述beforeCreate-实例创建前beforeCreate-实例创建后beforeMountonBeforeMountDOM挂载前调用mountedonMountedDOM挂载完成调用beforeUpdateonBeforeUpdate数据更新之前被调用updatedonUpdated数据更新之后被调用beforeDestroyonBeforeUnmount组件销毁前调用destroyedonUnmounted组件销毁完成调用

4、使用mounted做对比

4.1、vue2

<script>
export default{mounted(){
    console.log('挂载完成')}}</script>

4.2、vue3

<script>
import { onMounted,defineComponent } from "vue";
export defaultdefineComponent({setup(){onMounted(()=>{
  console.log('挂载完成')})return{
onMounted
};},});</script>

4.3、vue3 setup语法糖

<script setup>
import { onMounted } from "vue";onMounted(()=>{
  console.log('挂载完成')})</script>

总结:Vue3中的生命周期相对于Vue2做了一些调整,命名上发生了一些变化并且移除了beforeCreate和created,因为setup是围绕beforeCreate和created生命周期钩子运行的,所以不再需要它们。

5、watch和computed

5.1、vue2

<template><div>{{ addSum }}</div></template><script>
export default{data(){return{
      a:1,
      b:2}},
  computed:{addSum(){return this.a + this.b
    }},
  watch:{a(newValue, oldValue){
      console.log(`a从${oldValue}变成了${newValue}`)}}}</script>

5.2、vue3

<template><div>{{addSum}}</div></template><script>
import { computed, ref, watch, defineComponent } from "vue";
export defaultdefineComponent({setup(){const a =ref(1)const b =ref(2)
    let addSum =computed(()=>{return a.value+b.value
    })watch(a,(newValue, oldValue)=>{
     console.log(`a从${oldValue}变成了${newValue}`)})return{
      addSum
    };},});</script>

5.3、vue3 setup语法糖

<template><div>{{ addSum }}</div></template><script setup>
import { computed, ref, watch } from "vue";const a =ref(1)const b =ref(2)
let addSum =computed(()=>{return a.value + b.value
})watch(a,(newValue, oldValue)=>{
  console.log(`a从${oldValue}变成了${newValue}`)})</script>

Vue3中除了watch,还引入了副作用监听函数watchEffect;
watchEffect它会立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

例如:

<template><div>{{ watchTarget }}</div></template><script setup>
import { watchEffect,ref } from "vue";const watchTarget =ref(0)watchEffect(()=>{
  console.log(watchTarget.value)})setInterval(()=>{
  watchTarget.value++},1000)</script>

首先刚进入页面就会执行watchEffect中的函数打印出:0,随着定时器的运行,watchEffect监听到依赖数据的变化回调函数每隔一秒就会执行一次

总结:

computed和watch所依赖的数据必须是响应式的。Vue3引入了watchEffect,watchEffect 相当于将 watch 的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watch的是watchEffect的回调函数会被立即执行,即({ immediate: true })

6、组件通信

Vue2Vue3(setup)描述propsprops父传子$emitemits子传父$attrsattrs父传子$listeners无(合并到 attrs方式)子传父provideprovide父传子injectinject子传父$parent无子组件访问父组件$children无父组件访问子组件$refexpose&ref父组件访问子组件EventBusmitt兄弟传值

7、props传值

props是组件通信中最常用的通信方式之一。父组件通过v-bind传入,子组件通过props接收,下面是它的三种实现方式

7.1、vue2

//父组件<template><div><Child :msg="parentMsg"/></div></template><script>
import Child from './Child'
export default{
  components:{
    Child
  },data(){return{
      parentMsg:'父组件信息'}}}</script>//子组件<template><div>{{msg}}</div></template><script>
export default{
  props:['msg']}</script>

7.2、vue3

//父组件<template><div><Child :msg="parentMsg"/></div></template><script>
import { ref,defineComponent } from 'vue'
import Child from './Child.vue'
export defaultdefineComponent({
  components:{
    Child
  },setup(){const parentMsg =ref('父组件信息')return{
      parentMsg
    };},});</script>//子组件<template><div>{{ parentMsg }}</div></template><script>
import { defineComponent,toRef } from "vue";
export defaultdefineComponent({
    props:["msg"],// 如果这行不写,下面就接收不到setup(props){
        console.log(props.msg)//父组件信息
        let parentMsg =toRef(props,'msg')return{
            parentMsg
        };},});</script>

7.3、vue3 setup语法糖

//父组件<template><div><Child :msg="parentMsg"/></div></template><script setup>
import { ref } from 'vue'
import Child from './Child.vue'const parentMsg =ref('父组件信息')</script>//子组件<template><div>{{ parentMsg }}</div></template><script setup>
import { toRef, defineProps } from "vue";const props =defineProps(["msg"]);
console.log(props.msg)//父组件信息
let parentMsg =toRef(props,'msg')</script>

总结:props中数据流是单项的,即子组件不可改变父组件传来的值

在组合式API中,如果想在子组件中用其它变量接收props的值时需要使用toRef将props中的属性转为响应式。

8、emit

子组件可以通过emit发布一个事件并传递一些参数,父组件通过v-onj进行这个事件的监听

8.1、vue2

//父组件<template><div><Child @sendMsg="getFromChild"/></div></template><script>
import Child from './Child'
export default{
  components:{
    Child
  },
  methods:{getFromChild(val){
      console.log(val)//我是子组件数据}}}</script>// 子组件<template><div><button @click="sendFun">send</button></div></template><script>
export default{
  methods:{sendFun(){
      this.$emit('sendMsg','我是子组件数据')}}}</script>

8.2、vue3

//父组件<template><div><Child @sendMsg="getFromChild"/></div></template><script>
import Child from './Child'
import { defineComponent } from "vue";
export defaultdefineComponent({
  components:{
    Child
  },setup(){const getFromChild =(val)=>{
      console.log(val)//我是子组件数据}return{
      getFromChild
    };},});</script>//子组件<template><div><button @click="sendFun">send</button></div></template><script>
import { defineComponent } from "vue";
export defaultdefineComponent({
    emits:['sendMsg'],setup(props, ctx){const sendFun =()=>{
            ctx.emit('sendMsg','我是子组件数据')}return{
            sendFun
        };},});</script>

8.3、vue3 setup语法糖

//父组件<template><div><Child @sendMsg="getFromChild"/></div></template><script setup>
import Child from './Child'const getFromChild =(val)=>{
      console.log(val)//我是子组件数据}</script>//子组件<template><div><button @click="sendFun">send</button></div></template><script setup>
import { defineEmits } from "vue";const emits =defineEmits(['sendMsg'])const sendFun =()=>{emits('sendMsg','我是子组件数据')}</script>

9、attrs和listeners

子组件使用$attrs可以获得父组件除了props传递的属性和特性绑定属性 (class和 style)之外的所有属性。

子组件使用$listeners可以获得父组件(不含.native修饰器的)所有v-on事件监听器,在Vue3中已经不再使用;但是Vue3中的attrs不仅可以获得父组件传来的属性也可以获得父组件v-on事件监听器

9.1、vue2

//父组件<template><div><Child @parentFun="parentFun":msg1="msg1":msg2="msg2"/></div></template><script>
import Child from './Child'
export default{
  components:{
    Child
  },data(){return{
      msg1:'子组件msg1',
      msg2:'子组件msg2'}},
  methods:{parentFun(val){
      console.log(`父组件方法被调用,获得子组件传值:${val}`)}}}</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script>
export default{
  methods:{getParentFun(){
      this.$listeners.parentFun('我是子组件数据')}},created(){//获取父组件中所有绑定属性
    console.log(this.$attrs)//{"msg1": "子组件msg1","msg2": "子组件msg2"}//获取父组件中所有绑定方法    
    console.log(this.$listeners)//{parentFun:f}}}</script>

9.2、vue3

//父组件<template><div><Child @parentFun="parentFun":msg1="msg1":msg2="msg2"/></div></template><script>
import Child from './Child'
import { defineComponent,ref } from "vue";
export defaultdefineComponent({
  components:{
    Child
  },setup(){const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')const parentFun =(val)=>{
      console.log(`父组件方法被调用,获得子组件传值:${val}`)}return{
      parentFun,
      msg1,
      msg2
    };},});</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script>
import { defineComponent } from "vue";
export defaultdefineComponent({
    emits:['sendMsg'],setup(props, ctx){//获取父组件方法和事件
        console.log(ctx.attrs)//Proxy {"msg1": "子组件msg1","msg2": "子组件msg2"}const getParentFun =()=>{//调用父组件方法
            ctx.attrs.onParentFun('我是子组件数据')}return{
            getParentFun
        };},});</script>

9.3、vue3 setup语法糖

//父组件<template><div><Child @parentFun="parentFun":msg1="msg1":msg2="msg2"/></div></template><script setup>
import Child from './Child'
import { ref } from "vue";const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')const parentFun =(val)=>{
  console.log(`父组件方法被调用,获得子组件传值:${val}`)}</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script setup>
import { useAttrs } from "vue";const attrs =useAttrs()//获取父组件方法和事件
console.log(attrs)//Proxy {"msg1": "子组件msg1","msg2": "子组件msg2"}const getParentFun =()=>{//调用父组件方法
    attrs.onParentFun('我是子组件数据')}</script>

注意:Vue3中使用attrs调用父组件方法时,方法前需要加上on;如parentFun->onParentFun

10、provide/inject

provide:是一个对象,或者是一个返回对象的函数。里面包含要给子孙后代属性

inject:一个字符串数组,或者是一个对象。获取父组件或更高层次的组件provide的值,既在任何后代组件都可以通过inject获得

10.1、vue2

//父组件<script>
import Child from './Child'
export default{
  components:{
    Child
  },data(){return{
      msg1:'子组件msg1',
      msg2:'子组件msg2'}},provide(){return{
      msg1: this.msg1,
      msg2: this.msg2
    }}}</script>//子组件<script>
export default{
  inject:['msg1','msg2'],created(){//获取高层级提供的属性
    console.log(this.msg1)//子组件msg1
    console.log(this.msg2)//子组件msg2}}</script>

10.2、vue3

//父组件<script>
import Child from './Child'
import { ref, defineComponent,provide } from "vue";
export defaultdefineComponent({
  components:{
    Child
  },setup(){const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')provide("msg1", msg1)provide("msg2", msg2)return{}},});</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script>
import { inject, defineComponent } from "vue";
export defaultdefineComponent({setup(){
        console.log(inject('msg1').value)//子组件msg1
        console.log(inject('msg2').value)//子组件msg2},});</script>

10.3、vue3 setup语法糖

//父组件<script setup>
import Child from './Child'
import { ref,provide } from "vue";const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')provide("msg1",msg1)provide("msg2",msg2)</script>//子组件<script setup>
import { inject } from "vue";
console.log(inject('msg1').value)//子组件msg1
console.log(inject('msg2').value)//子组件msg2</script>

provide/inject一般在深层组件嵌套中使用合适。一般在组件开发中用的居多。

11、parent/children

$parent: 子组件获取父组件Vue实例,可以获取父组件的属性方法等

$children: 父组件获取子组件Vue实例,是一个数组,是直接儿子的集合,但并不保证子组件的顺序

11.1、vue2

import Child from './Child'
export default{
  components:{
    Child
  },created(){
    console.log(this.$children)//[Child实例]
    console.log(this.$parent)//父组件实例}}

12、expose&ref

$refs可以直接获取元素属性,同时也可以直接获取子组件实例

12.1、vue2

//父组件<template><div><Child ref="child"/></div></template><script>
import Child from './Child'
export default{
  components:{
    Child
  },mounted(){//获取子组件属性
    console.log(this.$refs.child.msg)//子组件元素//调用子组件方法
    this.$refs.child.childFun('父组件信息')}}</script>//子组件 <template><div><div></div></div></template><script>
export default{data(){return{
      msg:'子组件元素'}},
  methods:{childFun(val){
      console.log(`子组件方法被调用,值${val}`)}}}</script>

12.2、vue3

//父组件<template><div><Child ref="child"/></div></template><script>
import Child from './Child'
import { ref, defineComponent, onMounted } from "vue";
export defaultdefineComponent({
  components:{
    Child
  },setup(){const child =ref()//注意命名需要和template中ref对应onMounted(()=>{//获取子组件属性
      console.log(child.value.msg)//子组件元素//调用子组件方法
      child.value.childFun('父组件信息')})return{
      child //必须return出去 否则获取不到实例}},});</script>//子组件<template><div></div></template><script>
import { defineComponent, ref } from "vue";
export defaultdefineComponent({setup(){const msg =ref('子组件元素')const childFun =(val)=>{
            console.log(`子组件方法被调用,值${val}`)}return{
            msg,
            childFun
        }},});</script>

12.3、vue3 setup语法糖

//父组件<template><div><Child ref="child"/></div></template><script setup>
import Child from './Child'
import { ref, onMounted } from "vue";const child =ref()//注意命名需要和template中ref对应onMounted(()=>{//获取子组件属性
  console.log(child.value.msg)//子组件元素//调用子组件方法
  child.value.childFun('父组件信息')})</script>//子组件<template><div></div></template><script setup>
import { ref,defineExpose } from "vue";const msg =ref('子组件元素')const childFun =(val)=>{
    console.log(`子组件方法被调用,值${val}`)}//必须暴露出去父组件才会获取到defineExpose({
    childFun,
    msg
})</script>

注意:
通过ref获取子组件实例必须在页面挂载完成后才能获取。

在使用setup语法糖时候,子组件必须元素或方法暴露出去父组件才能获取到

13、EventBus/mitt

兄弟组件通信可以通过一个事件中心EventBus实现,既新建一个Vue实例来进行事件的监听,触发和销毁。

在Vue3中没有了EventBus兄弟组件通信,但是现在有了一个替代的方案mitt.js,原理还是 EventBus

13.1、vue2

//组件1<template><div><button @click="sendMsg">传值</button></div></template><script>
import Bus from './bus.js'
export default{data(){return{
      msg:'子组件元素'}},
  methods:{sendMsg(){
      Bus.$emit('sendMsg','兄弟的值')}}}</script>//组件2<template><div>
    组件2</div></template><script>
import Bus from './bus.js'
export default{created(){
   Bus.$on('sendMsg',(val)=>{
    console.log(val);//兄弟的值})}}</script>//bus.js

import Vue from "vue"
export default new Vue()

13.2、vue3

首先安装mitt

npm i mitt -S

然后像Vue2中bus.js一样新建mitt.js文件
mitt.js

import mitt from'mitt'const Mitt =mitt()exportdefault Mitt
//组件1<template><button @click="sendMsg">传值</button></template><script>
import { defineComponent } from "vue";
import Mitt from './mitt.js'
export defaultdefineComponent({setup(){const sendMsg =()=>{
            Mitt.emit('sendMsg','兄弟的值')}return{
           sendMsg
        }},});</script>//组件2<template><div>
    组件2</div></template><script>
import { defineComponent, onUnmounted } from "vue";
import Mitt from './mitt.js'
export defaultdefineComponent({setup(){const getMsg =(val)=>{
      console.log(val);//兄弟的值}
    Mitt.on('sendMsg', getMsg)onUnmounted(()=>{//组件销毁 移除监听
      Mitt.off('sendMsg', getMsg)})},});</script>

13.3、vue3 setup语法糖

//组件1<template><button @click="sendMsg">传值</button></template><script setup>
import Mitt from './mitt.js'const sendMsg =()=>{
    Mitt.emit('sendMsg','兄弟的值')}</script>//组件2<template><div>
    组件2</div></template><script setup>
import { onUnmounted } from "vue";
import Mitt from './mitt.js'const getMsg =(val)=>{
  console.log(val);//兄弟的值}
Mitt.on('sendMsg', getMsg)onUnmounted(()=>{//组件销毁 移除监听
  Mitt.off('sendMsg', getMsg)})</script>

14、v-model和sync

-model大家都很熟悉,就是双向绑定的语法糖。这里不讨论它在input标签的使用;只是看一下它和sync在组件中的使用

我们都知道Vue中的props是单向向下绑定的;每次父组件更新时,子组件中的所有props都会刷新为最新的值;但是如果在子组件中修改 props ,Vue会向你发出一个警告(无法在子组件修改父组件传递的值);可能是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得混乱难以理解。

但是可以在父组件使用子组件的标签上声明一个监听事件,子组件想要修改props的值时使用$emit触发事件并传入新的值,让父组件进行修改。

为了方便vue就使用了v-model和sync语法糖。

14.1、vue2

//父组件<template><div><!-- 
      完整写法
      <Child :msg="msg" @update:changePval="msg=$event"/>--><Child :changePval.sync="msg"/>{{msg}}</div></template><script>
import Child from './Child'
export default{
  components:{
    Child
  },data(){return{
      msg:'父组件值'}}}</script>//子组件<template><div><button @click="changePval">改变父组件值</button></div></template><script>
export default{data(){return{
      msg:'子组件元素'}},
  methods:{changePval(){//点击则会修改父组件msg的值
      this.$emit('update:changePval','改变后的值')}}}</script>

14.2、vue3 setup语法糖

//父组件<template><div><!-- 
      完整写法
      <Child :msg="msg" @update:changePval="msg=$event"/>--><Child v-model:changePval="msg"/>{{msg}}</div></template><script setup>
import Child from './Child'
import { ref } from 'vue'const msg =ref('父组件值')</script>//子组件<template><button @click="changePval">改变父组件值</button></template><script setup>
import { defineEmits } from 'vue';const emits =defineEmits(['changePval'])const changePval =()=>{//点击则会修改父组件msg的值emits('update:changePval','改变后的值')}</script>

总结:
vue3中移除了sync的写法,取而代之的式v-model:event的形式

其v-model:changePval="msg"或者:changePval.sync="msg"的完整写法为:msg=“msg” @update:changePval=“msg=$event”。

所以子组件需要发送update:changePval事件进行修改父组件的值

15、路由

vue3和vue2路由常用功能只是写法上有些区别

15.1、vue2

<template><div><button @click="toPage">路由跳转</button></div></template><script>
export default{beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),
  methods:{toPage(){//路由跳转
      this.$router.push(xxx)}},created(){//获取params
    this.$router.params
    //获取query
    this.$router.query
  }}</script>

15.2、vue3

<template><div><button @click="toPage">路由跳转</button></div></template><script>
import { defineComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export defaultdefineComponent({beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),setup(){const router =useRouter()const route =useRoute()const toPage =()=>{
      router.push(xxx)}//获取params 注意是route
    route.params
    //获取query
    route.query
    return{
      toPage
    }},});</script>

15.3、vue3 setup语法糖

我之所以用beforeRouteEnter作为路由守卫的示例是因为它在setup语法糖中是无法使用的;大家都知道setup中组件实例已经创建,是能够获取到组件实例的。而beforeRouteEnter是再进入路由前触发的,此时组件还未创建,所以是无法setup中的;如果想在setup语法糖中使用则需要再写一个setup语法糖的script 如下:

<template><div><button @click="toPage">路由跳转</button></div></template><script>
export default{beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},};</script><script setup>
import { useRoute, useRouter,onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'const router =useRouter()const route =useRoute()const toPage =()=>{
  router.push(xxx)}//获取params 注意是route
route.params
//获取query
route.query

//路由守卫onBeforeRouteUpdate((to, from, next)=>{//当前组件路由改变后,进行触发next()})onBeforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()})</script>

provide/inject一般在深层组件嵌套中使用合适。一般在组件开发中用的居多。


本文转载自: https://blog.csdn.net/m0_47791238/article/details/135014336
版权归原作者 smileAgain-lg 所有, 如有侵权,请联系我们删除。

“【全方位对比】前端vue2、vue3、vue3语法糖三种写法”的评论:

还没有评论