Vue3.0 新特性以及使用經驗總結


前言
Vue3.0 在去年 9 月正式發布了,看大家都有在熱情的擁抱 Vue3.0。今年初新項目也開始使用 Vue3.0 來開發,這篇文章就是在使用后的一個總結, 包含 Vue3 新特性的使用以及一些使用經驗分享。

為什么要升級 Vue3
使用 Vue2.x 的小伙伴都熟悉,Vue2.x 中所有數據都是定義在data中,方法定義在methods中的,並且使用this來調用對應的數據和方法。那 Vue3.x 中就可以不這么玩了, 具體怎么玩我們后續再說, 先說一下 Vue2.x 版本這么寫有什么缺陷,所有才會進行升級變更的。
回顧 Vue2.x 實現加減

<template>
  <div class="homePage">
    <p>count: {{ count }}</p>   
    <p>倍數: {{ multiple }}</p>        
    <div>
      <button style="margin-right: 10px" @click="increase">加1</button>
      <button @click="decrease">減一</button>    
    </div>      
  </div>
</template>
<script>
export default {
  data() {
    return { count: 0 };
  },
  computed: {
    multiple() {
      return 2 * this.count;
    },
  },
  methods: {
    increase() {
      this.count++;
    },
    decrease() {
      this.count--;
    },
  },
};
</script>

上面代碼只是實現了對count的加減以及顯示倍數, 就需要分別在 data、methods、computed 中進行操作,當我們增加一個需求,就會出現下圖的情況:

當我們業務復雜了就會大量出現上面的情況, 隨着復雜度上升,就會出現這樣一張圖, 每個顏色的方塊表示一個功能:

甚至一個功能還有會依賴其他功能,全攪合在一起。
當這個組件的代碼超過幾百行時,這時增加或者修改某個需求, 就要在 data、methods、computed 以及 mounted 中反復的跳轉,這其中的的痛苦寫過的都知道。
那我們就想啊, 如果可以按照邏輯進行分割,將上面這張圖變成下邊這張圖,是不是就清晰很多了呢, 這樣的代碼可讀性和可維護性都更高:


那么 vue2.x 版本給出的解決方案就是 Mixin, 但是使用 Mixin 也會遇到讓人苦惱的問題:

命名沖突問題
不清楚暴露出來的變量的作用
邏輯重用到其他 component 經常遇到問題

關於上面經常出現的問題我就不一一舉例了,使用過的小伙伴多多少少都會遇到。文章的重點不是 Mixin, 如果確實想知道的就留言啦~
所以,我們 Vue3.x 就推出了Composition API主要就是為了解決上面的問題,將零散分布的邏輯組合在一起來維護,並且還可以將單獨的功能邏輯拆分成單獨的文件。接下來我們就重點認識Composition API。

Composition API

setup
setup 是 Vue3.x 新增的一個選項, 他是組件內使用 Composition API的入口。
setup 執行時機
我在學習過程中看到很多文章都說 setup 是在 beforeCreate和created之間, 這個結論是錯誤的。實踐是檢驗真理的唯一標准, 於是自己去檢驗了一下:

export default defineComponent({
  beforeCreate() {
    console.log("----beforeCreate----");
  },
  created() {
    console.log("----created----");
  },
  setup() {
    console.log("----setup----");
  },
});


setup 執行時機是在 beforeCreate 之前執行,詳細的可以看后面生命周期講解。
setup 參數
使用setup時,它接受兩個參數:

props: 組件傳入的屬性
context

setup 中接受的props是響應式的, 當傳入新的 props 時,會及時被更新。由於是響應式的, 所以不可以使用 ES6 解構,解構會消除它的響應式。
錯誤代碼示例, 這段代碼會讓 props 不再支持響應式:

// demo.vue
export default defineComponent ({
    setup(props, context) {
        const { name } = props
        console.log(name)
    },
})

那在開發中我們想要使用解構,還能保持props的響應式,有沒有辦法解決呢?大家可以思考一下,在后面toRefs學習的地方為大家解答。
接下來我們來說一下setup接受的第二個參數context,我們前面說了setup中不能訪問 Vue2 中最常用的this對象,所以context中就提供了this中最常用的三個屬性:attrs、slot 和emit,分別對應 Vue2.x 中的 $attr屬性、slot插槽 和$emit發射事件。並且這幾個屬性都是自動同步最新的值,所以我們每次使用拿到的都是最新值。
reactive、ref 與 toRefs
在 vue2.x 中, 定義數據都是在data中, 但是 Vue3.x 可以使用reactive和ref來進行數據定義。
那么ref和reactive他們有什么區別呢?分別什么時候使用呢?說到這里,我又不得不提一下,看到很多網上不少文章說 (reactive用於處理對象的雙向綁定,ref則處理 js 基礎類型的雙向綁定)。我其實不太贊同這樣的說法,這樣很容易初學者認為ref就能處理 js 基本類型, 比如ref也是可以定義對象的雙向綁定的啊, 上段代碼:

 setup() {
    const obj = ref({count:1, name:"張三"})
    setTimeout(() =>{
        obj.value.count = obj.value.count + 1
        obj.value.name = "李四"
    }, 1000)
    return{
        obj
    }
  }

我們將obj.count和obj.name綁定到頁面上也是可以的;但是reactive函數確實可以代理一個對象, 但是不能代理基本類型,例如字符串、數字、boolean 等。 接下來使用代碼展示一下ref、reactive的使用:


,我們綁定到頁面是通過user.name,user.age;這樣寫感覺很繁瑣,我們能不能直接將user中的屬性解構出來使用呢? 答案是不能直接對user進行結構, 這樣會消除它的響應式, 這里就和上面我們說props不能使用 ES6 直接解構就呼應上了。那我們就想使用解構后的數據怎么辦,解決辦法就是使用toRefs。
toRefs 用於將一個 reactive 對象轉化為屬性全部為 ref 對象的普通對象。具體使用方式如下

<template>
  <div class="homePage">
    <p>第 {{ year }} 年</p>
    <p>姓名: {{ nickname }}</p>
    <p>年齡: {{ age }}</p>
  </div>
</template>

<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
  setup() {
    const year = ref(0);
    const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
    setInterval(() => {
      year.value++;
      user.age++;
    }, 1000);
    return {
      year,
      // 使用reRefs
      ...toRefs(user),
    };
  },
});
</script>

生命周期鈎子
我們可以直接看生命周期圖來認識都有哪些生命周期鈎子 (圖片是根據官網翻譯后繪制的):

從圖中我們可以看到 Vue3.0 新增了setup,這個在前面我們也詳細說了, 然后是將 Vue2.x 中的beforeDestroy名稱變更成beforeUnmount; destroyed 表更為 unmounted,作者說這么變更純粹是為了更加語義化,因為一個組件是一個mount和unmount的過程。其他 Vue2 中的生命周期仍然保留。
上邊生命周期圖中並沒包含全部的生命周期鈎子, 還有其他的幾個, 全部生命周期鈎子如圖所示:


我們可以看到beforeCreate和created被setup替換了(但是 Vue3 中你仍然可以使用, 因為 Vue3 是向下兼容的, 也就是你實際使用的是 vue2 的)。其次,鈎子命名都增加了on; Vue3.x 還新增用於調試的鈎子函數onRenderTriggered和onRenderTricked
下面我們簡單使用幾個鈎子, 方便大家學習如何使用,Vue3.x 中的鈎子是需要從 vue 中導入的:

import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated,
onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked,
onRenderTriggered } from "vue"; export default defineComponent({ //
beforeCreate和created是vue2的 beforeCreate() {
console.log("------beforeCreate-----"); }, created() {
console.log("------created-----"); }, setup() { console.log("------setup-----");
// vue3.x生命周期寫在setup中 onBeforeMount(() => {
console.log("------onBeforeMount-----"); }); onMounted(() => {
console.log("------onMounted-----"); }); // 調試哪些數據發生了變化
onRenderTriggered((event) =>{ console.log("------onRenderTriggered-----",event);
}) }, });

關於生命周期相關的內容就介紹到這里。

簡單對比 vue2.x 與 vue3.x 響應式
其實在 Vue3.x 還沒有發布 bate 的時候, 很火的一個話題就是Vue3.x 將使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty。
沒有無緣無故的愛,也沒有無緣無故的恨。為何要將Object.defineProperty換掉呢,咋們可以簡單聊一下。
我剛上手 Vue2.x 的時候就經常遇到一個問題,數據更新了啊,為何頁面不更新呢?什么時候用$set更新,什么時候用$forceUpdate強制更新,你是否也一度陷入困境。后來的學習過程中開始接觸源碼,才知道一切的根源都是 Object.defineProperty。
對這塊想要深入了解的小伙伴可以看這篇文章 為什么 Vue3.0 不再使用 defineProperty 實現數據監聽?要詳細解釋又是一篇文章,這里就簡單對比一下Object.defineProperty 與 Proxy

Object.defineProperty只能劫持對象的屬性, 而 Proxy 是直接代理對象

由於Object.defineProperty只能劫持對象屬性,需要遍歷對象的每一個屬性,如果屬性值也是對象,就需要遞歸進行深度遍歷。但是 Proxy 直接代理對象, 不需要遍歷操作

Object.defineProperty對新增屬性需要手動進行Observe

因為Object.defineProperty劫持的是對象的屬性,所以新增屬性時,需要重新遍歷對象, 對其新增屬性再次使用Object.defineProperty進行劫持。也就是 Vue2.x 中給數組和對象新增屬性時,需要使用$set才能保證新增的屬性也是響應式的, $set內部也是通過調用Object.defineProperty去處理的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM