👀简单易懂的实践总结!Vue2升级Vue3必看全面对照迁移示例

1,495 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

前言

自从vue3 <script setup> 语法糖出现后,我的个人小项目和毕业设计都开始使用 vue3+ts 进行开发,在经历了很多的业务开发和踩坑经历后,我将vue2项目迁移升级vue3写了一个阶段性总结!

请大家在真正进行项目迁移前务必先看下官方迁移的文档,以下内容尽量讲vue官方文档中比较难找到或者没有讲过的内容。

vue2迁移vue3官网文档介绍

以下内容和示例统一使用的是 vue3 composition api <script setup> 语法糖的写法,我会使用简单的例子和官方文档对应位置的的链接让大家快速理解的。

开发准备

首先如果你是用的是vscode进行vue2的开发,我相信你一定会安装vetur这个拓展插件,我们要做的第一件事是安装一个新的拓展插件 volar , volar 是官方的一个vue3代码提示插件。

具体的插件介绍之后单独出,这期不是重点,但是请务必安装这个拓展后再进行vue3的开发噢!

单文件组件

响应式变量声明

vue2写法

<!-- vue2写法 -->
<template>
  <div>
    {{ a }}
    {{ b.c }}{{ b.d }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      a: "我是oil",
      b: {
        c: "我是张三",
        d: "我是赵四",
      },
    }
  },
}
</script>

vue3写法

<!-- vue3写法 -->
<template>
  <div>
    {{ a }}
    {{ b.c }}{{ b.d }}
  </div>
</template>

<script setup lang="ts">
import { ref, reactive } from "vue"
const a = ref("我是oil")
const b = reactive({
  c: "我是张三",
  d: "我是赵四",
})
</script>

<style lang="scss" scoped></style>

在vue3中响应式变量用 refreactive 两个api来定义,reactive用来定义对象,ref用来定义除了对象的其他数据类型。

reactive api官方介绍

ref api官方介绍

watch,computed及生命周期

vue2写法

<!-- vue2写法 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
    {{ computedA }}
    <!-- 其实我是oil -->
  </div>
</template>

<script>
export default {
  data() {
    return {
      a: "我是oil",
    }
  },
  watch: {
    a: {
      handler() {
        console.log(this.a)
      },
      immediate: true,
      deep: true,
    },
  },
  computed: {
    computedA() {
      return "其实" + this.a
    },
  },
  mounted() {
    this.a = "我不是oi"
  },
}
</script>


vue3写法

<!-- vue3写法 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
    {{ computedA }}
    <!-- 其实我是oil -->
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, watch, computed } from "vue"
const a = ref("我是oil")
watch(
  () => a.value,
  () => {
    console.log(a.value)
  },
  { immediate: true }
)
const computedA = computed(() => "其实" + a.value)
onMounted(() => {
  a.value = "我不是oil"
})
</script>


在vue3中,生命周期computedwatch 都是通过传入回调方法进行方法声明的。具体的使用方法可以看上面的示例和官方的介绍

computed周期 官方文档

watch api官方文档

生命周期 官方文档

父组件向子组件传值

vue2写法

子组件

<!-- vue2子组件 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
    {{ computedA }}
    <!-- 其实我是oil -->
  </div>
</template>

<script>
export default {
  props: {
    a: {
      type: String,
    },
  },
}
</script>

父组件

<!-- vue2父组件 -->
<template>
  <div>
    <vue2-child></vue2-child>
    <!-- 我是oil -->
  </div>
</template>

<script>
import Vue2Child from "./vue2-child.vue"
export default {
  data() {
    return {
      a: "我是oil",
    }
  },
  components: {
    Vue2Child,
  },
}
</script>

vue3写法

子组件

<!-- vue3子组件 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
  </div>
</template>

<script setup lang="ts">
import { onMounted, defineProps } from "vue"
const props = defineProps({
  a: String,
})

onMounted(() => {
  console.log(props.a)
})
</script>

父组件

<!-- vue3父组件 -->
<template>
  <div>
    <vue3-child :a="a"></vue3-child>
    <!-- 我是oil -->
  </div>
</template>

<script setup lang="ts">
import vue3Child from "./vue3-child.vue"
import { ref } from "vue"

const a = ref("我是oil")
</script>

首先展示的是最传统的通过props传值的写法,在vue3中的变化为通过defineProps这个api来定义参数值,且组件的引入无需局部注册,可以直接使用

defineProps api官方文档

通过ref实现子组件向父组件传值

vue2 写法

子组件

<!-- vue2子组件 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
    {{ computedA }}
    <!-- 其实我是oil -->
  </div>
</template>

<script>
export default {
  props: {
    a: {
      type: String,
    },
  },
  data() {
    return {
      b: "这个是我想给父组件的值",
    }
  },
}
</script>

父组件

<!-- vue2父组件 -->
<template>
  <div>
    <vue2-child ref="childRef"></vue2-child>
    <!-- 我是oil -->
  </div>
</template>

<script>
import Vue2Child from "./vue2-child.vue"
export default {
  data() {
    return {
      a: "我是oil",
    }
  },
  components: {
    Vue2Child,
  },
  mounted() {
    console.log(this.$refs.childRef.b)
  },
}
</script>


vue3 写法

子组件

<!-- vue3子组件 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, defineProps, defineExpose } from "vue"
const props = defineProps({
  a: String,
})
const b = ref("这个是我想给父组件的值")
onMounted(() => {
  console.log(props.a)
})
defineExpose({
  b,
})
</script>

父组件

<!-- vue3父组件 -->
<template>
  <div>
    <vue3-child :ref="childRef" :a="a"></vue3-child>
    <!-- 我是oil -->
  </div>
</template>

<script setup lang="ts">
import vue3Child from "./vue3-child.vue"
import { ref, onMounted } from "vue"
const childRef = ref()
const a = ref("我是oil")

onMounted(() => {
  console.log(childRef.value.b) // "这个是我想给父组件的值"
})
</script>

通过以上例子的对比,在vue2中如果父组件想要拿到子组件的值可以通过$ref 被来给元素或子组件注册引用信息,通过childRef指向引用组件实例,这样我们可以获取到子组件上的属性

在vue3中,使用 <script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。所以我们需要通过 defineExpose 将子组件中的变量暴露出去才能在父组件中获取到!

ref api官方文档

defineExpose api官方文档

通过emit实现子组件向父组件传值

vue2写法

子组件

<!-- vue2子组件 -->
<template>
  <div>
    {{ a }}
    <!-- 我是oil -->
    {{ computedA }}
    <!-- 其实我是oil -->
  </div>
</template>

<script>
export default {
  props: {
    a: {
      type: String,
    },
  },
  data() {
    return {
      b: "这个是我想给父组件的值",
    }
  },
  mounted() {
    this.$emit("emitFunc", this.b)
  },
}
</script>


父组件

<!-- vue2父组件 -->
<template>
  <div>
    <vue2-child @emitFunc="getEmit"></vue2-child>
    <!-- 我是oil -->
  </div>
</template>

<script>
import Vue2Child from "./vue2-child.vue"
export default {
  data() {
    return {
      a: "我是oil",
    }
  },
  components: {
    Vue2Child,
  },
  getEmit(data) {
    console.log(data) // "这个是我想给父组件的值"
  },
}
</script>

vue3写法

子组件

<!-- vue3子组件 -->
<template>
  <div></div>
</template>

<script setup lang="ts">
import { ref, onMounted, defineEmits } from "vue"
const emit = defineEmits(["emitFunc"])
const b = ref("这个是我想给父组件的值")
onMounted(() => {
  emit("emitFunc", b.value)
})
</script>

父组件

<!-- vue3父组件 -->
<template>
  <div>
    <vue3-child @emitFunc="getEmit"></vue3-child>
  </div>
</template>

<script setup lang="ts">
import vue3Child from "./vue3-child.vue"
const getEmit = (data: string) => {
  console.log(data) // "这个是我想给父组件的值"
}
</script>

在vue3中实现子组件触发父组件的方法并传值给父组件需要通过defineEmits api来实现,上面的例子通过emit定义了一个 emitFunc 方法,在定义后我们可以通过emit方法传入一个参数来触发指定的事件。

defineEmits api官方介绍

通过project 和 inject实现爷孙组件传值

vue3写法

孙子组件

<!-- vue3孙组件 -->
<template>
  <div>
    {{ a }}
  </div>
</template>

<script setup lang="ts">
import { inject } from "vue"
const a = inject("grandpaData")
</script>

爷爷组件

<!-- vue3爷组件 -->
<template>
  <div>
    <vue3-child></vue3-child>
  </div>
</template>

<script setup lang="ts">
import { provide } from "vue"
provide("grandpaData", "我是来自爷爷的值")
</script>

无论在vue2还是在vue3中都有可能涉及到变量需要跨多个组件传递的问题,这个实现的方法其实有很多种,上面的示例展示的是通过projectinject实现.

实际开发中不仅可以通过 vuexlocalstorage这种定义全局变量的方式实现,还可以通过一层一层的去写props传递参数或通过 parent,parent,ref 的方式来拿到跨多组件的值,根据实际业务出发才是最好的。

抽离组件逻辑代码,实现模块化

vue2实现mixin

业务逻辑代码

// Vue2Mixin.js
export default {
  data() {
    return {
      b: "我是vue2的mixin中的值"
    }
  },
  mounted() {
    console.log("我被放在mixin中了,这样可以减少我的代码")
  }
}

组件代码

<!-- vue2组件 -->
<template>
  <div>{{b}}</div>
</template>

<script>
import Vue2Mixin from "./Vue2Mixin"
export default {
  data() {
    return {
      a: "我是oil",
    }
  },
  mixins: [Vue2Mixin],
}
</script>

如上示例,在vue2中我们可以通过mixin api来抽离分类一些业务代码,简化我们组件中的代码,但是要注意 变量名重复等一些问题

vue3实现hook

业务逻辑代码

//hook.ts
import { ref, onMounted } from "vue"
export default function useHook() {
  const b = ref("我是vue3的hook中的值")
  onMounted(() => {
    console.log("我被放在hook中了,这样可以减少我的代码")
  })
  return { b }
}

组件代码

<!-- vue3组件 -->
<template>
  <div>
    {{ b }}
  </div>
</template>

<script setup lang="ts">
import useHook from "./hook"
const { b } = useHook()
</script>

如上示例,在vue3中我们可以通过写 hook 的方式来抽离分类一些业务代码,简化我们组件中的代码。

咱们可以和 mixin 对比一下看看,咱们在两个例子中都将 变量b 给抽离出去了,然后通过不同的方式引入,但是在 mixin 中我们是不能看到具体引入那些值的,而使用hook的话就非常的清晰,也可以避免变量名重复的情况!

vue2 css穿透scoped作用域

<style lang="scss" scoped>
/deep/.el-upload-dragger {
  width: 20rem;
}
/deep/.el-upload-list__item {
  margin: 0;
}
</style>

vue2中使用 /deep/ + 选择器名称 进行深层样式穿透scoped选中

vue3 css穿透scoped作用域

<style lang="scss" scoped>
deep(.el-upload-dragger) {
  width: 20rem;
}
deep(.el-upload-list__item) {
  margin: 0;
}
</style>

vue2中使用 deep(选择器名称) 进行深层样式穿透scoped选中

总结

vue3相比vue2来说迁移的难度和纬度是根据个人能力而定的,在vue3中你大可以类似vue2一样使用option API的方式来写代码,但是既然要升级,咱们就要追求一个最佳实践嘛。

而且使用 componsition api 的话对ts的支持更加友好噢,所以建议如果要升级vue3的话就直接使用 script setup 的语法再加上 typescript绝对给你远超vue2的开发体验噢

这里是新人oil欧呦 ,是一个一直秉持 输出倒逼输入 思想的前端开发,如果文章对你有帮助,希望在收藏的同时点个赞噢!