【建議收藏】通過差異化對比學習法,帶你回顧Vue2快速掌握Vue3

前端有貓膩發表於2022-04-08

海闊憑魚躍,天高任鳥飛。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 系列

感謝

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

點選關注我讓我們換個姿勢玩兒前端,願你一路前行,眼裡有光!

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

相關文章