一、Composition API概述
當(dāng)我們編寫(xiě)Vue組件時(shí)候,組件中可能包含一系列的功能,例如一個(gè)代碼倉(cāng)庫(kù)管理的應(yīng)用,用戶(hù)的倉(cāng)庫(kù)列表可以看做是一個(gè)組件,這個(gè)組件還包含篩選、搜索的功能。
所謂的功能我們可以理解為MVC中的Model和Controller。從視圖角度,組件是最基本的代碼復(fù)用單元,但是從邏輯上,功能模塊是最基本的代碼復(fù)用單元。
每個(gè)組件中可能包含多個(gè)功能(也稱(chēng)為關(guān)注點(diǎn)),而多個(gè)功能的代碼會(huì)分散在Vue組件的各個(gè)部分:data/props/watch/computed/dom event callback/lifescycle。
這會(huì)帶來(lái)兩個(gè)問(wèn)題:
- 可讀性和可維護(hù)性差,比如我們想要閱讀某個(gè)功能的邏輯,需要翻遍整個(gè)組件,到處查看相應(yīng)的邏輯;當(dāng)需要改動(dòng)某個(gè)功能時(shí)候,需要在很多地方進(jìn)行改動(dòng)。
- Vue未提供功能模塊級(jí)別的代碼復(fù)用支持,只提供組件級(jí)別的復(fù)用的話(huà),在大型項(xiàng)目中是不夠的。
為了解決上面兩個(gè)問(wèn)題,Vue需要提供功能模塊級(jí)別的代碼組織方式,即需要將同一個(gè)功能模塊的代碼寫(xiě)在同一片區(qū)域,而不是分散到組件的各個(gè)API中,另外還需要支持功能模塊的封裝,以便我們可以提取功能模塊的代碼進(jìn)行復(fù)用。
如何實(shí)現(xiàn)上述兩個(gè)能力呢?我們看看一個(gè)功能模塊都包含哪些內(nèi)容。
- 數(shù)據(jù)
- 數(shù)據(jù)的計(jì)算
- 數(shù)據(jù)的更新
- 數(shù)據(jù)監(jiān)聽(tīng)
其中數(shù)據(jù)部分的代碼寫(xiě)在Vue組件中的data、props中
數(shù)據(jù)計(jì)算部分的代碼寫(xiě)在Vue組件中的computed和處理數(shù)據(jù)的方法中
數(shù)據(jù)更新部分代碼寫(xiě)在生命周期鉤子方法和交互事件中
數(shù)據(jù)監(jiān)聽(tīng)代碼寫(xiě)在watch中
因此我們希望Vue能提供一個(gè)API,讓我們把一個(gè)功能模塊(即關(guān)注點(diǎn))的這些代碼(包括data/props/computed/watch/dom event callback/lifecycle)都寫(xiě)在一起。并且能將其提取。
組合式API就提供了這樣的能力。
總之,組合式API是在Vue3中新增加的API,它有兩點(diǎn)優(yōu)勢(shì):
- 讓功能模塊代碼(關(guān)注點(diǎn))聚合在一起,使可讀性和可維護(hù)性更高。
- 提供了一種功能模塊級(jí)別的復(fù)用代碼的方式。
二、Composition API使用
前端面試刷題網(wǎng)站:靈題庫(kù),收集大廠(chǎng)面試真題,相關(guān)知識(shí)點(diǎn)詳細(xì)解析。
開(kāi)發(fā)者如何使用組合式API呢?簡(jiǎn)單地說(shuō),需要在Vue組件里實(shí)現(xiàn)setup方法,并在方法中返回一些方法和屬性,返回的所有內(nèi)容都將暴露給組件的其余部分 (計(jì)算屬性、方法、生命周期鉤子等等) 以及組件的模板。
首先看一個(gè)簡(jiǎn)單的例子,下面是一個(gè)單文件組件
<template>
<div id="app">
<span class="counter">
{{counter}}
<button @click="increase">+1</button>
</span>
<div class="msg-panel">
<span v-if="showMsg">
hello, world
</span>
<button @click="toggleShowCondition">{{showWhenOdd ? '奇數(shù)展示' : '偶數(shù)展示'}}</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0,
showWhenOdd: true
};
},
computed: {
showMsg() {
return this.counter % 2 === 0
? this.showWhenOdd
: !this.showWhenOdd;
}
},
mounted() {
this.counter = Math.floor(Math.random() * 9) + 1;
},
methods: {
increase() {
this.counter++;
},
toggleShowCondition() {
this.showWhenOdd = !this.showWhenOdd;
}
}
};
</script>
<style scoped>
#app {
text-align: center;
margin-top: 60px;
}
.counter {
width: 100px;
height: 100px;
border: 1px solid;
padding: 10px;
}
.msg-panel {
width: 100px;
height: 100px;
margin: 50px auto;
border: 1px solid;
padding: 10px;
}
</style>
效果如下:

這個(gè)組件有兩個(gè)功能
- 展示一個(gè)計(jì)數(shù)器,計(jì)數(shù)器初始化計(jì)數(shù)是1-10之間的一個(gè)隨機(jī)數(shù),每次點(diǎn)擊"+1"按鈕會(huì)讓計(jì)數(shù)加1,計(jì)數(shù)實(shí)時(shí)展示在界面上
- 信息展示,僅當(dāng)計(jì)數(shù)為偶數(shù)時(shí)候,才會(huì)展示"hello, world"信息。也可以通過(guò)點(diǎn)擊"奇數(shù)展示"按鈕來(lái)控制 僅當(dāng)計(jì)數(shù)為奇數(shù)時(shí)候展示信息。
理解了上面的功能的實(shí)現(xiàn)之后我們?cè)賮?lái)看下使用組合式API如何實(shí)現(xiàn)相同的功能。
<template>
<div id="app">
<span class="counter">
{{counter}}
<button @click="increase">+1</button>
</span>
<div class="msg-panel">
<span v-if="showMsg">
hello, world
</span>
<button @click="toggleShowCondition">{{showWhenOdd ? '奇數(shù)展示' : '偶數(shù)展示'}}</button>
</div>
</div>
</template>
<script>
import {ref, onMounted, computed} from 'vue';
export default {
setup() {
// counter邏輯
const counter = ref(0);
const increase = () => {
counter.value++;
};
onMounted(() => {
counter.value = Math.floor(Math.random() * 9) + 1;
});
// 提示信息邏輯
const showWhenOdd = ref(true);
const showMsg = computed(() => {
return counter.value % 2 === 0
? showWhenOdd.value
: !showWhenOdd.value;
});
const toggleShowCondition = () => {
showWhenOdd.value = !showWhenOdd.value;
};
return {
counter,
increase,
showWhenOdd,
showMsg,
toggleShowCondition
};
}
};
</script>
<style scoped>
#app {
text-align: center;
margin-top: 60px;
}
.counter {
width: 100px;
height: 100px;
border: 1px solid;
padding: 10px;
}
.msg-panel {
width: 100px;
height: 100px;
margin: 50px auto;
border: 1px solid;
padding: 10px;
}
</style>
上面使用了組合式API的示例實(shí)現(xiàn)了相同的功能,注意:
- 之前的各種Vue組件的屬性 data/methods/computed/mounted等被setup方法代替,setup方法返回的值里面包括模板需要用到的數(shù)據(jù)和方法。
- 之前組件的data里面的數(shù)據(jù)(counter、showWhenOdd)用ref(<初始值>)代替,數(shù)據(jù)使用時(shí)候,需要通過(guò).value取值。
- 之前寫(xiě)在methods中的方法都在setup方法里面實(shí)現(xiàn)并返回。
- 之前寫(xiě)在computed中的計(jì)算屬性通過(guò)vue中的computed實(shí)現(xiàn),computed接受一個(gè)函數(shù),函數(shù)返回計(jì)算結(jié)果。
- 之前生命周期鉤子mounted被setup中通過(guò)onMounted包裹的函數(shù)來(lái)代替。
對(duì)比兩段代碼我們可以看出,使用setup API的實(shí)現(xiàn)明顯可以將兩段功能邏輯(counter和展示提示信息)各自聚合在一起

這樣就實(shí)現(xiàn)了我們之前提到的“讓功能模塊代碼(關(guān)注點(diǎn))聚合在一起,使可讀性和可維護(hù)性更高”的效果,如果我們還希望實(shí)現(xiàn)“提供了一種功能模塊級(jí)別的復(fù)用代碼的方式”,我們可以這樣組織代碼:
<template>
<div id="app">
<span class="counter">
{{counter}}
<button @click="increase">+1</button>
</span>
<div class="msg-panel">
<span v-if="showMsg">
hello, world
</span>
<button @click="toggleShowCondition">{{showWhenOdd ? '奇數(shù)展示' : '偶數(shù)展示'}}</button>
</div>
</div>
</template>
<script>
import {ref, onMounted, computed} from 'vue';
const useCounter = () => {
// counter邏輯
const counter = ref(0);
const increase = () => {
counter.value++;
};
onMounted(() => {
counter.value = Math.floor(Math.random() * 9) + 1;
});
return {
counter,
increase
};
};
const useMsg = counter => {
const showWhenOdd = ref(true);
const showMsg = computed(() => {
return counter.value % 2 === 0
? showWhenOdd.value
: !showWhenOdd.value;
});
const toggleShowCondition = () => {
showWhenOdd.value = !showWhenOdd.value;
};
return {
showWhenOdd,
showMsg,
toggleShowCondition
};
};
export default {
setup() {
const {counter, increase} = useCounter();
const {showWhenOdd, showMsg, toggleShowCondition} = useMsg(counter);
return {
counter,
increase,
showWhenOdd,
showMsg,
toggleShowCondition
};
}
};
</script>
<style scoped>
#app {
text-align: center;
margin-top: 60px;
}
.counter {
width: 100px;
height: 100px;
border: 1px solid;
padding: 10px;
}
.msg-panel {
width: 100px;
height: 100px;
margin: 50px auto;
border: 1px solid;
padding: 10px;
}
</style>
通過(guò)上面示例可以看到,我們將不同的功能模塊封裝在函數(shù)中,并將關(guān)鍵數(shù)據(jù)和方法返回,然后在Vue組件的setup方法中引用并返回,就可以在模板中使用了。
除了上述的幾個(gè)關(guān)鍵API:setup/ref/computed/onMounted,還有toRef用于解構(gòu)props中的屬性,watch用于監(jiān)聽(tīng)數(shù)據(jù),代替Vue組件中的watch屬性。可以在官方文檔查看用法。