Vue 3 中令人興奮的新功能

作者:Filip Rakowski

翻譯:瘋狂的技術宅

原文:https://vueschool.io/articles...

未經允許嚴禁轉載

在上一篇文章中,我們瞭解了 Vue 3 將帶來的性能改進。我們已經知道,用新的 Vue 3 編寫的程序效果會很好,但性能並不是最重要的部分。對開發人員而言,最重要的是新版本將會怎樣影響我們編寫代碼的方式。

如你所料,Vue 3 帶來了許多令人興奮的新功能。值得慶幸的是,Vue 團隊主要介紹了對當前 API 的添加和改進,而不是重大更改,所以已經瞭解 Vue 2 的人們應該很快就會對新語法感到滿意。

Let's start with the API that most of you probably heard about...
讓我們從大多數人可能聽說過的API開始...

組件 API(Composition API)

組件 API 是 Vue 的下一個主要版本中最常用的討論和特色語法。這是一種全新的邏輯重用和代碼組織方法。

當前,我們使用所謂的 Options API 構建組件。爲了向 Vue 組件添加邏輯,我們填充(可選)屬性,例如 datamethodscomputed等。這種方法的最大缺點是其本身並不是有效的 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() {

簡而言之,它只是一個將屬性和函數返回到模板的函數而已。我們在這裏聲明所有響應性屬性、計算屬性、觀察者和生命週期 hook,然後將它們返回,以便可以在模板中使用它們。

我們不從 setup 函數返回的內容在模板中將會不可用。

const count = ref(0)

根據上面的內容,我們聲明瞭帶有 ref 函數的名爲 count 的響應屬性。它可以包裝任何原語或對象並返回其響應性引用。傳遞的元素的值將會保留在所創建引用的 value 屬性中。例如,如果你想訪問 count 引用的值,則需要明確要求 count.value

const double = computed(() => count.value * 2)

function increment() {
  count.value++
}

這正是我們在聲明計算屬性 doubleincrement 函數時所要做的。

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

使用 onMounted hook,我們會在安裝組件時記錄一些消息,只是向你展示可以做到!😉

return {
  count,
  double,
  increment
}

最後,我們將使用 increment 方法返回 countdouble 屬性,以使它們在模板中可用。

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

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

這是一個簡單的例子,也可以通過 Options API 輕鬆實現。新的組件 API 的真正好處不僅在於能以不同的方式進行編碼,在對我們的代碼和邏輯進行重用時,這些好處也能顯示出來。

用組件 API 進行代碼重用

新的組件 API 具有更多優點。考慮一下代碼重用。目前如果我們要在其他組件之間共享一些代碼,則有兩個可用的選擇:mixins 和作用域插槽( scoped slots)。但是兩者都有缺點。

假設我們要提取 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 背後的原因,並提出了最好的用例建議。還有 great repository ,其中包含來自 Vue 核心團隊的 Thorsten Lü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 對象,那麼它可能會以意想不到的方式(尤其是全局混合)影響你的程序,而 Vue 3 則沒有這個問題。

目前,此 API 的更改正在 這個 RFC 中進行討論,這意味着將來可能會有所更改。

片段(Fragments)

我們可以在 Vue 3 中期待的另一個激動人心的附加功能是片段。

你可能會問什麼片段?好吧,如果你創建了一個 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>
    );
  }
}

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

現在你可以在帶有 vue-fragments 庫的 Vue 2 中使用片段,而在 Vue 3 中你可以直接使用它!

Suspense

將被用在 Vue 3 中的另一個從 React 學來的功能是 Suspense 組件。

Suspense 能夠暫停你的組件渲染,並渲染後備組件,直到條件滿足爲止。在 Vue London 期間,尤雨溪簡短地談到了這個主題,並向我們展示了可以期望的 API。事實證明,Suspense 只是帶有插槽的組件:

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

直到 Suspended-component 完全渲染前將會顯示後備內容。掛起可以等待,直到該組件被下載(如果該組件是異步組件的話),或者在 setup 函數中執行一些異步操作。

Multiple v-models

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

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

目前,此 API 的更改已在這個 RFC 中進行討論,這意味着將來可能會有更改。

Portals

Portals 是特殊的組件,用來在當前組件之外渲染某些內容。它也是在 React 中實現的功能之一。這就是 React 文檔關於 Portals 的內容:

Portals 提供了一種獨特的方法來將子級渲染到父組件的 DOM 層次結構之外的 DOM 節點中。

這種處理模式,是彈出式窗口以及通常顯示在頁面頂部的組件所使用的一種非常好的方法。通過使用 Portals,你可以確保沒有任何主機組件 CSS 規則會影響你要顯示的組件,並且可以避免用 z-index 進行的黑客攻擊。

對於每個 Portal,我們需要爲其指定目標位置,在該目標位置將渲染 Portals 內容。在下面,你可以從 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">
  <!--
  This component can be located anywhere in your App.
  The slot content of the above portal component wilbe rendered here.
  -->
</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 兼容版本涵蓋到。

在此 RFC 中討論了這個 API 的更改,這意味着將來可能會改進。

摘要

除了 Composition API(它是 Vue 3 中最大的主要新 API)之外,我們還可以找到很多較小的改進。可以看到 Vue 正在朝着更友好的開發體驗和更簡單、更直觀的 API 邁進。十分高興看到 Vue 團隊決定在框架的核心採用了許多目前只能通過第三方庫獲得的想法。

上面的列表僅表示主要的 API 更改和改進。如果你對其他的內容感到好奇,請務必檢查 Vue RFCs 信息庫


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,每天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,每天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章