Vue 3 有什麼新功能?

已發表: 2022-03-10
快速總結 ↬ Vue 3 帶來了許多有趣的新特性,並對一些現有特性進行了更改,旨在使框架的開發更加容易和可維護。 在本文中,我們將了解其中的一些新功能以及如何開始使用它們。 我們還將研究對現有功能所做的一些更改。

隨著 Vue 3 的發布,開發人員必須從 Vue 2 進行升級,因為它帶有一些新功能,這些新功能非常有助於構建易於閱讀和可維護的組件,並改進了在 Vue 中構建應用程序的方法。 我們將在本文中介紹其中的一些功能。

在本教程結束時,讀者將;

  1. 了解provide / inject以及如何使用它。
  2. 對 Teleport 及其使用方法有基本的了解。
  3. 了解 Fragments 以及如何使用它們。
  4. 了解對 Global Vue API 所做的更改。
  5. 了解對事件 API 所做的更改。

本文面向對 Vue 2.x 有一定了解的人。 您可以在 GitHub 中找到此示例中使用的所有代碼。

provide / inject

在 Vue 2.x 中,我們使用props可以輕鬆地將數據(字符串、數組、對像等)從父組件直接傳遞到其子組件。 但是在開發過程中,我們經常會發現需要將數據從父組件傳遞到深度嵌套的組件的情況,而使用props則更難做到這一點。 這導致了 Vuex Store、Event Hub 的使用,有時還通過深度嵌套的組件傳遞數據。 讓我們看一個簡單的應用程序;

需要注意的是,Vue 2.2.0 還附帶了不建議在通用應用程序代碼中使用的provide / inject

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { color: "", colors: ["red", "blue", "green"], }; }, }; </script>
 # childComponent.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <color-selector :color="color"></color-selector> </div> </template> <script> import colorSelector from "@/components/colorComponent.vue"; export default { name: "HelloWorld", components: { colorSelector, }, props: { msg: String, color: String, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { props: { color: String, }, }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

在這裡,我們有一個登錄頁面,其中包含一個包含顏色列表的下拉列表,並且我們將選定的color作為道具傳遞給childComponent.vue 。 這個子組件還有一個msg屬性,它接受要在模板部分顯示的文本。 最後,該組件有一個子組件( colorComponent.vue ),它接受來自父組件的color屬性,用於確定該組件中文本的類。 這是通過所有組件傳遞數據的示例。

但是在 Vue 3 中,我們可以使用新的提供和注入對以更簡潔、更簡潔的方式做到這一點。 顧名思義,我們使用provide作為函數或對象,以使數據從父組件可用於其任何嵌套組件,而不管此類組件的嵌套有多深。 我們在傳遞硬編碼值時使用對象形式來provide這樣的;

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { colors: ["red", "blue", "green"], }; }, provide: { color: 'blue' } }; </script>

但是對於需要傳遞組件實例屬性來provide的實例,我們使用函數模式,所以這是可能的;

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "blue", colors: ["red", "blue", "green"], }; }, provide() { return { color: this.selectedColor, }; }, }; </script>

由於我們在childComponent.vuecolorComponent.vue中都不需要color屬性,所以我們要去掉它。 使用provide的好處是父組件不需要知道哪個組件需要它提供的屬性。

為了在這種情況下需要它的組件中使用它,我們在colorComponent.vue中這樣做;

 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

在這裡,我們使用inject接收組件所需的變量數組。 在這種情況下,我們只需要color屬性,所以我們只傳遞它。 之後,我們可以像使用道具時一樣使用color

我們可能會注意到,如果我們嘗試使用下拉菜單選擇新顏色,顏色不會在colorComponent.vue中更新,這是因為默認情況下, provide中的屬性不是響應式的。 為了解決這個問題,我們使用了computed方法。

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; import { computed } from "vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "", todos: ["Feed a cat", "Buy tickets"], colors: ["red", "blue", "green"], }; }, provide() { return { color: computed(() => this.selectedColor), }; }, }; </script>

在這裡,我們導入computed並傳遞我們的selectedColor ,以便它可以在用戶選擇不同的顏色時進行響應和更新。 當您將變量傳遞給計算方法時,它會返回一個具有value的對象。 這個屬性保存你的變量的,所以對於這個例子,我們必須更新colorComponent.vue看起來像這樣;

 # colorComponent.vue <template> <p :class="[color.value]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

在這裡,我們將color更改為color.value以表示使用computed方法使color反應後的變化。 此時,每當父組件中的selectedColor發生變化時,該組件中文本的class總是會發生變化。

跳躍後更多! 繼續往下看↓

傳送

在某些情況下,由於應用程序使用的邏輯,我們創建組件並將它們放置在應用程序的一部分中,但旨在顯示在應用程序的另一部分中。 一個常見的例子是用於顯示和覆蓋整個屏幕的模式或彈出窗口。 雖然我們可以在此類元素上使用 CSS 的position屬性來解決此問題,但使用 Vue 3,我們也可以使用 Teleport。

Teleport 允許我們將組件從其在文檔中的原始位置中取出,從默認的#app容器中包裝 Vue 應用程序並將其移動到正在使用的頁面上的任何現有元素。 一個很好的例子是使用 Teleport 將 header 組件從#app div 內部移動到header重要的是要注意,您只能 Teleport 到存在於 Vue DOM 之外的元素。

傳送到無效元素時控制台中的錯誤消息
控制台中的傳送錯誤消息:終端中的傳送目標錯誤消息無效。 (大預覽)

Teleport 組件接受兩個決定該組件行為的道具,它們是;

  1. to
    該道具接受類名、id、元素或 data-* 屬性。 我們還可以通過傳遞一個:to屬性來使這個值動態化,而不是動態to改變 Teleport 元素。
  2. :disabled
    此道具接受Boolean ,可用於切換元素或組件上的傳送功能。 這對於動態更改元素的位置很有用。

使用 Teleport 的理想示例如下所示;

 # index.html** <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title> <%= htmlWebpackPlugin.options.title %> </title> </head> <!-- add container to teleport to --> <header class="header"></header> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div></div> <!-- built files will be auto injected --> </body> </html>

在您的 Vue 應用程序的默認index.html文件中,我們添加了一個header元素,因為我們希望將我們的 header 組件傳送到我們的應用程序中的那個點。 我們還為這個元素添加了一個類,用於樣式化和在我們的 Teleport 組件中輕鬆引用。

 # Header.vue** <template> <teleport to="header"> <h1 class="logo">Vue 3 </h1> <nav> <router-link to="/">Home</router-link> </nav> </teleport> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; align-items: center; justify-content: center; } .logo { margin-right: 20px; } </style>

在這裡,我們創建標題組件並添加一個帶有指向我們應用程序主頁鏈接的徽標。 我們還添加了 Teleport 組件並為to屬性賦予header值,因為我們希望該組件在該元素內呈現。 最後,我們將這個組件導入到我們的應用程序中;

 # App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>

在這個文件中,我們導入 header 組件並將其放置在模板中,以便它可以在我們的應用程序中可見。

現在,如果我們檢查應用程序的元素,我們會注意到我們的 header 組件位於header元素內;

DevTools 中的標頭組件
DevTools 中的 Header 組件(大預覽)

碎片

使用 Vue 2.x,文件template中不可能有多個根元素,作為一種解決方法,開發人員開始將所有元素包裝在一個父元素中。 雖然這看起來不是一個嚴重的問題,但在某些情況下,開發人員希望在沒有容器包裹這些元素的情況下渲染組件,但不得不這樣做。

在 Vue 3 中,引入了一個名為 Fragments 的新功能,該功能允許開發人員在其根模板文件中擁有多個元素。 所以在 Vue 2.x 中,輸入字段容器組件是這樣的;

 # inputComponent.vue <template> <div> <label :for="label">label</label> <input :type="type" : :name="label" /> </div> </template> <script> export default { name: "inputField", props: { label: { type: String, required: true, }, type: { type: String, required: true, }, }, }; </script> <style></style>

在這裡,我們有一個簡單的表單元素組件,它接受兩個 props, labeltype ,並且這個組件的模板部分被包裝在一個 div 中。 這不一定是問題,但如果您希望標籤和輸入字段直接位於form元素內。 使用 Vue 3,開發人員可以輕鬆地將此組件重寫為如下所示;

 # inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>

對於單個根節點,屬性始終歸屬於根節點,它們也稱為非道具屬性 它們是傳遞給組件的事件或屬性,在propsemits中沒有定義相應的屬性。 此類屬性的示例是classid 。 但是,需要明確定義多根節點組件中的哪些元素應歸屬於。

這就是使用上面的inputComponent.vue的含義;

  1. 在父組件中向該組件添加class時,必須指定class將歸屬於哪個組件,否則該屬性無效。
 <template> <div class="home"> <div> <input-component class="awesome__class" label="name" type="text" ></input-component> </div> </div> </template> <style> .awesome__class { border: 1px solid red; } </style>

當您在未定義屬性應歸屬於何處的情況下執行此類操作時,您會在控制台中收到此警告;

未分發屬性時終端中的錯誤消息
未分發屬性時終端中的錯誤消息(大預覽)

並且border對組件沒有影響;

沒有屬性分佈的組件
無屬性分佈的組件(大預覽)
  1. 要解決此問題,請在您希望將此類屬性分發到的元素上添加一個v-bind="$attrs"
 <template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>

在這裡,我們告訴 Vue 我們希望將屬性分配給label元素,這意味著我們希望將awesome__class應用於它。 現在,如果我們在瀏覽器中檢查我們的元素,我們會看到該類現在已添加到label中,因此標籤周圍現在有一個邊框。

具有屬性分佈的組件
具有屬性分佈的組件(大預覽)

全局 API

在 Vue 應用程序的main.js文件中看到Vue.componentVue.use並不少見。 這些類型的方法被稱為全局 API,在 Vue 2.x 中有相當多的方法。 這種方法的挑戰之一是它不可能將某些功能隔離到您的應用程序的一個實例(如果您的應用程序中有多個實例)而不影響其他應用程序,因為它們都安裝在 Vue 上。 這就是我的意思;

 Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })

對於上面的代碼,不可能說 Vue 指令與app1相關聯,而 Mixin 與app2相關聯,但它們在兩個應用程序中都可用。

Vue 3 附帶了一個新的 Global API,試圖通過引入createApp來解決此類問題。 此方法返回 Vue 應用程序的新實例。 應用程序實例公開了當前全局 API 的子集。 有了這個,所有從Vue 2.x 改變 Vu​​e 的 API(組件、mixin、指令、使用等)現在都將被移動到單獨的應用程序實例中,現在,你的 Vue 應用程序的每個實例都可以具有獨特的功能它們不會影響其他現有應用程序。

現在,上面的代碼可以重寫為;

 const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })

但是,可以創建您希望在所有應用程序之間共享的功能,這可以通過使用工廠功能來完成。

事件 API

除了使用 Vuex 存儲之外,開發人員在沒有父子關係的組件之間傳遞數據的最常見方法之一是使用事件總線。 這種方法很常見的原因之一是因為它很容易上手。

 # eventBus.js const eventBus = new Vue() export default eventBus;

在此之後,接下來就是將此文件導入main.js以使其在我們的應用程序中全局可用,或者將其導入您需要的文件中;

 # main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus

現在,您可以像這樣發出事件並監聽發出的事件;

 this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')

有很多 Vue 代碼庫都充滿了這樣的代碼。 但是,使用 Vue 3,這是不可能的,因為$on$off$once都已被刪除,但$emit仍然可用,因為子組件需要向其父組件發出事件。 另一種方法是使用provide / inject或任何推薦的第三方庫。

結論

在本文中,我們介紹瞭如何使用provide / inject對將數據從父組件向下傳遞到深度嵌套的子組件。 我們還研究瞭如何將組件從應用程序中的一個點重新定位和傳輸到另一個點。 我們研究的另一件事是多根節點組件以及如何確保我們分配屬性以便它們正常工作。 最後,我們還介紹了對事件 API 和全局 API 的更改。

更多資源

  • “使用 ES6+ 的 JavaScript 工廠函數”,Eric Elliott,Medium
  • “使用事件總線在 Vue 組件之間共享道具”,Kingsley Silas,CSS-Tricks
  • 在同一目標上使用多個 Teleports,Vue.js 文檔
  • 非道具屬性,Vue.js 文檔
  • 使用反應性,Vue.js 文檔
  • teleport , Vue.js 文檔
  • 片段,Vue.js 文檔
  • 2.x 語法,Vue.js 文檔