Vue 3 中那些激動人心的新功能

  • 2019 年 11 月 7 日
  • 筆記

讓我們先從大多數人可能都聽說過的 API 開始……

合成 API

合成(Composition)API 是 Vue 的新大版本中討論最多的特色語法。這是一種全新的邏輯復用和程式碼組織方法。

目前,我們使用所謂 Options API 構建組件。要將邏輯添加到 Vue 組件中,我們要填充(option)諸如 data、methods、computed 之類的屬性。這種方法的最大缺點是它本身並非有效的 JavaScript 程式碼。你需要準確了解模板中可以訪問哪些屬性,以及 this 關鍵字的行為。在後台,Vue 編譯器需要將屬性轉換為可用的程式碼。因此我們無法享受自動提示或類型檢查功能的幫助。

合成 API 會將組件屬性中當前可用的機制暴露為 JavaScript 函數,從而解決這個問題。Vue 核心團隊將合成 API 定義為「一組基於函數的附加 API,可以靈活地組合組件邏輯」。用合成 API 編寫的程式碼更具可讀性,也沒有在幕後隱藏什麼魔法,這使它更易於閱讀和學習。

下面來看一個使用了新的合成 API 的組件示例,從中了解其工作機制。

<template>    <button @click="increment">      Count is: {{ count }}, double is {{ double }}, click to increment.    </button>  </template>    <script>  import { ref, computed, onMounted } from \'vue\'    export default {    setup() {      const count = ref(0)      const double = computed(() => count.value * 2)        function increment() {        count.value++      }        onMounted(() => console.log(\'component mounted!\'))        return {        count,        double,        increment      }    }  }  </script>

現在我們將這段程式碼分解為幾部分,看看到底發生了什麼事情:

import { ref, computed, onMounted } from \'vue\'

如前所述,合成 API 將組件屬性暴露為函數,因此第一步是導入所需的函數。在我們的示例中,我們需要用 ref 創建響應性引用、用 computed 創建計算屬性,並用 onMounted 訪問掛載的生命周期 hook。

現在你可能想知道神秘的 setup 方法是做什麼的?

export default {    setup() {

簡單說,它只是一個將屬性和函數返回到模板的函數,僅此而已。我們在這裡聲明所有響應屬性、計算屬性、觀察者和生命周期 hooks,然後返回它們,以便在模板中使用。

我們沒有從 setup 函數返回的內容將無法在模板中使用。

const count = ref(0)

根據上述內容,我們使用 ref 函數聲明了稱為 count 的響應屬性。它可以包裝任何原語或對象,並返回其響應性引用。傳遞的元素的值將保留在所創建引用的 value 屬性中。例如,如果要訪問 count 引用的值,則需要顯式請求 count.-value。

const double = computed(() => count.value * 2)    function increment() {    count.value++  }

這正是我們在聲明計算屬性 double 和 increment 函數時所做的事情。

onMounted(() => console.log(\'component mounted!\'))

我們使用 onMounted hook 在組件掛載時記錄了一些消息,這裡只是告訴你可以這樣做?

return {    count,    double,    increment  }

最後,我們使用 increment 方法返回 count 和 double 屬性,以使其在模板中可用。

<template>    <button @click="increment">      Count is: {{ count }}, double is {{ double }}. Click to increment.    </button>  </template>

好了!現在,我們可以訪問模板中 setup 方法返回的屬性和函數,就像通過舊的 Options API 聲明它們一樣。

這是一個簡單的示例,也可以通過 Options API 輕鬆實現。新的合成 API 的真正好處不僅僅是以不同的方式編碼,在復用我們的程式碼 / 邏輯時,其優勢就能顯現出來。

使用合成 API 復用程式碼

新的合成 API 有更多優勢。想想程式碼復用,這就是它的一大用武之地。目前,如果我們要在其他組件之間共享一些程式碼,則有兩個選擇可用——分別是 mixins 和作用域插槽。兩者都有自己的缺點。

假設我們要提取 counter 功能並在其他組件中復用。下面的程式碼中你可以看到如何分別使用現有的 API 和新的合成 API 做到這一點:

先從 mixins 開始:

import CounterMixin from \'./mixins/counter\'    export default {    mixins: [CounterMixin]  }

mixins 的最大缺點是我們對它實際上添加到組件中的內容一無所知。這不僅讓程式碼很難理解,而且還可能導致命名與已有的屬性和函數發生衝突。

下面是作用域插槽。

<template>    <Counter v-slot="{ count, increment }">       {{ count }}      <button @click="increment">Increment</button>    </Counter>  </template>

使用作用域插槽時,我們確切地知道可以通過 v-slot 屬性訪問哪些屬性,因此程式碼更容易理解。這種方法的缺點是我們只能在模板中訪問它,並且只能在 Counter 組件作用域中使用。

現在是時候使用合成 API 了:

function useCounter() {    const count = ref(0)    function increment () { count.value++ }      return {      count,      incrememt    }  }    export default {    setup () {      const { count, increment } = useCounter()      return {        count,        increment      }    }  }

是不是更優雅?我們不受模板和組件作用域的限制,並且確切地知道可以從 counter 訪問哪些屬性。此外,由於 useCounter 只是返回某些屬性的函數,因此我們可以獲得編輯器中程式碼自動完成的幫助。沒有隱藏在幕後的魔法,因此編輯器可以幫助我們檢查類型並給出建議。

使用第三方庫也能變得更優雅。例如,如果我們要使用 Vuex,則可以顯式使用 useStore 函數,而不用污染 Vue 原型(this.$store),這種方法也消除了 Vue 插件的幕後魔法。

const { commit, dispatch } = useStore()

如果你想了解有關合成 API 及其用例的更多資訊,我強烈建議你閱讀 Vue 團隊寫的這篇文檔。這份文檔解釋了新 API 背後的理念,並給出了最佳用例的建議。Vue 核心團隊的 ThorstenLünborg 還提供了一個很棒的存儲庫,其中包含合成 API 的使用示例。

全局掛載 / 配置 API 更改

在實例化和配置應用程式的方式方面,還有一項重大變化。現在我們是這樣做的:

import Vue from \'vue\'  import App from \'./App.vue\'    Vue.config.ignoredElements = [/^app-/]  Vue.use(/* ... */)  Vue.mixin(/* ... */)  Vue.component(/* ... */)  Vue.directive(/* ... */)    new Vue({    render: h => h(App)  }).$mount(\'#app\')

目前我們使用全局 Vue 對象來提供配置並創建新的 Vue 實例。對 Vue 對象所做的任何更改都會影響每個 Vue 實例和組件。

下面我們看看 Vue 3 中是怎麼做的:

import { createApp } from \'vue\'  import App from \'./App.vue\'    const app = createApp(App)    app.config.ignoredElements = [/^app-/]  app.use(/* ... */)  app.mixin(/* ... */)  app.component(/* ... */)  app.directive(/* ... */)    app.mount(\'#app\')

你可能已經注意到,每個配置都局限於使用 createApp 定義的某個 Vue 應用程式。

它可以讓你的程式碼更容易理解,並且不容易出現由第三方插件引起的意外問題。當前,如果某些第三方解決方案正在修改 Vue 對象,則可能會以意想不到的方式(尤其是全局 mixins)影響你的應用程式,而 Vue 3 則不會出現這種情況。

這一 API 更改現在正在這個 RFC 中討論,意味著將來它可能還會繼續變動。

片段

Vue 3 中值得期待的另一個激動人心的新功能是片段(Fragments)。

你可能會問什麼是片段?嗯,如果你創建一個 Vue 組件,則它只能有一個根節點。這意味著無法創建這樣的組件:

<template>    <div>Hello</div>    <div>World</div>  </template>

原因是代表任何 Vue 組件的 Vue 實例都需要綁定到單個 DOM 元素中。想要創建具有多個 DOM 節點的組件,唯一的方法是創建一個沒有基礎 Vue 實例的功能組件。

事實證明,React 社區也有同樣的問題。他們提出的解決方案是一個名為片段(Fragment)的虛擬元素。它看起來差不多是這個樣子:

class Columns extends React.Component {    render() {      return (        <React.Fragment>          <td>Hello</td>          <td>World</td>        </React.Fragment>      );    }  }

儘管片段看起來像是普通的 DOM 元素,但它是虛擬的,根本不會在 DOM 樹中渲染。這樣我們可以將組件功能綁定到單個元素中,而無需創建多餘的 DOM 節點。

目前,你可以在 Vue 2 中使用 vue-fragments 庫來應用片段,而在 Vue 3 中它是開箱即用的!

Suspense

React 生態系統中還有一個好主意也將在 Vue 3 中採用,就是 Suspense 組件。

Suspense 會暫停你的組件渲染,並渲染回退組件,直到滿足一個條件為止。在 Vue London Evan 期間,Vue 團隊簡單介紹了這個主題,並向我們展示了值得期待的 API。到頭來 Suspense 只是一個具有插槽的組件:

<Suspense>    <template >      <Suspended-component />    </template>    <template #fallback>      Loading...    </template>  </Suspense>

在 Suspended-component 完全渲染之前將顯示回退組件。Suspense 可以等待組件下載完畢(如果該組件是非同步的),或者在 setup 函數中執行一些非同步操作。

多個 v-model

V-model 是一種指令,可用於在給定組件上實現雙向綁定。我們可以傳遞響應性屬性,並從組件內部對其進行修改。

從表單元素可以很好地了解 v-model:

<input v-bind="property />

但是你知道可以對所有組件使用 v-model 嗎?在後台,v-model 只是傳遞 value 屬性和偵聽 input 事件的捷徑。將上面的示例重寫為以下語法會有完全相同的效果:

<input    v-bind:value="property"    v-on:input="property = $event.target.value"  />

我們甚至可以使用組件 model 屬性更改默認屬性和事件的名稱:

model: {    prop: \'checked\',    event: \'change\'  }

如你所見,如果我們希望在組件中進行雙向綁定,則 v-model 指令可能是一個非常有用的語法糖。不幸的是,每個組件只能有一個 v-model。

還好在 Vue 3 中不會有這個問題!你將能夠給 v-model 賦予屬性名稱,並根據需要擁有儘可能多的 v-model。下面這個示例中,你可以在一個表單組件中找到兩個 v-model:

<InviteeForm    v-model:name="inviteeName"    v-model:email="inviteeEmail"  />

這一 API 更改現在正在這個 RFC 中討論,這意味著將來可能會有新的變化。

Portals

Portals 是特殊的組件,用來在當前組件之外渲染某些內容。這也是 React 原生實現的功能之一。React 文檔關於 portals 是這樣介紹的:

「Portals 提供了一種一流的方式來將子級渲染到父組件的 DOM 層級之外的 DOM 節點中。」

這是一種處理模態、彈出窗口以及頁面頂部組件的非常好用的方法。通過 portals,你可以確保沒有任何主機組件 CSS 規則會影響你要顯示的組件,也無需使用 z-index 搞些小動作了。

對於每個 portal,我們需要指定其目標位置,在其中渲染 portal 內容。下面是 portal-vue 庫的實現,它為 Vue 2 添加了這一功能:

<portal to="destination">    <p>This slot content will be rendered wherever thportal-target with name \'destination\'      is located.</p>  </portal>    <portal-target name="destination">    <!--    這個組件可以放在你應用中的任何位置上    上面 portal 組件的插槽內容會在這裡渲染。    -->  </portal-target>

Vue 3 將提供對 portals 的開箱即用支援!

新的自定義指令 API

自定義指令 API 在 Vue 3 中將略有變化,以更好地與組件生命周期保持一致。這項更改會讓 API 更加直觀,從而幫助新手更容易地理解和學習 API。

這是當前的自定義指令 API:

const MyDirective = {    bind(el, binding, vnode, prevVnode) {},    inserted() {},    update() {},    componentUpdated() {},    unbind() {}  }

下面是 Vue 3 中的樣子:

const MyDirective = {    beforeMount(el, binding, vnode, prevVnode) {},    mounted() {},    beforeUpdate() {},    updated() {},    beforeUnmount() {}, // new    unmounted() {}  }

雖然這是一項重大更改,使用 Vue 兼容構建也應該能輕鬆覆蓋。

這一 API 更改現在正在這個 RFC 中討論,意味著將來它可能還會繼續變動。

小結

除了合成 API(它是 Vue 3 中最重要的新 API)之外,我們還能在新版中找到很多較小的改進。我們可以看到,Vue 正在向更好的開發人員體驗和更簡單、更直觀的 API 的目標前進。我們也很高興看到 Vue 團隊決定採用目前由第三方庫帶來的許多理念,將它們引入核心框架。

上面的內容只介紹了主要的 API 更改和改進。如果你對其他內容感到好奇,請務必查看 Vue RFCs 存儲庫。