4

【建议收藏】通过差异化对比学习法,带你回顾Vue2快速掌握Vue3

 2 years ago
source link: https://segmentfault.com/a/1190000041645889
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

海阔凭鱼跃,天高任鸟飞。Hey 你好!我是猫力Molly

Vue3已经发布有一段时间了,同时也得到了各大厂商和社区的支持和众多开发者喜爱,周边生态也正在逐步完善。可谓是一片欣欣向荣的美景。本文意在通过梳理Vue2常用api通过差异化对比Vue3,帮助你快速掌握Vue3

本文假设你已经有一定vue2实操经验,不会过多描述api细节

为什么要升级Vue3?

两个关键的因素导致了我们考虑重写Vue新的主要版本:

  1. 主流浏览器对新的JavaScript语言特性的普遍支持。
  2. 当前Vue代码库随着时间的推移而暴露出来的设计和体系架构问题。

更多细节,我们可以听听祖师爷在知乎的回答尤雨溪亲笔:重头来过的 Vue 3 带来了什么?

vue2 VS vue3(编码体感)

近期做了一个关于“共享童车”的后台管理系统项目,由于是新项目,我便大胆选定了vue3+vite+typescript+element plus作为基础技术栈。不过目前市面上并没有一款免费且好用的中后台基础模板框架,于是我又仿照着花裤衩大佬的传送门:vue-admin-template照猫画虎的写了一个vue3版本的vue-element-admin并且应用到实际项目中。就我个人编码习惯而言,相对于vue2Option api的直观明了,vue3Composition api可以更好的组织代码,支持自定义hooks来达到代码复用,从而取代mixin,代码风格上,也可以把类似的业务逻辑写到一个代码块,从而避免代码分散,走读代码需要上下反复横跳。更友好的支持了TS以及众多新特性的加入,也让vue3写起来更爽,更利于代码维护和拓展。结合vite使用,更是极大提升了开发体验。总体来说,让我们拥抱vue3吧,给vue团队点个赞👍👍👍

vue3比较直观的新特性

  • 全新的Composition api让我们换一种方式组织代码
  • <script setup> 语法糖,使得代码更简洁
  • 可以省去template的根元素包裹标签
  • 提供了新的内置组件<teleport>,支持了组件可以挂载到任意dom节点下
  • 提供了在css中使用v-bind来引入script变量,又一个强大的黑魔法小技巧
  • 使用createApp来创建应用实例
  • 更友好的TS支持
  • 使用proxy代理方式来替换掉defineproperty
  • 全局和内部 API 已经被重构为支持 tree-shake

option api VS composition api

composition apivue3的一大特色,vue3对外暴露了大量函数供以我们按需引用,随意组合

option api中,我们通过实例化Vue并将行为对象作为参数传入

new Vue({
    data(){
        return {}
    },
    methods:{},
    computed:{},
    created(){},
    ...
})

Composition api 我们可以通过setup作为入口函数,并返回一个对象数据暴露给模板使用

<template>
  <div @click="hi">{{ msg }}</div>
</template>
<script>
export default {
    setup() {
      const msg = ref('Hello!')
      function hi() {
        console.log(msg)
      }
      // 暴露给模板
      return {
        msg,
        hi
      }
    }
  }
</script>

setup还支持另一种写法,更加简化了代码

<template>
  <div @click="hi">{{ msg }}</div>
</template>

<script setup>
const msg = ref('Hello!')
function hi() {
  console.log(msg)
}
</script>

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用

响应数据的定义

vue2中把响应数据定义到data函数的return

data(){
    return {
        ...
    }
}

vue3中我们可以通过refreactive来定义响应数据

const num = ref(0)
const obj = reactive({name:'molly'})

ref:

  • 在模板中可以直接使用ref定义的数据,在js中则需要使用.value来取值或赋值
  • 使用geterseter方式实现响应式
  • 建议使用ref来定义基础数据

reactive:

  • 可使用toRefs将其解包,在模板中直接使用对应的attribute
  • 使用proxy代理方式实现响应式
  • 建议使用reactive来定义复杂数据

生命周期钩子

vue2中提供了11个生命周期钩子,我们可在选项对象上直接定义使用

vue3中把生命周期钩子单独抽离成了一个个对应的hooks函数,以onXXX的形式调用。值得注意的是生命周期钩子注册函数只能在setup期间同步使用,这就意味着beforeCreatecreated等同于setup执行阶段。其他的周期用法基本一致,例如:mounted对应onMounted

import { onMounted } from 'vue'
setup() {
    onMounted(() => {
        console.log('mounted!')
    })
}

computed

两个版本的computed基本保持一致,不同的是vue3computed抽离成一个hooks函数使用

// vue2
computed:{
    num:()=>this.num *2
}
// vue3

const numVal = computed(() => num.value *2)

watch

vue2:

watch监听一个特定数据源的结果变化,回调函数得到的参数为新值和旧值。除了监听data中的数据
还可以监听props$route$emitcomputed。可通过选项参数deep来深度监听,指定 immediate: true 将立即触发回调

watch:{
    obj:(val,old)=>{
        deep: true,
        immediate: true
    }
}

vue3

vue3的计算属性得到了很大的加强,支持监听多个数据源和执行副作用

// 监听单个数据源
const count = ref(0)
watch(count, (val, old) => {
  /* ... */
})
// 假设count是一个对象,也支持监听整个对象,而无需指定deep属性
// 监听多个数据源
const count = ref(0)
const obj = reactive({name:'molly'})
watch([() => obj.name, count], ([newName, newCount], [oldName, oldCount]) => {
    /* ... */
})
// 监听多个数据源时,watch函数的第一个参数可传入数据源数组,第二个回调函数的参数也是一个数组

vue3还新增了watchEffect,表示立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

watch相比,watchEffect不需要传入指定监听的数据源,它会自动收集依赖。也没有新值旧值的概念,只要依赖发生变化,就会重新执行函数

const count = ref(0)
watchEffect(() => console.log(count.value))
setTimeout(() => {
  count.value++
}, 100)

watchEffect 会返回一个用于停止这个监听的函数

watchEffect在组件的setup函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

const stop = watchEffect(() => {
  /* ... */
})
stop()

filters

vue2中我们可以很方便的使用filters过滤器来处理一些文本格式转换,对数据进行加工。

filters: {
  sum(num1,num2) {
    return num1+num2
  }
}

请注意:vue3中,将移除过滤器,且不再支持。官方更推荐我们使用方法调用或计算属性来替换filters

至于为啥要移除这个api官方的解释是:虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。

我的理解是:api功能设计重复,filters能干的事儿,计算属性和函数调用也能干,且干的更好。所以尤大大含泪移除filters,可怜的filters只能被抛弃

人生亦是如此,职场中优胜劣汰,希望我们永远都不会是那个被优化掉的filters😭😭😭

components

vue2中我们需要通过选项components进行组件注册,而在vue3中,我们可以直接使用组件

//vue2 
import A from './a.vue'
components:{
    A
}
//vue3
<template>
  <A/>
</template>
<script setup>
    import A from './a.vue'
</script>

指令:vue2 VS vue3

两个版本的指令用法基本相同,这里我只列出vue3差异部分

v-model

vue2中我们实现一个自定义v-model可以这样写

// vue2
props:{
    title:{
        type:String,
        default: 'molly'
    }
},
model: {
    prop: 'title',
    event: 'change',
},
methods:{
    change(val){
        this.$emit('input',val)
    }
},

vue3中可以定义v-model参数名,同时还支持设置多个v-model,简直美滋滋

// vue3
props:{
    title:{
        type:String
    },
    num:{
        type:Number
    },
},
setup(props,{emit}){
    function change1(val){
        emit('update:title',val)
    }
    function change2(val){
        emit('update:num',val)
    }
    return {
        change1,
        change2
    }
}

// 在父组件中使用
<Son v-model:title="molly" v-model:num="18" />
  • v-memo,该指令记住一个模板的子树。元素和组件上都可以使用。该指令接收一个固定长度的数组作为依赖值进行记忆比对。如果数组中的每个值都和上次渲染的时候相同,则整个该子树的更新会被跳过。相当于内存换时间,相同渲染内容则从内存读取。这对于长列表场景很有用
  • 对于 v-if/v-else/v-else-if 的各分支项 key 将不再是必须的,因为现在 vue3 会自动生成唯一的 key
  • <template v-for> 的 key 应该设置在 <template> 标签上 (而不是设置在它的子节点上)。
  • 作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。
  • v-on 的 .native 修饰符已被移除。
  • v-for 中的 ref 不再注册ref数组
  • 在使用v-bind="object"与组件独立属性重名时,那么绑定的声明顺序将决定它们如何被合并。以后者为标准

    <!-- 模板 -->
    <div id="red" v-bind="{ id: 'blue' }"></div>
    <!-- 结果 -->
    <div id="blue"></div>
    
    <!-- 模板 -->
    <div v-bind="{ id: 'blue' }" id="red"></div>
    <!-- 结果 -->
    <div id="red"></div>

组件通信:vue2 VS vue3

vue2中提供了多种api供以我们进行组件通信:

  • props / $emit / $on
  • $attrs / $listeners
  • provide / inject
  • $parent / $children / ref

vue3中依然支持大部分api,并做了适当调整

  • 移除了$on$off$once实例方法
  • 移除了$children实例property
  • $attrs 现在包含了所有传递给组件的 attribute,包括 class 和 style
  • 在 <script setup> 中必须使用 defineProps 和 defineEmits API 来声明 props 和 emits ,它们具备完整的类型推断并且在 <script setup> 中是直接可用的

插槽 vue2 VS vue3

在插槽这一块两个版本基本保持一致,没有太多的改动,还是保留原来的使用方式。vue3做了一点点小更新

  • this.$slots 现在将插槽作为函数公开
  • 移除 this.$scopedSlots

代码复用 vue2 VS vue3

vue2中代码复用的手段很多,主要有以下几种方式

  • 自定义指令
  • 实例全局挂载
  • mixin
  • extend

vue3中同样涵盖以上手段,并做了相应优化与更新

  • 实例全局挂载这一手段在vue2中,我们一般是简单粗暴的通过prototype将行为对象挂载到vue原型上,这种方式虽然简单明了,但是也存在全局污染的问题。
Vue.prototype.$xxx = {name:'molly'}

对应到vue3中,则不允许我们这样子干,取而代之的是 app.config.globalProperties

  • mixin也是代码复用的一大利器,不过相应的也暴露出一些问题。当mixin被滥用或大量使用时,会导致依赖关系不明确,不易维护。在vue3中更推荐我们使用自定义hooks的方式来复用代码。

实现一个自定义hooks

我们可以把一个功能相关的数据和逻辑都抽离出来放到一起维护,例如实现一个累加器

import {ref, onUnmounted} from 'vue'
export function useAccumulator(){
    const count = ref(0)
    let timer = null
    timer = setInterval(()=>{
        count.value ++
    },1000)
    onUnmounted(()=>{
        clearInterval(timer)
    })
    return {count}
}

我们定义了一个累加器的hooks函数,在组件入口就可以像普通函数一样使用useAccumulator()

import {useAccumulator} from '../utils'

let {count} = useAccumulator()

script setup 补充

贴上官网的描述

<script setup> 是在单文件组件 (SFC) 中使用组合式 API的编译时语法糖。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 Typescript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。

<script setup> 为我们带来了极大的便利,优势如下:

  • 所有顶层绑定变量内容都能在模板中直接使用
  • <script setup> 范围里的值也能被直接作为自定义组件的标签名使用,相当于我们可以不用通过components 注册组件
  • 支持使用:is来绑定动态组件
  • 支持顶层await,先比JavaScript一步实现这个特性,就是爽

强大的style特性

强大的style特性是vue3的又一个神兵利器

深度选择器

为了使组件之间样式互不影响,我们可以这样写<style scoped>,当处于scoped下想做深度选择时,可以使用:deep()伪类

<style scoped>
.a :deep(.b) {
  ...
}
</style>

插槽选择器

默认情况下,作用域样式不会影响到<slot/>内容,可以使用:slotted伪类来选择到插槽

:slotted(div) {
  color: red;
}

全局选择器

可通过:global伪类来实现全局样式

:global(.red) {
  color: red;
}

css module的支持

可通过<style module>方式将css类作为$style对象暴露出来给组件使用,同时也支持自定义名称:<style module=“molly” >

<template>
  <p :class="molly.red">red</p>
</template>

<style module="molly">
.red {
  color: red;
}
</style>

状态驱动的动态css

这是我认为最方便的一个特性,可以让我们少写很多代码,非常爽

style中允许我们使用v-bind来将css值动态关联到组件上

<template>
  <div class="text">hello</div>
</template>

<script>
export default {
  data() {
    return {
      color: 'red'
    }
  }
}
</script>

<style>
// v-bind可以直接引入script中的响应数据作为值
.text {
  color: v-bind(color);
}
</style>

好啦,总结至此应该可以轻松上手vue3项目了,喜欢的小伙伴欢迎点赞留言讨论。点赞超过30我将持续更新差异化对比vue-router4.x 和vuex 4.x 系列

撒花✿✿ヽ(°▽°)ノ✿

点击关注我让我们换个姿势玩儿前端,愿你一路前行,眼里有光!

撒花✿✿ヽ(°▽°)ノ✿


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK