簡介
- 2020年9月18日發(fā)布Vue3正式版本V3.0.0亏镰,命名為One Piece衔瓮。
- Vue 的組件可以按兩種不同的風(fēng)格書寫:選項(xiàng)式 API 和組合式 API收厨。
該選哪一個(gè)缓溅?
兩種 API 風(fēng)格都能夠覆蓋大部分的應(yīng)用場景洒沦。它們只是同一個(gè)底層系統(tǒng)所提供的兩套不同的接口。實(shí)際上钳榨,選項(xiàng)式 API 是在組合式 API 的基礎(chǔ)上實(shí)現(xiàn)的舰罚!關(guān)于 Vue 的基礎(chǔ)概念和知識在它們之間都是通用的。
選項(xiàng)式 API 以“組件實(shí)例”的概念為中心 (即上述例子中的 this)薛耻,對于有面向?qū)ο笳Z言背景的用戶來說营罢,這通常與基于類的心智模型更為一致。同時(shí),它將響應(yīng)性相關(guān)的細(xì)節(jié)抽象出來饲漾,并強(qiáng)制按照選項(xiàng)來組織代碼蝙搔,從而對初學(xué)者而言更為友好。
組合式 API 的核心思想是直接在函數(shù)作用域內(nèi)定義響應(yīng)式狀態(tài)變量考传,并將從多個(gè)函數(shù)中得到的狀態(tài)組合起來處理復(fù)雜問題吃型。這種形式更加自由,也需要你對 Vue 的響應(yīng)式系統(tǒng)有更深的理解才能高效使用僚楞。相應(yīng)的勤晚,它的靈活性也使得組織和重用邏輯的模式變得更加強(qiáng)大。
[圖片上傳失敗...(image-a9c136-1660454836197)]
Vue3帶來的變化
1. 性能提升1.3~2.x
- 核心代碼 + Composition API :13.5kb泉褐,最小可以到11.75kb
- 所有的Runtime:22.5kb(Vue2是32kb)
為什么會(huì)有這么大的性能提升呢赐写? 這里就要說到 Compiler 的原理: - 靜態(tài)Node不再做更新處理
- 靜態(tài)綁定的class和id不再做更新處理
- vue在mount的過程中會(huì)編譯成ast語法樹, 會(huì)給動(dòng)態(tài)的內(nèi)容打上一個(gè)標(biāo)記
PatchFlag
膜赃,進(jìn)行更新分析(動(dòng)態(tài)綁定)挺邀,會(huì)區(qū)分哪些是靜態(tài)內(nèi)容哪些是動(dòng)態(tài)內(nèi)容,然后對動(dòng)態(tài)內(nèi)容去做更新處理 - 事件監(jiān)聽器Cache緩存處理(cacheHandlers)跳座,組件創(chuàng)建的過程中不會(huì)去重復(fù)的多次實(shí)例化端铛,對內(nèi)存的優(yōu)化是非常好的,減少創(chuàng)建對象的數(shù)量疲眷,從而減少內(nèi)存占用提高性能
- hoistStatich自動(dòng)針對多靜態(tài)節(jié)點(diǎn)進(jìn)行優(yōu)化禾蚕,輸出字符串
測試地址:https://vue-next-template-explorer.netlify.app
2. Ts支持,新增:Fragment咪橙、Teleport夕膀、Suspense
- Fragment不受根節(jié)點(diǎn)限制,渲染函數(shù)可接收Array美侦。意思就是我們在temeplate中不再受根節(jié)點(diǎn)限制产舞,可以任意的插入多個(gè)文本,字符串或者圖片
- Teleport--類似Portal菠剩,隨用隨取比如:彈窗易猫、Actions,比如我們有可能需要在app節(jié)點(diǎn)之外比如body中控制一個(gè)彈窗的顯示或者隱藏具壮,可以用到它
- Suspense 從框架層面的一個(gè)異步組件准颓,可以支持嵌套加載的一個(gè)場景,比如樹形組件我們要加載多層加的組織架構(gòu)棺妓,我們希望把下面所有的組織架構(gòu)都加載完成以后再顯示整個(gè)組件攘已,這個(gè)時(shí)候就需要用到它。例如:
async setup()
3. 按需加載(配合vite)& 組合Api
Vue2和Vue3的比較
1. 為什么要用 Composition API怜跑?
(1) Vue2對于復(fù)雜邏輯的組件样勃,后期變得無法維護(hù)
下面是vue2實(shí)現(xiàn)加減的代碼:
<template>
<div class="homePage">
<p>count: {{ count }}</p>
<p>倍數(shù): {{ multiple }}</p>
<div>
<button style="margin-right: 10px" @click="increase">加1</button>
<button @click="decrease">減一</button>
</div>
</div>
</template>
<script>
export default {
data() {
return { count: 0 };
},
computed: {
multiple() {
return 2 * this.count;
},
},
methods: {
increase() {
this.count++;
},
decrease() {
this.count--;
},
},
};
</script>
上面代碼只是實(shí)現(xiàn)了對 count 的加減以及顯示倍數(shù)吠勘, 就需要分別在data、methods峡眶、computed中進(jìn)行操作剧防,當(dāng)我們增加一個(gè)需求,就會(huì)出現(xiàn)下圖的情況:
[圖片上傳失敗...(image-7fc1e4-1660454836197)]
當(dāng)我們業(yè)務(wù)復(fù)雜了就會(huì)大量出現(xiàn)上面的情況辫樱, 隨著復(fù)雜度上升峭拘,就會(huì)出現(xiàn)這樣一張圖:
[圖片上傳失敗...(image-43cb6f-1660454836197)]
當(dāng)這個(gè)組件的代碼超過幾百行時(shí),這時(shí)增加或者修改某個(gè)需求狮暑, 就要在data鸡挠、methods、computed以及mounted中反復(fù)的跳轉(zhuǎn)
如果可以按照邏輯進(jìn)行分割心例,將上面這張圖變成下邊這張圖宵凌,是不是就清晰很多了呢, 這樣的代碼可讀性和可維護(hù)性都更高:
[圖片上傳失敗...(image-6d6040-1660454836197)]
那么vue2.x版本給出的解決方案就是Mixin, 但是使用Mixin會(huì)有缺陷:
- 命名空間沖突
- 不清楚暴露出來的變量的作用
- 邏輯重用到其他 component 經(jīng)常遇到問題(不易復(fù)用)
Vue3.x就推出了 Composition API 主要就是為了解決上面的問題,將零散分布的邏輯組合在一起來維護(hù)止后,并且還可以將單獨(dú)的功能邏輯拆分成單獨(dú)的文件。
(2)scoped slot作用域插槽(配置項(xiàng)多溜腐、代碼分裂译株、性能差)
(3) Vue2對Ts支持不充分
(4)Vue3使復(fù)雜組件邏輯進(jìn)行分離,組件間的邏輯共享
(5)Vue3組合式API + 函數(shù)式編程
組合式 API (Composition API)
- 通過組合式 API挺益,我們可以使用導(dǎo)入的 API 函數(shù)來描述組件邏輯歉糜。在單文件組件中,組合式 API 通常會(huì)與
<script setup>
搭配使用望众。這個(gè)setup
attribute 是一個(gè)標(biāo)識匪补,告訴 Vue 需要在編譯時(shí)進(jìn)行一些處理,讓我們可以更簡潔地使用組合式 API烂翰。比如夯缺,<script setup>
中的導(dǎo)入和頂層變量/函數(shù)都能夠在模板中直接使用。
[圖片上傳失敗...(image-fcfc2-1660454836197)]
1. setup
-
setup
是 Vue3.x 新增的一個(gè)選項(xiàng)甘耿, 他是組件內(nèi)使用Composition API
的入口踊兜。
export default defineComponent({
beforeCreate() {
console.log("----beforeCreate----");
},
created() {
console.log("----created----");
},
setup() {
console.log("----setup----");
},
})
[圖片上傳失敗...(image-cf7909-1660454836197)]
- 通過代碼打印結(jié)果,
setup
執(zhí)行時(shí)機(jī)是在 beforeCreate 之前執(zhí)行
setup 參數(shù)
- 使用setup時(shí)佳恬,它接受兩個(gè)參數(shù):
- props: 組件傳入的屬性
- context
- setup 中接受的props是響應(yīng)式的捏境, 當(dāng)傳入新的 props 時(shí),會(huì)及時(shí)被更新毁葱。由于是響應(yīng)式的垫言, 所以不可以使用 ES6 解構(gòu),解構(gòu)會(huì)消除它的響應(yīng)式倾剿。
- 錯(cuò)誤代碼示例筷频, 這段代碼會(huì)讓 props 不再支持響應(yīng)式:
export default defineComponent ({
setup(props, context) {
const { name } = props
console.log(name)
},
})
- 開發(fā)中我們想要使用解構(gòu),還能保持
props
的響應(yīng)式,需要用到后面的toRefs
來解決 -
setup
中不能訪問 Vue2 中最常用的this
對象,所以context
中就提供了this中最常用的三個(gè)屬性:attrs
截驮、slot
和emit
分別對應(yīng) Vue2.x 中的$attr
屬性笑陈、slot
插槽 和$emit
發(fā)射事件。并且這幾個(gè)屬性都是自動(dòng)同步最新的值葵袭,所以我們每次使用拿到的都是最新值涵妥。
2. reactive、ref 與 toRefs
- 在 vue2.x 中坡锡, 定義數(shù)據(jù)都是在data中蓬网, 但是 Vue3.x 可以使用reactive和ref來進(jìn)行數(shù)據(jù)定義。
import { ref } from 'vue'
const count = ref(0)
reactive
函數(shù)可以代理一個(gè)對象鹉勒, 但是不能代理基本類型帆锋,例如字符串、數(shù)字禽额、boolean 等
<template>
<div class="homePage">
<p>第 {{ year }} 年</p>
<p>姓名: {{ nickname }}</p>
<p>年齡: {{ age }}</p>
</div>
</template>
<script>
import { defineComponent, reactive, ref } from "vue";
export default defineComponent({
setup() {
const year = ref(0);
const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
setInterval(() => {
year.value++;
user.age++;
}, 1000);
return {
year,
user
};
},
});
</script>
上面的代碼中锯厢,我們綁定到頁面是通過user.name
, user.age
;這樣寫感覺很繁瑣脯倒,我們能不能直接將user
中的屬性解構(gòu)出來使用呢? 答案是不能直接對user
進(jìn)行結(jié)構(gòu)实辑, 這樣會(huì)消除它的響應(yīng)式, 這里就和上面我們說props不能使用 ES6 直接解構(gòu)就呼應(yīng)上了藻丢。那我們就想使用解構(gòu)后的數(shù)據(jù)怎么辦剪撬,解決辦法就是使用toRefs
。
toRefs
用于將一個(gè) reactive
對象轉(zhuǎn)化為屬性全部為 ref
對象的普通對象悠反。具體使用方式如下:
<template>
<div class="homePage">
<p>第 {{ year }} 年</p>
<p>姓名: {{ nickname }}</p>
<p>年齡: {{ age }}</p>
</div>
</template>
<script>
import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
setup() {
const year = ref(0);
const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
setInterval(() => {
year.value++;
user.age++;
}, 1000);
return {
year,
// 使用reRefs
...toRefs(user),
};
},
});
</script>
生命周期鉤子
[圖片上傳失敗...(image-e57ed6-1660454836197)]
從圖中我們可以看到 Vue3.0 新增了setup
残黑,這個(gè)在前面我們也詳細(xì)說了, 然后是將 Vue2.x 中的beforeDestroy
名稱變更成beforeUnmount
; destroyed
表更為 unmounted
斋否,作者說這么變更純粹是為了更加語義化梨水,因?yàn)橐粋€(gè)組件是一個(gè)mount
和unmount
的過程。其他 Vue2 中的生命周期仍然保留如叼。
上邊 生命周期圖 中并沒包含全部的生命周期鉤子冰木, 還有其他的幾個(gè), 全部生命周期鉤子如圖所示:
[圖片上傳失敗...(image-e1eadd-1660454836197)]
我們可以看到 beforeCreate
和 created
被 setup
替換了(但是Vue3中你仍然可以使用笼恰, 因?yàn)閂ue3是向下兼容的踊沸, 也就是你實(shí)際使用的是vue2的)。其次社证,鉤子命名都增加了 on
; Vue3.x還新增用于調(diào)試的鉤子函數(shù) onRenderTriggered
和 onRenderTricked
自定義 Hooks
開篇的時(shí)候我們使用 Vue2.x 寫了一個(gè)實(shí)現(xiàn)加減的例子逼龟, 這里可以將其封裝成一個(gè) hook, 我們約定這些「自定義 Hook」以 use 作為前綴,和普通的函數(shù)加以區(qū)分追葡。 useCount.ts 實(shí)現(xiàn):
import { ref, Ref, computed } from "vue";
type CountResultProps = {
count: Ref<number>;
multiple: Ref<number>;
increase: (delta?: number) => void;
decrease: (delta?: number) => void;
};
export default function useCount(initValue = 1): CountResultProps {
const count = ref(initValue);
const increase = (delta?: number): void => {
if (typeof delta !== "undefined") {
count.value += delta;
} else {
count.value += 1;
}
};
const multiple = computed(() => count.value * 2);
const decrease = (delta?: number): void => {
if (typeof delta !== "undefined") {
count.value -= delta;
} else {
count.value -= 1;
}
};
return {
count,
multiple,
increase,
decrease,
};
}
接下來看一下在組件中使用useCount這個(gè) hook:
<template>
<p>count: {{ count }}</p>
<p>倍數(shù): {{ multiple }}</p>
<div>
<button @click="increase()">加1</button>
<button @click="decrease()">減一</button>
</div>
</template>
<script lang="ts">
import useCount from "../hooks/useCount";
setup() {
const { count, multiple, increase, decrease } = useCount(10);
return {
count,
multiple,
increase,
decrease,
};
},
</script>
簡單對比 vue2.x 與 vue3.x 響應(yīng)式
Vue3.x 將使用 Proxy
取代 Vue2.x 版本的 Object.defineProperty
這里就簡單對比一下:
-
Object.defineProperty
只能劫持對象的屬性腺律, 而Proxy
是直接代理對象
由于Object.defineProperty
只能劫持對象屬性奕短,需要遍歷對象的每一個(gè)屬性,如果屬性值也是對象匀钧,就需要遞歸進(jìn)行深度遍歷翎碑。但是 Proxy
直接代理對象, 不需要遍歷操作
-
Object.defineProperty
對新增屬性需要手動(dòng)進(jìn)行Observe
因?yàn)?code>Object.defineProperty劫持的是對象的屬性之斯,所以新增屬性時(shí)日杈,需要重新遍歷對象, 對其新增屬性再次使用Object.defineProperty
進(jìn)行劫持佑刷。也就是 Vue2.x 中給數(shù)組和對象新增屬性時(shí)莉擒,需要使用set內(nèi)部也是通過調(diào)用Object.defineProperty
去處理的
Teleport
Teleport
就像是哆啦 A 夢中的「任意門」,任意門的作用就是可以將人瞬間傳送到另一個(gè)地方瘫絮。有了這個(gè)認(rèn)識涨冀,我們再來看一下為什么需要用到 Teleport 的特性呢,看一個(gè)小例子:
- 在子組件Header中使用到Dialog組件麦萤,我們實(shí)際開發(fā)中經(jīng)常會(huì)在類似的情形下使用到 Dialog 鹿鳖,此時(shí)Dialog就被渲染到一層層子組件內(nèi)部,處理嵌套組件的定位壮莹、z-index和樣式都變得困難栓辜。
- Dialog從用戶感知的層面,應(yīng)該是一個(gè)獨(dú)立的組件垛孔,從 dom 結(jié)構(gòu)應(yīng)該完全剝離 Vue 頂層組件掛載的 DOM;同時(shí)還可以使用到 Vue 組件內(nèi)的狀態(tài)(data或者props)的值施敢。簡單來說就是,即希望繼續(xù)在組件內(nèi)部使用Dialog, 又希望渲染的 DOM 結(jié)構(gòu)不嵌套在組件的 DOM 中周荐。
- 此時(shí)就需要
Teleport
上場,我們可以用<Teleport>
包裹Dialog, 此時(shí)就建立了一個(gè)傳送門僵娃,可以將Dialog渲染的內(nèi)容傳送到任何指定的地方概作。 - 接下來就舉個(gè)小例子,看看 Teleport 的使用方式:
我們希望 Dialog 渲染的 dom 和頂層組件是兄弟節(jié)點(diǎn)關(guān)系, 在index.html文件中定義一個(gè)供掛載的元素:
<body>
<div id="app"></div>
<div id="dialog"></div>
</body>
定義一個(gè)Dialog組件Dialog.vue, 留意 to 屬性默怨, 與上面的id選擇器一致:
<template>
<teleport to="#dialog">
<div class="dialog">
<div class="dialog_wrapper">
<div class="dialog_header" v-if="title">
<slot name="header">
<span>{{ title }}</span>
</slot>
</div>
</div>
<div class="dialog_content">
<slot></slot>
</div>
<div class="dialog_footer">
<slot name="footer"></slot>
</div>
</div>
</teleport>
</template>
最后在一個(gè)子組件Header.vue中使用Dialog組件
<div class="header">
...
<navbar />
<Dialog v-if="dialogVisible"></Dialog>
</div>
[圖片上傳失敗...(image-8ff413-1660454836197)]
可以看到讯榕,我們使用 teleport 組件,通過 to 屬性匙睹,指定該組件渲染的位置與 <div id="app"></div>
同級愚屁,也就是在 body 下,但是 Dialog 的狀態(tài) dialogVisible 又是完全由內(nèi)部 Vue 組件控制.
Suspense
Suspense是 Vue3.x 中新增的特性痕檬, 那它有什么用呢霎槐?我們通過 Vue2.x 中的一些場景來認(rèn)識它的作用。 Vue2.x 中應(yīng)該經(jīng)常遇到這樣的場景:
<template>
<div>
<div v-if="!loading">
...
</div>
<div v-if="loading">
加載中...
</div>
</div>
</template>
在前后端交互獲取數(shù)據(jù)時(shí)梦谜, 是一個(gè)異步過程丘跌,一般我們都會(huì)提供一個(gè)加載中的動(dòng)畫袭景,當(dāng)數(shù)據(jù)返回時(shí)配合v-if來控制數(shù)據(jù)顯示。
如果你使用過vue-async-manager
這個(gè)插件來完成上面的需求闭树, 你對Suspense
可能不會(huì)陌生耸棒,Vue3.x 感覺就是參考了vue-async-manager
.
Vue3.x 新出的內(nèi)置組件Suspense
, 它提供兩個(gè)template
slot, 剛開始會(huì)渲染一個(gè) fallback
狀態(tài)下的內(nèi)容, 直到到達(dá)某個(gè)條件后才會(huì)渲染 default
狀態(tài)的正式內(nèi)容报辱, 通過使用Suspense
組件進(jìn)行展示異步渲染就更加的簡單与殃。注意如果使用 Suspense
, 要返回一個(gè) promise
使用:
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
asyncComponent.vue:
<template>
<div>
<h4>這個(gè)是一個(gè)異步加載數(shù)據(jù)</h4>
<p>用戶名:{{user.nickname}}</p>
<p>年齡:{{user.age}}</p>
</div>
</template>
<script>
import { defineComponent } from "vue"
import axios from "axios"
export default defineComponent({
setup(){
const rawData = await axios.get("http://xxx.xinp.cn/user")
return {
user: rawData.data
}
}
})
</script>
從上面代碼來看,Suspense
只是一個(gè)帶插槽的組件捏肢,只是它的插槽指定了default
和 fallback
兩種狀態(tài)奈籽。
片段(Fragment)
在 Vue2.x 中, template中只允許有一個(gè)根節(jié)點(diǎn):
<template>
<div>
<span></span>
<span></span>
</div>
</template>
但是在 Vue3.x 中鸵赫,你可以直接寫多個(gè)根節(jié)點(diǎn)
<template>
<span></span>
<span></span>
</template>