Vue3.0的新特性有哪些呢,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
尤大大在B站直播時分享了Vue3.0的幾個亮點:
Performance:性能優化
Tree-shaking support:支持搖樹優化
Composition API:組合API
Fragment,Teleport,Suspense:新增的組件
Better TypeScript support:更好的TypeScript支持
Custom Renderer API:自定義渲染器
在性能方面,對比Vue2.x,性能提升了1.3~2倍左右;打包后的體積也更小了,如果單單寫一個HelloWorld進行打包,只有13.5kb;加上所有運行時特性,也不過22.5kb。
那么作為終端用戶的我們,在開發時,和Vue2.x有什么不同呢?Talk is cheap,我們還是來看代碼。
Vue3最重要的變化之一就是引入了Tree-Shaking,Tree-Shaking帶來的bundle體積更小是顯而易見的。在2.x版本中,很多函數都掛載在全局Vue對象上,比如nextTick、nextTick、nextTick、set等函數,因此雖然我們可能用不到,但打包時只要引入了vue這些全局函數仍然會打包進bundle中。
而在Vue3中,所有的API都通過ES6模塊化的方式引入,這樣就能讓webpack或rollup等打包工具在打包時對沒有用到API進行剔除,最小化bundle體積;我們在main.js中就能發現這樣的變化:
//src/main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router).mount("#app");創建app實例方式從原來的new Vue()變為通過createApp函數進行創建;不過一些核心的功能比如virtualDOM更新算法和響應式系統無論如何都是會被打包的;這樣帶來的變化就是以前在全局配置的組件(Vue.component)、指令(Vue.directive)、混入(Vue.mixin)和插件(Vue.use)等變為直接掛載在實例上的方法;我們通過創建的實例來調用,帶來的好處就是一個應用可以有多個Vue實例,不同實例之間的配置也不會相互影響:
const app = createApp(App) app.use(/* ... */) app.mixin(/* ... */) app.component(/* ... */) app.directive(/* ... */)
因此Vue2.x的以下全局API也需要改為ES6模塊化引入:
Vue.nextTick
Vue.observable不再支持,改為reactive
Vue.version
Vue.compile (僅全構建)
Vue.set (僅兼容構建)
Vue.delete (僅兼容構建)
除此之外,vuex和vue-router也都使用了Tree-Shaking進行了改進,不過api的語法改動不大:
//src/store/index.js
import { createStore } from "vuex";
export default createStore({
state: {},
mutations: {},
actions: {},
modules: {},
});
//src/router/index.js
import { createRouter, createWebHistory } from "vue-router";
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});我們都知道,在Vue2.x中有8個生命周期函數:
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed
在vue3中,新增了一個setup生命周期函數,setup執行的時機是在beforeCreate生命函數之前執行,因此在這個函數中是不能通過this來獲取實例的;同時為了命名的統一,將beforeDestroy改名為beforeUnmount,destroyed改名為unmounted,因此vue3有以下生命周期函數:
beforeCreate(建議使用setup代替)
created(建議使用setup代替)
setup
beforeMount
mounted
beforeUpdate
updated
beforeUnmount
unmounted
同時,vue3新增了生命周期鉤子,我們可以通過在生命周期函數前加on來訪問組件的生命周期,我們可以使用以下生命周期鉤子:
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
onErrorCaptured
onRenderTracked
onRenderTriggered
那么這些鉤子函數如何來進行調用呢?我們在setup中掛載生命周期鉤子,當執行到對應的生命周期時,就調用對應的鉤子函數:
import { onBeforeMount, onMounted } from "vue";
export default {
setup() {
console.log("----setup----");
onBeforeMount(() => {
// beforeMount代碼執行
});
onMounted(() => {
// mounted代碼執行
});
},
}說完生命周期,下面就是我們期待的Vue3新增加的那些功能。
我們可以使用reactive來為JS對象創建響應式狀態:
import { reactive, toRefs } from "vue";
const user = reactive({
name: 'Vue2',
age: 18,
});
user.name = 'Vue3'reactive相當于Vue2.x中的Vue.observable。
reactive函數只接收object和array等復雜數據類型。
對于一些基本數據類型,比如字符串和數值等,我們想要讓它變成響應式,我們當然也可以通過reactive函數創建對象的方式,但是Vue3提供了另一個函數ref:
import { ref } from "vue";
const num = ref(0);
const str = ref("");
const male = ref(true);
num.value++;
console.log(num.value);
str.value = "new val";
console.log(str.value);
male.value = false;
console.log(male.value);ref返回的響應式對象是只包含一個名為value參數的RefImpl對象,在js中獲取和修改都是通過它的value屬性;但是在模板中被渲染時,自動展開內部的值,因此不需要在模板中追加.value。
<template>
<p>
<span>{{ count }}</span>
<button @click="count ++">Increment count</button>
</p>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count
}
}
}
</script>reactive主要負責復雜數據結構,而ref主要處理基本數據結構;但是很多童鞋就會誤解ref只能處理基本數據,ref本身也是能處理對象和數組的:
import { ref } from "vue";
const obj = ref({
name: "qwe",
age: 1,
});
setTimeout(() => {
obj.value.name = "asd";
}, 1000);
const list = ref([1, 2, 3, 4, 6]);
setTimeout(() => {
list.value.push(7);
}, 2000);當我們處理一些大型響應式對象的property時,我們很希望使用ES6的解構來獲取我們想要的值:
let book = reactive({
name: 'Learn Vue',
year: 2020,
title: 'Chapter one'
})
let {
name,
} = book
name = 'new Learn'
// Learn Vue
console.log(book.name);但是很遺憾,這樣會消除它的響應式;對于這種情況,我們可以將響應式對象轉換為一組ref,這些ref將保留與源對象的響應式關聯:
let book = reactive({
name: 'Learn Vue',
year: 2020,
title: 'Chapter one'
})
let {
name,
} = toRefs(book)
// 注意這里解構出來的name是ref對象
// 需要通過value來取值賦值
name.value = 'new Learn'
// new Learn
console.log(book.name);對于一些只讀數據,我們希望防止它發生任何改變,可以通過readonly來創建一個只讀的對象:
import { reactive, readonly } from "vue";
let book = reactive({
name: 'Learn Vue',
year: 2020,
title: 'Chapter one'
})
const copy = readonly(book);
//Set operation on key "name" failed: target is readonly.
copy.name = "new copy";有時我們需要的值依賴于其他值的狀態,在vue2.x中我們使用computed函數來進行計算屬性,在vue3中將computed功能進行了抽離,它接受一個getter函數,并為getter返回的值創建了一個不可變的響應式ref對象:
const num = ref(0); const double = computed(() => num.value * 2); num.value++; // 2 console.log(double.value); // Warning: computed value is readonly double.value = 4
或者我們也可以使用get和set函數創建一個可讀寫的ref對象:
const num = ref(0);
const double = computed({
get: () => num.value * 2,
set: (val) => (num.value = val / 2),
});
num.value++;
// 2
console.log(double.value);
double.value = 8
// 4
console.log(num.value);和computed相對應的就是watch,computed是多對一的關系,而watch則是一對多的關系;vue3也提供了兩個函數來偵聽數據源的變化:watch和watchEffect。
我們先來看下watch,它的用法和組件的watch選項用法完全相同,它需要監聽某個數據源,然后執行具體的回調函數,我們首先看下它監聽單個數據源的用法:
import { reactive, ref, watch } from "vue";
const state = reactive({
count: 0,
});
//偵聽時返回值得getter函數
watch(
() => state.count,
(count, prevCount) => {
// 1 0
console.log(count, prevCount);
}
);
state.count++;
const count = ref(0);
//直接偵聽ref
watch(count, (count, prevCount) => {
// 2 0
console.log(count, prevCount, "watch");
});
count.value = 2;我們也可以把多個值放在一個數組中進行偵聽,最后的值也以數組形式返回:
const state = reactive({
count: 1,
});
const count = ref(2);
watch([() => state.count, count], (newVal, oldVal) => {
//[3, 2] [1, 2]
//[3, 4] [3, 2]
console.log(newVal, oldVal);
});
state.count = 3;
count.value = 4;如果我們來偵聽一個深度嵌套的對象屬性變化時,需要設置deep:true:
const deepObj = reactive({
a: {
b: {
c: "hello",
},
},
});
watch(
() => deepObj,
(val, old) => {
// new hello new hello
console.log(val.a.b.c, old.a.b.c);
},
{ deep: true }
);
deepObj.a.b.c = "new hello";最后的打印結果可以發現都是改變后的值,這是因為偵聽一個響應式對象始終返回該對象的引用,因此我們需要對值進行深拷貝:
import _ from "lodash";
const deepObj = reactive({
a: {
b: {
c: "hello",
},
},
});
watch(
() => _.cloneDeep(deepObj),
(val, old) => {
// new hello hello
console.log(val.a.b.c, old.a.b.c);
},
{ deep: true }
);
deepObj.a.b.c = "new hello";一般偵聽都會在組件銷毀時自動停止,但是有時候我們想在組件銷毀前手動的方式進行停止,可以調用watch返回的stop函數進行停止:
const count = ref(0);
const stop = watch(count, (count, prevCount) => {
// 不執行
console.log(count, prevCount);
});
setTimeout(()=>{
count.value = 2;
}, 1000);
// 停止watch
stop();還有一個函數watchEffect也可以用來進行偵聽,但是都已經有watch了,這個watchEffect和watch有什么區別呢?他們的用法主要有以下幾點不同:
watchEffect不需要手動傳入依賴
每次初始化時watchEffect都會執行一次回調函數來自動獲取依賴
watchEffect無法獲取到原值,只能得到變化后的值
import { reactive, ref, watch, watchEffect } from "vue";
const count = ref(0);
const state = reactive({
year: 2021,
});
watchEffect(() => {
console.log(count.value);
console.log(state.year);
});
setInterval(() => {
count.value++;
state.year++;
}, 1000);watchEffect會在頁面加載時自動執行一次,追蹤響應式依賴;在加載后定時器每隔1s執行時,watchEffect都會監聽到數據的變化自動執行,每次執行都是獲取到變化后的值。
Composition API(組合API)也是Vue3中最重要的一個功能了,之前的2.x版本采用的是Options API(選項API),即官方定義好了寫法:data、computed、methods,需要在哪里寫就在哪里寫,這樣帶來的問題就是隨著功能增加,代碼也越來復雜,我們看代碼需要上下反復橫跳:

Composition API對比
上圖中,一種顏色代表一個功能,我們可以看到
Options API的功能代碼比較分散;Composition API則可以將同一個功能的邏輯,組織在一個函數內部,利于維護。
我們首先來看下之前Options API的寫法:
export default {
components: {},
data() {},
computed: {},
watch: {},
mounted() {},
}Options API就是將同一類型的東西放在同一個選項中,當我們的數據比較少的時候,這樣的組織方式是比較清晰的;但是隨著數據增多,我們維護的功能點會涉及到多個data和methods,但是我們無法感知哪些data和methods是需要涉及到的,經常需要來回切換查找,甚至是需要理解其他功能的邏輯,這也導致了組件難以理解和閱讀。
而Composition API做的就是把同一功能的代碼放到一起維護,這樣我們需要維護一個功能點的時候,不用去關心其他的邏輯,只關注當前的功能;Composition API通過setup選項來組織代碼:
export default {
setup(props, context) {}
};我們看到這里它接收了兩個參數props和context,props就是父組件傳入的一些數據,context是一個上下文對象,是從2.x暴露出來的一些屬性:
attrs
slots
emit
注:props的數據也需要通過toRefs解構,否則響應式數據會失效。
我們通過一個Button按鈕來看下setup具體的用法:

舉個栗子
<template>
<p>{{ state.count }} * 2 = {{ double }}</p>
<p>{{ num }}</p>
<p @click="add">Add</p>
</template>
<script>
import { reactive, computed, ref } from "vue";
export default {
name: "Button",
setup() {
const state = reactive({
count: 1,
});
const num = ref(2);
function add() {
state.count++;
num.value += 10;
}
const double = computed(() => state.count * 2);
return {
state,
double,
num,
add,
};
},
};
</script>很多童鞋可能就有疑惑了,這跟我在data和methods中寫沒什么區別么,不就是把他們放到一起么?我們可以將setup中的功能進行提取分割成一個一個獨立函數,每個函數還可以在不同的組件中進行邏輯復用:
export default {
setup() {
const { networkState } = useNetworkState();
const { user } = userDeatil();
const { list } = tableData();
return {
networkState,
user,
list,
};
},
};
function useNetworkState() {}
function userDeatil() {}
function tableData() {}所謂的Fragment,就是片段;在vue2.x中,要求每個模板必須有一個根節點,所以我們代碼要這樣寫:
<template> <p> <span></span> <span></span> </p> </template>
或者在Vue2.x中還可以引入vue-fragments庫,用一個虛擬的fragment代替p;在React中,解決方法是通過的一個React.Fragment標簽創建一個虛擬元素;在Vue3中我們可以直接不需要根節點:
<template> <span>hello</span> <span>world</span> </template>
這樣就少了很多沒有意義的p元素。
Teleport翻譯過來就是傳送、遠距離傳送的意思;顧名思義,它可以將插槽中的元素或者組件傳送到頁面的其他位置:

傳送門游戲
在React中可以通過createPortal函數來創建需要傳送的節點;本來尤大大想起名叫Portal,但是H5原生的Portal標簽也在計劃中,雖然有一些安全問題,但是為了避免重名,因此改成Teleport。
Teleport一個常見的使用場景,就是在一些嵌套比較深的組件來轉移模態框的位置。雖然在邏輯上模態框是屬于該組件的,但是在樣式和DOM結構上,嵌套層級后較深后不利于進行維護(z-index等問題);因此我們需要將其進行剝離出來:
<template>
<button @click="showDialog = true">打開模態框</button>
<teleport to="body">
<p class="modal" v-if="showDialog" style="position: fixed">
我是一個模態框
<button @click="showDialog = false">關閉</button>
<child-component :msg="msg"></child-component>
</p>
</teleport>
</template>
<script>
export default {
data() {
return {
showDialog: false,
msg: "hello"
};
},
};
</script>這里的Teleport中的modal p就被傳送到了body的底部;雖然在不同的地方進行渲染,但是Teleport中的元素和組件還是屬于父組件的邏輯子組件,還是可以和父組件進行數據通信。Teleport接收兩個參數to和disabled:
to - string:必須是有效的查詢選擇器或 HTMLElement,可以id或者class選擇器等。
disabled - boolean:如果是true表示禁用teleport的功能,其插槽內容將不會移動到任何位置,默認false不禁用。
Suspense是Vue3推出的一個內置組件,它允許我們的程序在等待異步組件時渲染一些后備的內容,可以讓我們創建一個平滑的用戶體驗;Vue中加載異步組件其實在Vue2.x中已經有了,我們用的vue-router中加載的路由組件其實也是一個異步組件:
export default {
name: "Home",
components: {
AsyncButton: () => import("../components/AsyncButton"),
},
}在Vue3中重新定義,異步組件需要通過defineAsyncComponent來進行顯示的定義:
// 全局定義異步組件
//src/main.js
import { defineAsyncComponent } from "vue";
const AsyncButton = defineAsyncComponent(() =>
import("./components/AsyncButton.vue")
);
app.component("AsyncButton", AsyncButton);
// 組件內定義異步組件
// src/views/Home.vue
import { defineAsyncComponent } from "vue";
export default {
components: {
AsyncButton: defineAsyncComponent(() =>
import("../components/AsyncButton")
),
},
};同時對異步組件的可以進行更精細的管理:
export default {
components: {
AsyncButton: defineAsyncComponent({
delay: 100,
timeout: 3000,
loader: () => import("../components/AsyncButton"),
errorComponent: ErrorComponent,
onError(error, retry, fail, attempts) {
if (attempts <= 3) {
retry();
} else {
fail();
}
},
}),
},
};這樣我們對異步組件加載情況就能掌控,在加載失敗也能重新加載或者展示異常的狀態:

異步組件加載失敗
我們回到Suspense,上面說到它主要是在組件加載時渲染一些后備的內容,它提供了兩個slot插槽,一個default默認,一個fallback加載中的狀態:
<template>
<p>
<button @click="showButton">展示異步組件</button>
<template v-if="isShowButton">
<Suspense>
<template #default>
<AsyncButton></AsyncButton>
</template>
<template #fallback>
<p>組件加載中...</p>
</template>
</Suspense>
</template>
</p>
</template>
<script>
export default {
setup() {
const isShowButton = ref(false);
function showButton() {
isShowButton.value = true;
}
return {
isShowButton,
showButton,
};
},
}
</script>
異步組件加載顯示占位
非兼容的功能主要是一些和Vue2.x版本改動較大的語法,已經在Vue3上可能存在兼容問題了。
在Vue2.x中,我們可以定義data為object或者function,但是我們知道在組件中如果data是object的話會出現數據互相影響,因為object是引用數據類型;
在Vue3中,data只接受function類型,通過function返回對象;同時Mixin的合并行為也發生了改變,當mixin和基類中data合并時,會執行淺拷貝合并:
const Mixin = {
data() {
return {
user: {
name: 'Jack',
id: 1,
address: {
prov: 2,
city: 3,
},
}
}
}
}
const Component = {
mixins: [Mixin],
data() {
return {
user: {
id: 2,
address: {
prov: 4,
},
}
}
}
}
// vue2結果:
{
id: 2,
name: 'Jack',
address: {
prov: 4,
city: 3
}
}
// vue3結果:
user: {
id: 2,
address: {
prov: 4,
},
}我們看到最后合并的結果,vue2.x會進行深拷貝,對data中的數據向下深入合并拷貝;而vue3只進行淺層拷貝,對data中數據發現已存在就不合并拷貝。
在vue2.x中,我們還可以通過過濾器filter來處理一些文本內容的展示:
<template>
<p>{{ status | statusText }}</p>
</template>
<script>
export default {
props: {
status: {
type: Number,
default: 1
}
},
filters: {
statusText(value){
if(value === 1){
return '訂單未下單'
} else if(value === 2){
return '訂單待支付'
} else if(value === 3){
return '訂單已完成'
}
}
}
}
</script>最常見的就是處理一些訂單的文案展示等;然而在vue3中,過濾器filter已經刪除,不再支持了,官方建議使用方法調用或者計算屬性computed來進行代替。
在Vue2.x中,v-model相當于綁定value屬性和input事件,它本質也是一個語法糖:
<child-component v-model="msg"></child-component> <!-- 相當于 --> <child-component :value="msg" @input="msg=$event"></child-component>
在某些情況下我們需要對多個值進行雙向綁定,其他的值就需要顯示的使用回調函數來改變了:
<child-component v-model="msg" :msg1="msg1" @change1="msg1=$event" :msg2="msg2" @change2="msg2=$event"> </child-component>
在vue2.3.0+版本引入了.sync修飾符,其本質也是語法糖,是在組件上綁定@update:propName回調,語法更簡潔:
<child-component :msg1.sync="msg1" :msg2.sync="msg2"> </child-component> <!-- 相當于 --> <child-component :msg1="msg1" @update:msg1="msg1=$event" :msg2="msg2" @update:msg2="msg2=$event"> </child-component>
Vue3中將v-model和.sync進行了功能的整合,拋棄了.sync,表示:多個雙向綁定value值直接用多個v-model傳就好了;同時也將v-model默認傳的prop名稱由value改成了modelValue:
<child-component v-model="msg"> </child-component> <!-- 相當于 --> <child-component :modelValue="msg" @update:modelValue="msg = $event"> </child-component>
如果我們想通過v-model傳遞多個值,可以將一個argument傳遞給v-model:
<child-component v-model.msg1="msg1" v-model.msg2="msg2"> </child-component> <!-- 相當于 --> <child-component :msg1="msg1" @update:msg1="msg1=$event" :msg2="msg2" @update:msg2="msg2=$event"> </child-component>
在Vue2.x中,我們都知道v-for每次循環都需要給每個子節點一個唯一的key,還不能綁定在template標簽上,
<template v-for="item in list"> <p :key="item.id">...</p> <span :key="item.id">...</span> </template>
而在Vue3中,key值應該被放置在template標簽上,這樣我們就不用為每個子節點設一遍:
<template v-for="item in list" :key="item.id"> <p>...</p> <span>...</span> </template>
在vue2.x中,如果一個元素同時定義了v-bind="object"和一個相同的單獨的屬性,那么這個單獨的屬性會覆蓋object中的綁定:
<p id="red" v-bind="{ id: 'blue' }"></p>
<p v-bind="{ id: 'blue' }" id="red"></p>
<!-- 最后結果都相同 -->
<p id="red"></p>然而在vue3中,如果一個元素同時定義了v-bind="object"和一個相同的單獨的屬性,那么聲明綁定的順序決定了最后的結果(后者覆蓋前者):
<!-- template -->
<p id="red" v-bind="{ id: 'blue' }"></p>
<!-- result -->
<p id="blue"></p>
<!-- template -->
<p v-bind="{ id: 'blue' }" id="red"></p>
<!-- result -->
<p id="red"></p>vue2.x中,在v-for上使用ref屬性,通過this.$refs會得到一個數組:
<template
<p v-for="item in list" :ref="setItemRef"></p>
</template>
<script>
export default {
data(){
list: [1, 2]
},
mounted () {
// [p, p]
console.log(this.$refs.setItemRef)
}
}
</script>但是這樣可能不是我們想要的結果;因此vue3不再自動創建數組,而是將ref的處理方式變為了函數,該函數默認傳入該節點:
<template
<p v-for="item in 3" :ref="setItemRef"></p>
</template>
<script>
import { reactive, onUpdated } from 'vue'
export default {
setup() {
let itemRefs = reactive([])
const setItemRef = el => {
itemRefs.push(el)
}
onUpdated(() => {
console.log(itemRefs)
})
return {
itemRefs,
setItemRef
}
}
}
</script>在vue2.x中,在一個元素上同時使用v-for和v-if,v-for有更高的優先級,因此在vue2.x中做性能優化,有一個重要的點就是v-for和v-if不能放在同一個元素上。
而在vue3中,v-if比v-for有更高的優先級。因此下面的代碼,在vue2.x中能正常運行,但是在vue3中v-if生效時并沒有item變量,因此會報錯:
<template>
<p v-for="item in list" v-if="item % 2 === 0" :key="item">{{ item }}</p>
</template>
<script>
export default {
data() {
return {
list: [1, 2, 3, 4, 5],
};
},
};
</script>關于Vue3.0的新特性有哪些呢問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。