Vue 3尚未正式發佈,可是維護者已經發布了beta版本,以供咱們的用戶嘗試並提供反饋
html
若是您想知道Vue 3的主要特性和主要變化,我將在本文中經過使用Vue 3 beta 9建立一個簡單的應用程序來強調它們vue
我將介紹儘量多的新內容,包括片斷、傳送、複合API和其餘一些模糊的更改。我也會盡我所能來解釋這個特性或變動的基本原理webpack
咱們將構建一個帶有模態窗口功能的簡單應用程序。我選擇這個是由於它方便地容許我展現一些Vue 3的更改。git
下面是這款應用在打開和關閉狀態下的樣子,這樣你就能夠在腦海中想象出咱們正在作的事情:github
與其直接安裝Vue 3,不如克隆Vue -next- Webpack -preview項目,它將爲咱們提供包括Vue 3在內的最小的Webpack設置。web
$ git clone https://github.com/vuejs/vue-next-webpack-preview.git vue3-experiment
$ cd vue3-experiment
$ npm i
一旦克隆並安裝了NPM模塊,咱們所須要作的就是刪除樣板文件並建立一個新的main.js文件,這樣咱們就能夠從頭建立Vue 3應用程序了。npm
$ rm -rf src/*
$ touch src/main.js
如今咱們將運行開發服務器:數組
立刻,咱們啓動一個新的Vue應用程序的方式改變了。咱們如今須要導入新的createApp方法,而不是使用新的Vue()瀏覽器
而後咱們調用這個方法,傳遞咱們的Vue實例定義對象,並將返回對象分配給一個變量app服務器
接下來,咱們將在app上調用mount方法,並傳遞一個CSS選擇器來指示咱們的mount元素,就像咱們在Vue 2中使用$mount實例方法同樣
import { createApp } from "vue";
const app = createApp({
// root instance definition
});
app.mount("#app");
在舊的API中,咱們添加的任何全局配置(插件、混合、原型屬性等)都會永久地改變全局狀態。例如:
// Affects both instances
Vue.mixin({ ... })
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
這在單元測試中確實是一個問題,由於它使確保每一個測試與上一個測試隔離變得很棘手。
在新的API下,調用createApp將返回一個新的app實例,該實例不會被應用於其餘實例的任何全局配置所污染。
Learn more:Global API change RFC.
咱們的模式窗口能夠處於兩種狀態之一——打開或關閉。讓咱們用一個布爾狀態屬性modalOpen來管理它,咱們會給它一個初始值false
const app = createApp({
data: {
modalOpen: false
}
});
這是不容許的。相反,必須爲數據分配一個返回狀態對象的工廠函數。
這是您必須爲Vue組件作的事情,可是如今它也對Vue應用程序實例強制執行
const app = createApp({
data: () => ({
modalOpen: false
})
});
使用對象而不是工廠函數的優勢是,首先,它在語法上更簡單,其次,你能夠在多個根實例之間共享頂層狀態,例如:
const state = {
sharedVal: 0
};
const app1 = new Vue({ state });
const app2 = new Vue({ state });
// Affects both instances
app1._data.sharedVal = 1;
這種用例不多,可使用。由於有兩種類型的聲明是不適合初學者的,因此決定刪除這個特性。
Learn more:Data object declaration removed RFC
在繼續以前,讓咱們添加一個方法來切換modalOpen值。這與Vue 2沒有什麼不一樣。
const app = createApp({
data: () => ({
modalOpen: true
}),
methods: {
toggleModalState() {
this.modalOpen = !this.modalOpen;
}
}
});
若是您如今轉到瀏覽器並檢查控制檯,您將看到警告「組件缺乏呈現函數」,由於咱們尚未爲根實例定義模板。
Vue 2的最佳實踐是爲根實例建立一個最小的模板,並建立一個應用程序組件,其中將聲明主應用程序標記。
咱們在這裏也作一下。
touch src/App.vue
如今咱們能夠得到根實例來呈現該組件。不一樣之處在於,在Vue 2中,咱們一般會使用渲染函數來完成如下操做:
import App from "./App.vue";
const app = createApp({
...
render: h => h(App)
});
app.mount("#app");
咱們仍然能夠這樣作,可是Vue 3有一個更簡單的方法——讓應用程序成爲一個根組件。爲此,咱們能夠刪除根實例定義並傳遞App組件。
import App from "./App.vue";
const app = createApp(App);
app.mount("#app");
這意味着App組件不只由根實例呈現,並且是根實例。
在此過程當中,讓咱們經過刪除app變量來簡化一下語法:
createApp(App).mount("#app");
如今移動到根組件,讓咱們從新添加狀態和方法到這個組件:
<script>
export default {
data: () => ({
modalOpen: true
}),
methods: {
toggleModalState() {
this.modalOpen = !this.modalOpen;
}
}
};
</script>
讓咱們也爲模態特性建立一個新組件:
touch src/Modal.vue
如今,咱們將提供一個包含內容插槽的最小模板。這確保了咱們的模式是可重用的。稍後咱們將向該組件添加更多內容。
<template>
<div class="modal">
<slot></slot>
</div>
</template>
如今讓咱們爲根組件建立模板。咱們將建立一個按鈕來打開模態,它將觸發toggleModalState方法
咱們還將使用剛剛建立的模態組件,它將根據modalState的值呈現。咱們還能夠在內容槽中插入一段文本。
<template>
<button @click="toggleModalState">Open modal</button>
<modal v-if="modalOpen">
<p>Hello, I'm a modal window.</p>
</modal>
</template>
<script>
import Modal from "./Modal.vue";
export default {
components: {
Modal
},
...
}
</script>
注意到這個模板有什麼奇怪的地方嗎?看一遍。我將等待。
沒錯,有兩個根元素。在Vue 3中,因爲一個稱爲fragment的特性,它再也不強制擁有單個根元素!
Vue 3的旗艦特性是複合API。這個新的API容許您使用setup函數定義組件功能,而不是使用添加到組件定義對象的屬性。
如今,讓咱們重構應用程序組件,以使用複合API。
在我解釋代碼以前,要清楚咱們所作的一切都是重構——組件的功能是相同的。還要注意,模板沒有改變,由於複合API隻影響咱們定義組件功能的方式,而不是咱們呈現它的方式。
<template>
<button @click="toggleModalState">Open modal</button>
<modal v-if="modalOpen">
<p>Hello, I'm a modal window.</p>
</modal>
</template>
<script>
import Modal from "./Modal.vue";
import { ref } from "vue";
export default {
setup () {
const modalState = ref(false);
const toggleModalState = () => {
modalState.value = !modalState.value;
};
return {
modalState,
toggleModalState
}
}
};
</script>
setup
method首先,請注意咱們導入了ref函數,該函數容許咱們定義一個反應變量modalState。這個變量等價於This . modalstate。
toggleModalState方法只是一個普通的JavaScript函數。可是,請注意,要更改方法體中的modalState的值,咱們須要更改它的子屬性值。這是由於使用ref建立的反應變量被包裝在一個對象中。這對於保持它們在傳遞過程當中的活性是必要的。
若是您想詳細瞭解refs的工做方式,最好查閱Vue Composition API文檔。
最後,咱們從setup方法返回modalState和toggleModalState,由於它們是在模板呈現時傳遞給模板的值。
請記住,組合API不是一個更改,由於它徹底是可選的。主要動機是考慮更好的代碼組織和組件之間的代碼重用(由於mixin本質上是一種反模式)
若是您認爲在本例中重構應用程序組件以使用複合API是沒必要要的,那麼您是正確的。可是,若是這是一個更大的組件,或者咱們須要與其餘組件共享它的特性,那麼您就會看到它的有用性。
提供更深刻的示例超出了本文的範圍,因此若是您有興趣瞭解更多關於新API的使用,請參閱個人另外一篇文章,瞭解什麼時候使用新Vue複合API(以及什麼時候不使用)。
若是您之前建立過模態特性,您就會知道它一般被放置在關閉的標記以前。
<body>
<div>
<!--main page content here-->
</div>
<!--modal here-->
</body>
這樣作是由於情態動詞一般有一個頁面覆蓋的背景(若是你不明白個人意思,請參閱開頭的圖片)。要使用CSS實現這一點,您不須要處理父元素定位和z-index疊加上下文,所以最簡單的解決方案是將模態放在DOM的最底部。
這就與Vue產生了問題。不過,它假設UI將被構建爲一個組件樹。爲了容許樹的片斷移動到DOM中的其餘位置,Vue 3中添加了一個新的傳送組件
要使用傳送,讓咱們首先向頁面添加一個元素,咱們但願將模態內容移動到該頁面。咱們將轉到index.html,並在Vue的掛載元素旁邊放置一個帶ID modal-wrapper的div。
<body>
...
<div id="app"></div><!--Vue mounting element-->
<div id="modal-wrapper">
<!--modal should get moved here-->
</div>
</body>
如今,回到App.vue,咱們將把模態內容包裝在傳送組件中。咱們還須要指定一個to屬性,它將被分配一個用於標識目標元素的查詢選擇器,在本例中是#modal-wrapper。
<template>
<button @click="toggleModalState">Open modal</button>
<teleport to="#modal-wrapper">
<modal v-if="modalOpen">
<p>Hello, I'm a modal window.</p>
</modal>
</teleport>
</template>
就是這樣。傳送中的任何內容都將在目標元素中呈現。然而,它仍然會像它在層級中的最初位置同樣工做(關於道具,事件等)。
所以,在您保存代碼以後,從新加載頁面,在開發工具中檢查DOM,您會感到驚訝!
Learn more:Teleport RFC
如今讓咱們在模態中添加一個按鈕來關閉它。爲此,咱們將向modal tempate添加一個按鈕元素,並使用一個發出事件close的click處理程序。
<template>
<div class="modal">
<slot></slot>
<button @click="$emit('close')">Dismiss</button>
</div>
</template>
而後父組件將捕捉此事件,並切換modalState的值,使其在邏輯上爲假,並致使窗口關閉。
<template>
...
<modal
v-if="modalOpen"
@click="toggleModalState"
>
<p>Hello, I'm a modal window.</p>
</modal>
</teleport>
</template>
到目前爲止,這個特性與Vue 2中的特性徹底相同。可是,在Vue 3中,如今建議您使用新的component選項顯式地聲明組件的事件。就像使用道具同樣,您能夠簡單地建立一個字符串數組來命名組件將發出的每一個事件
<template>...</template>
<script>
export default {
emits: [ "close" ]
}
</script>
想象一下,打開別人編寫的組件文件,並查看顯式聲明的組件的道具和事件。立刻,您就會理解這個組件的接口,即它要發送和接收什麼。
除了提供自我記錄的代碼以外,您還可使用事件聲明來驗證事件負載,儘管在本例中我找不到這樣作的理由。
Learn more:Emits Option RFC
爲了使咱們的模式可重用,咱們爲內容提供了一個插槽。讓咱們經過向組件添加樣式標籤來開始對該內容進行樣式化。
在咱們的組件中使用限定範圍的CSS是一個很好的實踐,以確保咱們提供的規則不會對頁面中的其餘內容產生意外的影響
讓咱們把任何段落文本放到槽裏都改爲斜體。爲此,咱們將使用p選擇器建立一個新的CSS規則。
<template>...</template>
<script>...</script>
<style scoped>
p {
font-style: italic;
}
</style>
若是你試一下,你會發現它不起做用。問題是,當槽內容仍然屬於父內容時,在編譯時肯定了做用域樣式。
Vue 3提供的解決方案是提供一個僞選擇器::v- sloated(),容許您使用提供插槽的組件中的做用域規則來鎖定插槽內容。
Here's how we use it:
<style scoped>
::v-slotted(p) {
font-style: italic;
}
</style>
Vue 3 also includes some other new scoped styling selectors::v-deep
and::v-global
which you can learn more about here:Scoped Styles RFC
好了,這就是我能夠在一個簡單的例子中介紹的全部新特性。我獲得了大部分主要的,但這裏有一些我認爲重要到足以在結束文章以前提到,你能夠本身研究:
Added:
Removed:
Filters
Inline templates
Event interface for components (no more event bus!)
Changed:
Async component API
Custom directive API
Render function syntax
There are also various changes regarding Vue Router but I'll be dedicating a separate article to those!