Vue系列(十三)-Composition API 一
這篇文章說一下Composition API,提到這就要說一下setup這個函數(shù)了,這個函數(shù)直接把vue的難度拉高了一截,更接近原生的js了,也更加靈活了。并且可以更好的兼容TypeScript,然后第二部分補充了Mixin,和CompositionAPI沒有聯(lián)系
一
Composition API? ? ? ? ? ? ? ? ? ? ? ? ? ??
1.1 Options API的弊端
了解Composition API前先了解一下Options API的弊端。在Vue2中,我們編寫組件的方式是Options API,包括前面章節(jié)用的也是Options API,Options API的一大特點就是在對應(yīng)的屬性中編寫對應(yīng)的功能模塊;?
比如data定義數(shù)據(jù)、methods中定義方法、computed中定義計算屬性、watch中監(jiān)聽屬性改變,也包括生命周期鉤子
但是這種代碼有一個很大的弊端:
當(dāng)我們實現(xiàn)某一個功能時,這個功能對應(yīng)的代碼邏輯會被拆分到各個屬性中;當(dāng)我們組件變得更大、更復(fù)雜時,邏輯關(guān)注點的列表就會增長,那么同一個功能的邏輯就會被拆分的很分散;
如果我們能將同一個邏輯關(guān)注點相關(guān)的代碼收集在一起會更好。
這就是Composition API想要做的事情,以及可以幫助我們完成的事情。
也有人把Vue CompositionAPI簡稱為VCA。
1.2認(rèn)識Composition API
那么既然知道Composition API想要幫助我們做什么事情,接下來看一下到底是怎么做呢?
為了開始使用Composition API,我們需要有一個可以實際使用它(編寫代碼)的地方;
在Vue組件中,這個位置就是setup函數(shù);?
setup其實就是組件的另外一個選項:
只不過這個選項強大到我們可以用它來替代之前所編寫的大部分其他選項;
比如methods、computed、watch、data、生命周期等等
1.3 setup
接下來說一下這個函數(shù)的使用:
函數(shù)的參數(shù)
函數(shù)的返回值
-
setup函數(shù)的參數(shù)
我們先來研究一個setup函數(shù)的參數(shù),它主要有兩個參數(shù):
第一個參數(shù):props
第二個參數(shù):context
<script>
?export default {
? ?props: {
? ? ?message: {
? ? ? ?type: String,
? ? ? ?required: true
? ? ?}
? ?},
? ?/**
? ? * 參數(shù)一: props, 父組件傳遞過來屬性
? ? * 參數(shù)二: context,上下文
? ? */
? ?setup(props, context) {
? ?},
?}
</script>
props非常好理解,它其實就是父組件傳遞過來的屬性會被放到props對象中,我們在setup中如果需要使用,那么就可以直接通過props參數(shù)獲取:
對于定義props的類型,我們還是和之前的規(guī)則是一樣的,在props選項中定義;并且在template中依然是可以正常去使用props中的屬性,比如message;
如果我們在setup函數(shù)中想要使用props,那么不可以通過this去獲取(后面我會講到為什么);?
因為props有直接作為參數(shù)傳遞到setup函數(shù)中,所以我們可以直接通過參數(shù)來使用即可;?
另外一個參數(shù)是context,我們也稱之為是一個SetupContext,它里面包含三個屬性:?
-
attrs:所有的非prop的attribute;
-
slots:父組件傳遞過來的插槽(這個在以渲染函數(shù)返回時會有作用,后面會講到);
-
emit:當(dāng)我們組件內(nèi)部需要發(fā)出事件時會用到emit(因為我們不能訪問this,所以不可以通過 this.$emit發(fā)出事件);
//可直接對第二個參數(shù)context進行解構(gòu)
setup(props, {attrs, slots, emit}) {
},
-
setup函數(shù)的返回值
setup既然是一個函數(shù),那么它也可以有返回值,它的返回值用來做什么呢?
setup的返回值可以在模板template中被使用;?
也就是說我們可以通過setup的返回值來替代data選項;
甚至是我們可以返回一個執(zhí)行函數(shù)來代替在methods中定義的方法:
var counter = 100;
// 局部函數(shù)
const increment = () => {
?counter++;
?console.log(counter);
}
const decrement = () => {
?counter--
?console.log(counter);
}
return {
?increment,
?decrement
}
但是,如果我們將 counter 在 increment 或者 decrement進行操作時,是否可以實現(xiàn)界面的響應(yīng)式呢?
答案是不可以;這是因為對于一個定義的變量來說,默認(rèn)情況下,Vue并不會跟蹤它的變化,來引起界面的響應(yīng)式操作;
注:setup不可以使用this
1.4Reactive API
如果想為在setup中定義的數(shù)據(jù)提供響應(yīng)式的特性,那么我們可以使用reactive的函數(shù):
const state = reactive({
?counter: 100
})
那么這是什么原因呢?為什么就可以變成響應(yīng)式的呢?
這是因為當(dāng)我們使用reactive函數(shù)處理我們的數(shù)據(jù)之后,數(shù)據(jù)再次被使用時就會進行依賴收集;?
當(dāng)數(shù)據(jù)發(fā)生改變時,所有收集到的依賴都是進行對應(yīng)的響應(yīng)式操作(比如更新界面);
事實上,我們編寫的data選項,也是在內(nèi)部交給了reactive函數(shù)將其編程響應(yīng)式對象的;
1.5Ref API
reactive API對傳入的類型是有限制的,它要求我們必須傳入的是一個對象或者數(shù)組類型:?
如果我們傳入一個基本數(shù)據(jù)類型(String、Number、Boolean)會報一個警告;

這個時候Vue3給我們提供了另外一個API:
ref API?
ref會返回一個可變的響應(yīng)式對象,該對象作為一個響應(yīng)式的引用維護著它內(nèi)部的值,這就是ref名稱的來源;
它內(nèi)部的值是在ref的 value 屬性中被維護的;
const msg = ref('hello vue');
這里有兩個注意事項:
在模板中引入ref的值時,Vue會自動幫助我們進行解包操作,所以我們并不需要在模板中通過 ref.value的方式來使用;
但是在setup 函數(shù)內(nèi)部,它依然是一個ref引用, 所以對其進行操作時,我們依然需要使用 ref.value的方式;
1.6了解readonly
我們通過reactive或者ref可以獲取到一個響應(yīng)式的對象,但是某些情況下,我們傳入給其他地方(組件)的這個響應(yīng)式對象希望在另外一個地方(組件)被使用,但是不能被修改,這個時候如何防止這種情況的出現(xiàn)呢?
Vue3為我們提供了readonly的方法;?
readonly會返回原生對象的只讀代理(也就是它依然是一個Proxy,這是一個proxy的set方法被劫持,并且不能對其進行修改);
在開發(fā)中常見的readonly方法會傳入三個類型的參數(shù):
-
類型一:普通對象;
-
類型二:reactive返回的對象;?
-
類型三:ref的對象;
在我們傳遞給其他組件數(shù)據(jù)時,希望其他組件使用我們傳遞的內(nèi)容,但是不允許它們修改時,就可以使用readonly了
1.7readonly的使用
在readonly的使用過程中,有如下規(guī)則:
readonly返回的對象都是不允許修改的;?
但是經(jīng)過readonly處理的原來的對象是允許被修改的;?
比如const info = readonly(obj),info對象是不允許被修改的;?當(dāng)obj被修改時,readonly返回的info對象也會被修改;?但是我們不能去修改readonly返回的對象info;
其實本質(zhì)上就是readonly返回的對象的setter方法被劫持了而已;
//普通對象
const info1 = {
name : 'pzh',
age : 18
}
const onlyReadInfo1 = readonly(info1)
//reactice對象
const info2 = reactive({
name : 'pzh',
age : 18
})
const onlyReadInfo2 = readonly(info2)
//ref對象
const info3 = ref('pzh')
const onlyReadInfo3 = readonly(info3)
1.8Reactive判斷的API
isProxy
檢查對象是否是由reactive或readonly創(chuàng)建的 proxy;
isReactive?
檢查對象是否是由reactive創(chuàng)建的響應(yīng)式代理:?如果該代理是readonly建的,但包裹了由 reactive 創(chuàng)建的另一個代理,它也會返回 true;
isReadonly?
檢查對象是否是由 readonly創(chuàng)建的只讀代理。
toRaw
返回reactive或readonly 代理的原始對象(不建議保留對原始對象的持久引用。請謹(jǐn)慎使用)?
shallowReactive
創(chuàng)建一個響應(yīng)式代理,它跟蹤其自身 property 的響應(yīng)性,但不執(zhí)行嵌套對象的深層響應(yīng)式轉(zhuǎn)換 (深層還是原生對象)。
shallowReadonly
創(chuàng)建一個 proxy,使其自身的 property 為只讀,但不執(zhí)行嵌套對象的深度只讀轉(zhuǎn)換(深層還是可讀、可寫的)。
1.9toRefs
如果我們使用ES6的解構(gòu)語法,對reactive返回的對象進行解構(gòu)獲取值,那么之后無論是修改結(jié)構(gòu)后的變量,還是修改reactive返回的state對象,數(shù)據(jù)都不再是響應(yīng)式的:
const state = reactive({
name : 'pzh',
age : 18
})
那么有沒有辦法讓我們解構(gòu)出來的屬性是響應(yīng)式的呢?
Vue為我們提供了一個toRefs的函數(shù),可以將reactive返回的對象中的屬性都轉(zhuǎn)成ref;
那么我們再次進行結(jié)構(gòu)出來的name和age本身都是ref的;
//這樣寫的話,會返回兩個ref對象,它們是響應(yīng)式的
const {name , age } = toRefs(state)
這種做法相當(dāng)于已經(jīng)在state.name和ref.value之間建立了鏈接,任何一個修改都會引起另外一個變化;
1.10toRef
如果我們只希望轉(zhuǎn)換一個reactive對象中的屬性為ref, 那么可以使用toRef的方法:
const name = toRef(state,'name')
二
Mixin? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
Mixin為單獨的內(nèi)容,與Composition API沒有聯(lián)系
2.1認(rèn)識Mixin
目前我們是使用組件化的方式在開發(fā)整個Vue的應(yīng)用程序,但是組件和組件之間有時候會存在相同的代碼邏輯,我們希望對相同的代碼邏輯進行抽取。
在Vue2和Vue3中都支持的一種方式就是使用Mixin來完成:
Mixin提供了一種非常靈活的方式,來分發(fā)Vue組件中的可復(fù)用功能;
一個Mixin對象可以包含任何組件選項;?
當(dāng)組件使用Mixin對象時,所有Mixin對象的選項將被混合進入該組件本身的選項中;
2.2Mixin的基本使用:

2.3Mixin的合并規(guī)則
如果Mixin對象中的選項和組件對象中的選項發(fā)生了沖突,那么Vue會如何操作呢?
這里分成不同的情況來進行處理;?
-
情況一:如果是data函數(shù)的返回值對象, 返回值對象默認(rèn)情況下會 進行合并 ; 如果data返回值對象的屬性 發(fā)生了沖突 ,那么會 保留組件自身的數(shù)據(jù) ;?
-
情況二:如何生命周期鉤子函數(shù), 生命周期的鉤子函數(shù)會被合并到數(shù)組中,都會被調(diào)用;?
-
情況三:值為對象的選項,例如 methods、components 和 directives,將被合并為同一個對象。? 比如都有methods選項,并且都定義了方法,那么它們都會生效;? 但是如果對象的key相同,那么會取組件對象的鍵值對;
2.4全局混入Mixin
如果組件中的某些選項,是所有的組件都需要擁有的 ,那么這個時候我們可以使用全局的mixin:?
全局的Mixin可以使用 應(yīng)用app的方法mixin來完成注冊;
一旦注冊,那么全局混入的選項將會影響每一個組件;
const app = createApp(App);
app.mixin({
?data() {
? ?return {}
?},
?methods: {
?},
?created() {
? ?console.log("全局的created生命周期");
?}
});
