Released 2023-02-21

Modified 2023-02-21

Tech

4193Words (21min read)

[Day 11] Vue3.x 相對於 Vue2.x 的新特性和改進 - 嘗試 30 日寫文充版挑戰

Title Image

前言 - Prologue

嘗試 30 天寫文充實版面 (跳過特休假跟假日 ლ(́◕◞౪◟◕‵ლ) ) 不間斷更新文章的 Day 11

Vue 是一個簡潔而高效的 JavaScript 前端框架,Vue3.x 相對於 Vue2.x 帶來了多個重要改進和新特性。

Vue3 的核心在於更高效的渲染和更快的速度,而這是通過重新設計核心架構和創新功能來實現的。 這些改進使得 Vue3.x 更適用於大型 Web 應用程式,同時引入了多個新功能,例如: Composition API更好的 TypeScript 支援。這些功能讓開發者能夠更輕鬆地建構可重用且易於維護和擴展的應用程式,並能更快地構建更完整、更複雜的 Web 應用程式。

在本文中,將深入探討 Vue 3.x 相對於 Vue 2.x 的新特性和改進,並探討這些功能如何改進了 Vue 框架的開發體驗。

Why rewrite? - EVAN YOU

Two key considerations led us to the new major version (and rewrite) of Vue: First, the general availability of new JavaScript language features in mainstream browsers. Second, design and architectural issues in the current codebase that had been exposed over time.

兩個關鍵考量點促使我們推出了 Vue 新的主要版本 (和重寫)
  • 新的 JavaScript 語言特性在主流瀏覽器中的普遍支援性。
  • 當前程式碼庫中的設計和體系架構問題隨著時間的推移而暴露出來。

Vue2 與 Vue3 的不同之處 - Key differences between Vue2 and Vue3

  • 更快的渲染速度:

Vue3 在重新設計核心架構上下了很大的功夫,使用了更先進的渲染技術,可以實現更快的渲染速度。

  • 更好的 TypeScript 支援:

Vue3 強化了對 TypeScript 的支援,包括更好的類型推斷和更嚴格的類型檢查。

  • Composition API:

Vue3 引入了全新的 Composition API,提供了更好的代碼組織方式,可以更輕鬆地重用邏輯代碼和狀態邏輯,使得代碼更加清晰易懂。

  • 更好的 Tree Shaking 支援:

Vue3 在設計上更為精簡,使得 Tree Shaking 技術更加容易實現,可以減少打包出來的代碼體積。

  • 更好的適應性支援:

Vue3 提供了更好的支援適應性設計的 API,可以更輕鬆地開發適配不同設備的 Web 應用程式。

生命週期函數的變化 - Lifecycle Hooks

Vue2.xVue3.x
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedonActivated
deactivatedonDeactivated

vm.$destroy() 已從 Vue3.0 移除

renderTracked、renderTriggered:This hook is development-mode-only and not called during server-side rendering.

activated、deactivated:This hook is not called during server-side rendering.

示例 - mounted & onMounted

Vue2 可直接從元件選項訪問生命週期鉤子。

# Vue2

<script>
export default {
  props: {
    title: String,
  },
  // ...

  mounted() {
    console.log("component mounted");
  },

  // ...
};
</script>

Vue3 Composition API 幾乎所有內容都在 setup() 方法中,包括已安裝的生命週期掛鉤。

但在默認情況下不包含生命週期掛鉤,因此必須 onMounted 在 Vue3 中調用它時導入該方法。與下方導入響應式相同。

# Vue3

<script>
import { reactive, onMounted } from "vue";

export default {
  props: {
    title: String,
  },
  setup() {
    // ..

    onMounted(() => {
      console.log("component mounted");
    });

    // ...
  },
};
</script>

設置數據 - Setting Up Data

  • Options API 與 Vue3.x Composition API
  • 定義資料變數和方法
  • 示例
Vue2.x Options API 與 Vue3.x Composition API
  • Vue2 使用 Options API
  1. Options API 在程式分割成了不同的屬性:data, computed, methods 等。
  2. Options API 對於方法有一個單獨的部分。在其中可以定義所有的方法並以想要的任何方式組織它們。
  • Vue3 使用 Composition API
  1. Composition API 允許按功能而不是屬性類型對代碼進行分組,相比舊的 api 使用屬性分組,程式會更加簡便和整潔。
  2. Composition API 中的 setup() 也處理方法。它的工作方式有點類似於聲明數據—— 必須先聲明方法然後返回它,以便元件的其他部分可以訪問它。
定義資料變數和方法

Vue2 將資料放入 data 中,定義資料變數是 data(){},創建方法要在 methods:{} 中。

Vue3 使用 setup() 方法在元件初始化構造的時候觸發。 為了讓開發人員更好地控制什麼是響應性的,可以直接訪問 Vue 的 reactivity API (響應性 API)。

創建響應式數據涉及三個步驟:

  • vue 導入 reactive
  • 使用 reactive() 來聲明資料為響應性資料
  • 使用 setup() 來返回響應性資料,從 template 可獲取這些響應性資料
兩者兼容性

當使用 Object.defineProperty() 定義一個屬性時,該屬性只能在 ES5 及以上版本的 JavaScript 中使用。然而,大多數現代瀏覽器都支援 ES5,因此在大多數瀏覽器中可以使用 Object.defineProperty()。

當使用 Proxy API 時,該 API 只能在 ES6 及以上版本的 JavaScript 中使用。這意味著某些舊瀏覽器不支援 Proxy API,例如:IE11 及以下版本的瀏覽器不支援 (但 IE 沒了,可喜可賀)。為了向下兼容,可以使用 polyfill 或垫片庫,這些庫提供 Proxy API 的類似實現,讓較舊的瀏覽器也能夠使用 Proxy API。

在向上兼容方面,使用 Object.defineProperty() 定義的屬性可以在 ES5 及以上版本的瀏覽器中正常使用,也可以在支援 ES6 及以上版本的瀏覽器中使用;而使用 Proxy API 定義的屬性只能在 ES6 及以上版本的瀏覽器中使用。

總體而言,Object.defineProperty()Proxy API 兩者並不完全相容,因為它們要求的最低 JavaScript 版本不同。然而,這兩種方法都可以用於實現資料雙向繫結,只是使用 Proxy API 通常更簡單和方便。

<template>

沒有特殊指令的標記(v-if/else-if/else、v-forv-slot)的 <template> 現在被視為普通元素,並將生成原生的 <template> 元素,而不是渲染其內部內容。

注:markdown 不可直接打 < + template + >,須加上 "`",否則會默認成本體程式一部分而結束。

父子傳參差異
  • Vue2.x 和 Vue3.x 皆為父傳子使用 props,而子傳父事件是使用 Emitting Events
  • Vue2.x 調用 this.$emit 傳入事件和對象。
  • Vue3.x 中 setup() 第二個參數 content 對象就有 emit,只要在 setup() 接受第二個參數中使用分解對象法取出 emit 就可以在 setup() 中任意取用了。
指令與插槽不同
  • Vue2.x 使用 slot 時可直接使用 slot
  • Vue2.x 中 v-forv-if 優先權高的是 v-for 指令,不建議一起使用,可能衝突。
  • Vue3.x 必須使用 v-slot
  • Vue3.x 中 v-forv-if 只會把當前 v-if 當作 v-for 中的判斷語句,不會衝突。
  • Vue3.x 移除 keyCode 做為 v-on 修飾符,也不支持 config.keyCode
  • Vue3.x 中移除 v-on.native 修飾符、過濾器 filter
示例 - 資料變數和方法

假設目前有兩個資料變數:

  • username
  • password
# Vue2

<script>
export default {
  props: {
    title: String,
  },
  data() {
    return {
      username: "",
      password: "",
    };
  },
  mounted() {
    console.log('title: ' + this.title)
  },
  computed: {
    lowerCaseUsername() {
      return this.username.toLowerCase()
    },
  },
  methods: {
    login() {
      this.$emit('login', {
        username: this.username,
        password: this.password,
      })
    },
  },
};
</script>

// access them like title, username and password
# Vue3

<script>
import { reactive, onMounted, computed } from 'vue';

export default {
  props: {
    title: String,
  },
  setup(props, { emit }) {
    const state = reactive({
      username: "",
      password: "",
      lowerCaseUsername: computed(() => state.username.toLowerCase()),
    });

    onMounted(() => {
      console.log('title: ' + props.title);
    });

    const login = () => {
      emit('login', {
        username: state.username,
        password: state.password,
      });
    };

    return {
      state,
      login,
    };
  },
};
</script>

// access them like state.title, state.username and state.password

資料雙向繫結原理不同

Vue3.x 使用 proxy 代替 Vue2.x defineProperty

Options API 中,即使是 data 裡定義的變數只是單純做為 setInterval 等功能的變數,Vue2.x 仍然會將它們做成雙向綁定,而在 Composition API 中,能夠更好地區分哪些變數應該被 Vue3.x 用於雙向綁定,哪些變數只需作為單純變數使用。

Vue2.x 的資料雙向繫結原理
  • 利用 ES5 的 API Object.defineProperty() 在對象上定義一個屬性,該屬性的 getset 函數被觸發時,會通過觀察者模式(Observer Pattern)將資料變化通知給 Vue 的更新機制。
  • 對於 v-model 或表單元素等,使用的是 input 事件和 value 屬性繫結,當表單元素的值發生變化時,會通過 input 事件觸發 Vue 的更新機制。
  • Object.defineProperty() 有一些限制,例如無法監聽屬性的添加和刪除,對於嵌套對象的監聽也不方便。

最大缺點:為什麼 Vue2.x 無法深度監聽陣列物件?

因為每次元件渲染時,Vue2.x 是透過 defineProperty 雙向繫結 data 中的資料。如果一個屬性沒有在一開始定義時就加入,它就不會被綁定,也不會觸發更新和重新渲染。

Vue3.x 的資料雙向繫結原理
  • 使用 ES6 的 Proxy API 將對象包裝在代理對象中,代理對象可以偵測到對目標對象的修改,從而實現資料變化的監聽和通知更新。
  • 每當資料屬性的值發生變化時,Vue3.x 會通過 Proxy API 自動更新相關的 DOM 元素。
  • 對於 v-model 或表單元素等,使用的是 modelValueupdate:modelValue 兩個 props 進行繫結,當表單元素的值發生變化時,會觸發 update:modelValue 事件並將變化的值作為參數傳入,從而觸發 Vue 的更新機制。
  • Proxy API 具有更大的彈性和更好的效能,例如可以監聽屬性的添加和刪除,對於嵌套對象的監聽也更加方便。但是需要注意的是,由於 Proxy API 是 ES6 的新特性,因此需要在較新的瀏覽器或 Node.js 環境中使用。
ES6 Proxy API 的優勢

Vue3.x 使用 ES6 的 Proxy API 來對資料進行代理,相較於 defineProperty 的方式,使用 proxy 有以下優勢:

  • 可以監聽整個對象,而不是只能監聽特定屬性
  • 省去了使用 for inObject.keys 等方式遍歷對象的步驟,提高了效率
  • 可以監聽陣列內部資料的變化,不需要對陣列進行特殊處理,使得 Vue3.x 在處理陣列時更加靈活。
示例

比較 Vue2.x 和 Vue3.x 在使用 Object.defineProperty() 和 Proxy API 時的差異:

# Vue2.x:使用 Object.defineProperty()

const obj = {
  name: 'Herry',
  age: 22,
};

// 通過 Object.defineProperty() 監聽對象屬性的變化
Object.keys(obj).forEach(key => {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    get() {
      console.log(`讀取 ${key} 屬性值: ${value}`);
      return value;
    },
    set(newValue) {
      console.log(`設置 ${key} 屬性值: ${newValue}`);
      value = newValue;
    },
  });
});

// 設置 name 屬性值: Bob
obj.name = 'Bob';

// 讀取 name 屬性值: Bob
console.log(obj.name);
# Vue3.x:使用 Proxy API

const obj = {
  name: 'Herry',
  age: 22,
};

// 通過 Proxy 監聽整個對象的變化
const handler = {
  get(target, key, receiver) {
    console.log(`讀取 ${key} 屬性值: ${target[key]}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`設置 ${key} 屬性值: ${value}`);
    return Reflect.set(target, key, value, receiver);
  };
};

const proxy = new Proxy(obj, handler);

// 設置 name 屬性值: Bob
proxy.name = 'Bob';

// 讀取 name 屬性值: Bob
console.log(proxy.name);

可以看到 Vue2.x 使用 Object.defineProperty() 監聽對象屬性的變化,但是需要逐個設置每個屬性,而且無法監聽整個對象的變化。而 Vue3.x 使用 Proxy API 可以監聽整個對象的變化,並且配置選項多,可以做更細緻的事情。

總結

總結而言,Vue3.x 的資料雙向繫結原理通過 Proxy 對象實現,相較於 Vue2.x 使用 Object.defineProperty() 方法實現的方式,更加高效和精確。

Diff 算法的改進及向下/向上兼容性 - Improvement of Diff Algorithm and Downward/Upward Compatibility

當 Virtual DOM 中的資料發生變化時,Vue2.x 和 Vue3.x 都會透過 Diff 演算法比較 Virtual DOM 與真實 DOM 的差異,然後只更新差異部分的真實 DOM。

Vue3.x 的 Diff 演算法相較於 Vue2.x 有以下改進
  • 使用了全新的 Diff 算法 —— Fragment Block Patch。
  • 選用更高效的 Diff 策略,將原本的 O(n2) 時間複雜度降至 O(n) 級別,更快速且節省資源。
  • 能夠更好地處理動態元件的新增、刪除和移動,降低 Virtual DOM 的重新渲染次數。
  • 可以更精確地比較差異,並且只更新必要的部分,避免不必要的重新渲染,提升性能。
  • 向下兼容 Vue2.x 的 Diff 算法,同時向上兼容 Fragment Block Patch 算法,以保證舊版本的應用能够平穩遷移至 Vue3.x。
Vue3.x 的向上兼容:
  • 部分 Vue2.x API 在 Vue3.x 中已不再支援,需要進行替換和調整。
  • Vue3.x 提供 Migration Build,可以在 Vue2.x 的應用程式中順利升級到 Vue3.x。
  • Vue3.x 提供 Composition API,可以更好地支援可重用邏輯和程式組合,進一步提高開發效率。
Vue3.x 的向下兼容:
  • Vue3.x 使用了 ES6 的語法,可能無法在一些較舊的瀏覽器上運行,需要進行降級處理。
  • 部分 Vue2.x 的插件和庫可能無法在 Vue3.x 中正常運行,需要進行更新和修改。
Vue3.x 的其他優化:
  • Vue3.x 靜態提升:靜態元件在編譯時就被編譯器轉換為常數,不需要在運行時再進行處理。
  • Vue3.x 按需編譯:只編譯需要使用的元件。
  • Vue3.x 更好的 Tree shaking:刪除沒有使用的代碼。
  • Vue3.x 更好的 Code splitting:代碼分割。

Fragments

  • Vue2 不支持,根節點只能有一個
  • Vue3 支持,可以設置多個根節點,但是必須通過 v-bind='$attrs' 聲明繼承的根節點

以下為取自官方文檔實例解釋:

在 Vue2.x 中,不支持多個根節點,只可以多個元件包裝在 <div> 中。

<!-- Layout.vue -->

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

在 Vue3.x 中,元件可以設置多個根節點,然而這需要開發者去明確定義應該在哪裡分配屬性。

<!-- Layout.vue -->

<template>
  <header>...</header>
  <main v-bind="$attrs">...</main>
  <footer>...</footer>
</template>

Teleport

Vue 3.x 的 Teleport 功能可以讓你將一個元件的內容「傳送」(teleport)到 DOM 樹中的任何位置,而不需要將該元件包裝在其他元件中。這樣可以讓你更方便地控制元件的位置和渲染效果,不需要為了實現這些效果而添加額外的 DOM 結構或使用複雜的 CSS。

Teleport 具有多種用途,例如將對話框顯示在頁面的中心位置,或將提示信息顯示在畫面的任何位置。它還可以與動畫和過渡效果一起使用,以實現更加流暢和吸引人的使用者體驗。

Teleport 的使用方法非常簡單,只需要在模板中使用 Teleport 元素包裝你想要傳送的元件,並指定目標位置的選擇器即可。在元件被渲染時,它的內容就會自動傳送到目標位置。

範例可以參考官方文件說明

參考資料 - Reference

[1] Introduction | Vue.js

https://vuejs.org/

[2] Vue2 to Vue3 — What’s changed?. This article gives a brief introduction… | by Thilanka Dilakshi | Embla Tech | Medium

https://medium.com/emblatech/vue2-to-vue3-whats-changed-5572514da20d

[3] Building the Same Component in Vue2 vs. Vue 3 - LearnVue | LearnVue

https://learnvue.co/tutorials/vue-2-vs-vue-3

後記 - Epilogue

以上部分為譯文,結合個人理解與查閱各論壇與官方文件等所產生,如果有錯誤的地方歡迎透過 Email 指出 (留言區還在待辦計劃)。

因為面試題目所以來惡補實體知識了QQ,雖然腦袋知道有什麼不同但就是禿然一片空白,希望下次可以流暢的答出來。

總結努力打好基礎學會並努力記起每一種語法,GO GO GO!

Others

setup() 函數特性
  • 接收兩個參數:props、context (包含 attrs、slots、emit)
  • 處於生命週期 beforeCreated 和 created 兩個鉤子函數之前
  • 執行時,元件實例尚未被創建 (在 setup() 內部,this 不會是該活躍實例得引用,即不指向 vue 實例,Vue 為了避免使用錯誤,將 setup 中的 this 修改為 undefined)
  • 與模板使用時,須返回對象
  • 因為 setup 函數中,props 是響應式的,當傳入新的 prop 時,將會被更新,所以不能使用 ES6 解構,因為會消除 prop 的響應性,如需解構 prop,可透過使用 setup 函數中的 toRefs 完成此操作
  • 在內使用響應式資料時,需透過 .value 獲取
  • 從 setup() 中返回的對象上的 property 返回並可以在模板中被訪問時,它將自動展開為內部值。不需要在模板中追加. value
  • 只能是同步,不可為異步

來源: vue2 和 vue3 区别 - 拓展阅读•Worktile社区

[Day 11] Vue3.x 相對於 Vue2.x 的新特性和改進 - 嘗試 30 日寫文充版挑戰

https:///en/blog/18

Author

Kama

Read more from Tech
lightbox-image
Toggle Button - Red Pandas Icons by svgrepo.com

Copyrights © 2023 Kama, All Rights Reserved.