本文整理來(lái)自深入Vue3+TypeScript技術(shù)棧-coderwhy大神新課,只作為個(gè)人筆記記錄使用,請(qǐng)大家多支持王紅元老師盾碗。
Composition API其實(shí)就是用來(lái)替代Mixin的税弃,我們先來(lái)學(xué)習(xí)一下Mixin降盹。
認(rèn)識(shí)Mixin
目前我們是使用組件化的方式在開(kāi)發(fā)整個(gè)Vue的應(yīng)用程序馅笙,但是組件和組件之間有時(shí)候會(huì)存在相同的代碼邏輯骏令,我們希望對(duì)相同的代碼邏輯進(jìn)行抽取蔬捷。
在Vue2和Vue3中都支持的一種方式就是使用Mixin來(lái)完成,Mixin提供了一種非常靈活的方式榔袋,來(lái)分發(fā)Vue組件中的可復(fù)用功能周拐,一個(gè)Mixin對(duì)象可以包含任何組件選項(xiàng),當(dāng)組件使用Mixin對(duì)象時(shí)凰兑,所有Mixin對(duì)象的選項(xiàng)將被混合進(jìn)入該組件本身的選項(xiàng)中妥粟。
Mixin的基本使用
組件中使用Mixin一般通過(guò)一個(gè)數(shù)組,因?yàn)榻M件中可能使用不止一個(gè)Mixin對(duì)象吏够。
Mixin的合并規(guī)則
如果Mixin對(duì)象中的選項(xiàng)和組件對(duì)象中的選項(xiàng)發(fā)生了沖突勾给,那么Vue會(huì)如何操作呢?這里分成不同的情況來(lái)進(jìn)行處理:
- 情況一:如果是data函數(shù)的返回值對(duì)象
返回值對(duì)象默認(rèn)情況下會(huì)進(jìn)行合并锅知,如果data返回值對(duì)象的屬性發(fā)生了沖突播急,那么會(huì)保留組件自身的數(shù)據(jù)。 - 情況二:生命周期鉤子函數(shù)
生命周期的鉤子函數(shù)會(huì)被合并到數(shù)組中喉镰,都會(huì)被調(diào)用旅择。 - 情況三:值為對(duì)象的選項(xiàng)
例如 methods惭笑、components 和 directives侣姆,將被合并為同一個(gè)對(duì)象,比如都有methods選項(xiàng)沉噩,并且都定義了方法捺宗,那么它們都會(huì)生效,但是如果對(duì)象的key相同川蒙,那么會(huì)取組件對(duì)象的鍵值對(duì)蚜厉。
全局混入Mixin
如果組件中的某些選項(xiàng),是所有的組件都需要擁有的畜眨,那么這個(gè)時(shí)候我們可以使用全局的mixin昼牛。全局的Mixin可以使用應(yīng)用app的方法 mixin 來(lái)完成注冊(cè)术瓮,一旦注冊(cè),那么全局混入的選項(xiàng)將會(huì)影響每一個(gè)組件贰健。使用全局mixin之后我們就不用在組件中一個(gè)一個(gè)寫(xiě)mixins: [sayHelloMixin]了胞四。
import { createApp } from 'vue';
import App from './01_mixin和extends/App.vue';
const app = createApp(App);
app.mixin({
data() {
return {}
},
methods: {
},
created() {
console.log("全局的created生命周期");
}
});
app.mount("#app");
extends
另外一個(gè)類似于Mixin的方式是通過(guò)extends屬性,extends是繼承的意思伶椿,繼承只會(huì)把組件的export default{}對(duì)象繼承過(guò)去辜伟,組件的html結(jié)構(gòu)不會(huì)繼承過(guò)去。
如下代碼脊另,左邊是BasePage.vue导狡,里面有個(gè)message屬性,在右邊組件中我們引入BasePage.vue偎痛,然后指定extends: BasePage旱捧,就可以使用message屬性了。
在實(shí)際開(kāi)發(fā)中ext ends用的非常少看彼,在Vue2中比較推薦大家使用Mixin廊佩,而在Vue3中推薦使用Composition API。
Options API的弊端
在Vue2中靖榕,我們編寫(xiě)組件的方式是Options API标锄,Options API的一大特點(diǎn)就是在對(duì)應(yīng)的屬性中編寫(xiě)對(duì)應(yīng)的功能模塊,比如data定義數(shù)據(jù)茁计、methods中定義方法料皇、computed中定義計(jì)算屬性、watch中監(jiān)聽(tīng)屬性改變星压,也包括生命周期鉤子践剂。但是這種代碼有一個(gè)很大的弊端:當(dāng)我們實(shí)現(xiàn)某一個(gè)功能時(shí),這個(gè)功能對(duì)應(yīng)的代碼邏輯會(huì)被拆分到各個(gè)屬性中娜膘。當(dāng)我們組件變得更大逊脯、更復(fù)雜時(shí),邏輯關(guān)注點(diǎn)的列表就會(huì)增長(zhǎng)竣贪,那么同一個(gè)功能的邏輯就會(huì)被拆分的很分散军洼,尤其對(duì)于那些一開(kāi)始沒(méi)有編寫(xiě)這些組件的人來(lái)說(shuō),這個(gè)組件的代碼是難以閱讀和理解的演怎。
下面我們來(lái)看一個(gè)非常大的組件匕争,其中的邏輯功能按照顏色進(jìn)行了劃分,這種碎片化的代碼使用理解和維護(hù)這個(gè)復(fù)雜的組件變得異常困難爷耀,并且隱藏了潛在的邏輯問(wèn)題甘桑,并且當(dāng)我們處理單個(gè)邏輯關(guān)注點(diǎn)時(shí),需要不斷的跳到相應(yīng)的代碼塊中。
大組件的邏輯分散
Options API:
Composition API:
如果我們能將同一個(gè)邏輯關(guān)注點(diǎn)相關(guān)的代碼收集在一起會(huì)更好跑杭,這就是Composition API想要做以及可以幫助我們完成的事情铆帽,所以也有人把Vue Composition API簡(jiǎn)稱為VCA。
認(rèn)識(shí)Composition API
為了開(kāi)始使用Composition API德谅,我們需要有一個(gè)可以實(shí)際使用它(編寫(xiě)代碼)的地方锄贼,在Vue組件中,這個(gè)位置就是 setup 函數(shù)女阀。setup其實(shí)就是組件的另外一個(gè)選項(xiàng)宅荤,只不過(guò)這個(gè)選項(xiàng)強(qiáng)大到我們可以用它來(lái)替代之前所編寫(xiě)的大部分其他選項(xiàng),比如methods浸策、computed冯键、watch、data庸汗、生命周期等等惫确。
setup函數(shù)的參數(shù)
我們先來(lái)研究一個(gè)setup函數(shù)的參數(shù),它主要有兩個(gè)參數(shù):props和context蚯舱。
props非常好理解改化,它其實(shí)就是父組件傳遞過(guò)來(lái)的屬性會(huì)被放到props對(duì)象中,我們?cè)趕etup中如果需要使用枉昏,那么就可以直接通過(guò)props參數(shù)獲瘸赂亍:
- 對(duì)于定義props的類型,我們還是和之前的規(guī)則是一樣的兄裂,在props選項(xiàng)中定義句旱,并且在template中依然是可以正常去使用props中的屬性,比如message晰奖。
- 如果我們?cè)趕etup函數(shù)中想要使用props谈撒,那么不可以通過(guò) this 去獲取(后面我會(huì)講到為什么)匾南,因?yàn)閜rops有直接作為參數(shù)傳遞到setup函數(shù)中啃匿,所以我們可以直接通過(guò)參數(shù)來(lái)使用即可,比如:props.message蛆楞。
另外一個(gè)參數(shù)是context溯乒,我們也稱之為是一個(gè)SetupContext,它里面包含三個(gè)屬性:
- attrs:所有的非prop的attribute臊岸;
- slots:父組件傳遞過(guò)來(lái)的插槽(這個(gè)在以渲染函數(shù)返回時(shí)才會(huì)有用橙数,后面會(huì)講到尊流,用的不多)帅戒;
- emit:當(dāng)我們組件內(nèi)部需要發(fā)出事件時(shí)會(huì)用到emit(因?yàn)槲覀儾荒茉L問(wèn)this,所以不可以通過(guò) this.$emit發(fā)出事件);
// setup(props, context), 下面是對(duì)象解構(gòu)
setup(props, {attrs, slots, emit}) {
console.log(props.message);
console.log(attrs.id, attrs.class);
console.log(slots);
console.log(emit);
}
setup函數(shù)的返回值
setup函數(shù)的返回值可以在模板template中被使用逻住,也就是說(shuō)我們可以通過(guò)setup的返回值來(lái)替代data選項(xiàng)钟哥。
// setup(props, context), 下面是對(duì)象解構(gòu)
setup(props, {attrs, slots, emit}) {
console.log(props.message);
console.log(attrs.id, attrs.class);
console.log(slots);
console.log(emit);
//返回?cái)?shù)據(jù)對(duì)象
return {
title: "Hello Home",
counter: 100
}
},
甚至是我們可以返回一個(gè)執(zhí)行函數(shù)來(lái)代替在methods中定義的方法,如下計(jì)數(shù)器的案例:
<template>
<div>
Home Page
<h2>{{message}}</h2>
<h2>{{title}}</h2>
<h2>當(dāng)前計(jì)數(shù): {{counter}}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true
}
},
setup() {
let counter = 100;
// 局部函數(shù)
const increment = () => {
counter++;
console.log(counter);
}
return {
title: "Hello Home",
counter,
increment
}
}
}
</script>
<style scoped>
</style>
但是瞎访,如果我們將 counter 進(jìn)行 increment 操作時(shí)腻贰,是否可以實(shí)現(xiàn)界面的響應(yīng)式呢?
答案是不可以扒秸。這是因?yàn)閷?duì)于一個(gè)定義的變量 (let counter = 100) 來(lái)說(shuō)播演,默認(rèn)情況下,Vue并不會(huì)跟蹤它的變化來(lái)引起界面的響應(yīng)式操作伴奥。我們以前在data()函數(shù)中定義的是響應(yīng)式的写烤,那是因?yàn)閂ue內(nèi)部通過(guò)reactive()函數(shù)包裹了一下。
setup不可以使用this
官方關(guān)于this有這樣一段描述:
表達(dá)的含義是this并沒(méi)有指向當(dāng)前組件實(shí)例拾徙,并且在setup被調(diào)用之前洲炊,data、computed尼啡、methods等都沒(méi)有被解析暂衡,所以無(wú)法在setup中獲取this。
其實(shí)之前的這段描述是和源碼有出入的崖瞭,coderwhy向官方提交了PR做出了描述的修改狂巢,后來(lái)coderwhy的PR也有被合并到官方文檔中。之前的描述大概含義是不可以使用this是因?yàn)榻M件實(shí)例還沒(méi)有被創(chuàng)建出來(lái)书聚。
其實(shí)Vue源碼是在調(diào)用createComponentInstance()方法之后再調(diào)用的setup()函數(shù)隧膘,所以調(diào)用setup()函數(shù)的時(shí)候組件實(shí)例肯定已經(jīng)創(chuàng)建出來(lái)了,只不過(guò)在setup()函數(shù)中沒(méi)有進(jìn)行任何this的綁定寺惫,所以不可以使用this疹吃。
coderwhy之前關(guān)于this的描述問(wèn)題
coderwhy是如何發(fā)現(xiàn)官方文檔的錯(cuò)誤的呢?
在閱讀源碼的過(guò)程中西雀,代碼是按照如下順序執(zhí)行的:
- 調(diào)用 createComponentInstance 創(chuàng)建組件實(shí)例萨驶;
- 調(diào)用 setupComponent 初始化 component 內(nèi)部的操作;
- 調(diào)用 setupStatefulComponent 初始化有狀態(tài)的組件艇肴;
- 在 setupStatefulComponent 取出了 setup 函數(shù)腔呜;
- 通過(guò) callWithErrorHandling 的函數(shù)執(zhí)行 setup;
從上面的代碼我們可以看出再悼,組件的instance肯定在執(zhí)行setup函數(shù)之前就已經(jīng)創(chuàng)建出來(lái)了核畴,只不過(guò)在setup()函數(shù)中沒(méi)有進(jìn)行任何this的綁定,所以不可以使用this冲九。
Reactive API
接著上面計(jì)數(shù)器的案例谤草,如果想要為在setup中定義的數(shù)據(jù)提供響應(yīng)式的特性,那么我們可以使用reactive函數(shù)。
<template>
<div>
Home Page
<h2>{{message}}</h2>
<h2>當(dāng)前計(jì)數(shù): {{state.counter}}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
//先導(dǎo)入函數(shù)
import { reactive } from 'vue';
export default {
props: {
message: {
type: String,
required: true
}
},
setup() {
const state = reactive({
counter: 100
})
// 局部函數(shù)
const increment = () => {
state.counter++;
console.log(state.counter);
}
return {
state,
increment
}
}
}
</script>
<style scoped>
</style>
那么這是什么原因呢丑孩?為什么就可以變成響應(yīng)式的呢冀宴?
這是因?yàn)楫?dāng)我們使用reactive函數(shù)處理我們的數(shù)據(jù)之后,數(shù)據(jù)再次被使用時(shí)就會(huì)進(jìn)行依賴收集温学,當(dāng)數(shù)據(jù)發(fā)生改變時(shí)略贮,所有收集到的依賴都是進(jìn)行對(duì)應(yīng)的響應(yīng)式操作(比如更新界面)。事實(shí)上仗岖,我們編寫(xiě)的data選項(xiàng)逃延,也是在內(nèi)部交給了reactive函數(shù)將其編程響應(yīng)式對(duì)象的。
上面的reactive函數(shù)要求傳入的必須是對(duì)象或數(shù)組,所以上面,即使我們只有counter: 100芋哭,也需要包裹成對(duì)象,就顯得很麻煩盔然,這時(shí)候我們可以使用Ref API。
Ref API
reactive函數(shù)對(duì)傳入的類型是有限制的是嗜,它要求我們必須傳入的是一個(gè)對(duì)象或者數(shù)組類型愈案,如果我們傳入一個(gè)基本數(shù)據(jù)類型(String、Number鹅搪、Boolean)會(huì)報(bào)一個(gè)警告:
這個(gè)時(shí)候Vue3給我們提供了另外一個(gè)Ref API站绪,ref 會(huì)返回一個(gè)可變的響應(yīng)式對(duì)象,該對(duì)象作為一個(gè)響應(yīng)式的引用維護(hù)著它內(nèi)部的值丽柿,這就是 ref 名稱的來(lái)源恢准,它內(nèi)部的值是在 ref 的 value 屬性中被維護(hù)的。
let counter = ref(100);
這里有兩個(gè)注意事項(xiàng):
- 在模板中引入ref的值時(shí)甫题,Vue會(huì)自動(dòng)幫助我們進(jìn)行解包操作馁筐,所以我們并不需要在模板中通過(guò) ref.value 的方式來(lái)使用。
- 但是在 setup 函數(shù)內(nèi)部坠非,它依然是一個(gè) ref 引用敏沉, 所以對(duì)其進(jìn)行操作時(shí),我們依然需要使用 ref.value的方式炎码。
<template>
<div>
Home Page
<h2>{{message}}</h2>
<!-- 當(dāng)我們?cè)趖emplate模板中使用ref對(duì)象, 它會(huì)自動(dòng)進(jìn)行解包 -->
<h2>當(dāng)前計(jì)數(shù): {{counter}}</h2>
<button @click="increment">+1</button>
</div>
</template>
<script>
//先導(dǎo)入函數(shù)
import { ref } from 'vue';
export default {
props: {
message: {
type: String,
required: true
}
},
setup() {
// counter編程一個(gè)ref的可響應(yīng)式的引用
let counter = ref(100);
// 局部函數(shù)
const increment = () => {
// 在setup中就要通過(guò).value訪問(wèn)
counter.value++;
console.log(counter.value);
}
return {
counter,
increment
}
}
}
</script>
<style scoped>
</style>
Ref的淺層解包
模板中的解包是淺層解包盟迟,比如:我們用ref包裹一個(gè)"Hello World",賦值給message潦闲,再把message放到一個(gè)普通info對(duì)象里面攒菠,這時(shí)候在模板中我們只能使用info.message.value來(lái)獲取"Hello World",直接使用info.message獲取的就只是一個(gè)包裹對(duì)象歉闰,而不是包裹對(duì)象里面的"Hello World"辖众。
如果我們把上面的info對(duì)象用reactive函數(shù)包裹一下卓起,那么在模板中使用時(shí),它會(huì)自動(dòng)解包:
總結(jié):
- 如果ref對(duì)象沒(méi)被其他對(duì)象包裹赵辕,在模板中使用會(huì)自動(dòng)解包。
- 如果ref對(duì)象有被其他對(duì)象包裹概龄,如果包裹的是普通對(duì)象还惠,在模板中使用時(shí),不會(huì)自動(dòng)解包私杜,如果是reactive函數(shù)包裹的對(duì)象蚕键,則會(huì)自動(dòng)解包。
- 但是在 setup 函數(shù)內(nèi)部衰粹,它依然是一個(gè) ref 引用锣光, 所以對(duì)其進(jìn)行操作時(shí),我們依然需要使用 ref.value的方式铝耻。
認(rèn)識(shí)readonly
我們通過(guò) reactive 或者 ref 可以獲取到一個(gè)響應(yīng)式的對(duì)象誊爹,但是某些情況下,我們傳入給其他地方(組件)的這個(gè)響應(yīng)式對(duì)象希望在另外一個(gè)地方(組件)能被使用瓢捉,但是不能被修改频丘,這個(gè)時(shí)候如何防止這種情況的出現(xiàn)呢?
Vue3為我們提供了 readonly 的方法泡态,readonly會(huì)返回原生對(duì)象的只讀代理(也就是它依然是一個(gè)Proxy搂漠,這個(gè) proxy 的 set 方法被劫持,并且不能對(duì)其進(jìn)行修改)某弦。
在開(kāi)發(fā)中常見(jiàn)的 readonly 方法會(huì)傳入三個(gè)類型的參數(shù):
類型一:普通對(duì)象桐汤;
類型二:reactive返回的對(duì)象;
類型三:ref的對(duì)象靶壮;
readonly的使用
readonly返回的對(duì)象都是不允許修改的怔毛,但是經(jīng)過(guò)readonly處理的原來(lái)的對(duì)象是允許被修改的。比如 const info = readonly(obj)腾降,info對(duì)象是不允許被修改的馆截,obj可以被修改,當(dāng)obj被修改時(shí)蜂莉,readonly返回的info對(duì)象也會(huì)被修改蜡娶,所以一般我們會(huì)把info傳遞給其他組件使用。
<template>
<div>
<button @click="updateState">修改狀態(tài)</button>
</div>
</template>
<script>
// 導(dǎo)入函數(shù)
import { reactive, ref, readonly } from 'vue';
export default {
setup() {
// 1.普通對(duì)象
const info1 = {name: "why"};
const readonlyInfo1 = readonly(info1);
// 2.響應(yīng)式的對(duì)象reactive
const info2 = reactive({
name: "why"
})
const readonlyInfo2 = readonly(info2);
// 3.響應(yīng)式的對(duì)象ref
const info3 = ref("why");
const readonlyInfo3 = readonly(info3);
const updateState = () => {
// info1.name = "coderwhy"; 可修改
// info2.name = "coderwhy"; 可修改
// info3.value = "coderwhy"; 可修改
// readonlyInfo1.name = "coderwhy" 不可修改 非響應(yīng)式
// readonlyInfo2.name = "coderwhy" 不可修改 響應(yīng)式
// readonlyInfo3.value = "coderwhy" 不可修改 響應(yīng)式
}
return {
updateState,
}
}
}
</script>
<style scoped>
</style>
在我們傳遞給其他組件數(shù)據(jù)時(shí)映穗,往往希望其他組件使用我們傳遞的內(nèi)容窖张,但是不允許它們修改,這時(shí)就可以使用readonly了蚁滋,而且我們希望子組件使用的數(shù)據(jù)是響應(yīng)式的宿接,所以我們可以使用reactive函數(shù)或者ref赘淮。
Reactive判斷的API
- isProxy:檢查對(duì)象是否是由 reactive 或 readonly創(chuàng)建的 proxy。
- isReactive:檢查對(duì)象是否是由 reactive創(chuàng)建的響應(yīng)式代理睦霎,如果該代理是 readonly 建的梢卸,但包裹了由 reactive 創(chuàng)建的另一個(gè)代理,它也會(huì)返回 true副女。
- isReadonly:檢查對(duì)象是否是由 readonly 創(chuàng)建的只讀代理蛤高。
- toRaw:返回 reactive 或 readonly 代理的原始對(duì)象(不建議保留對(duì)原始對(duì)象的持久引用,請(qǐng)謹(jǐn)慎使用)碑幅。
- shallowReactive:翻譯過(guò)來(lái)就是淺層響應(yīng)式戴陡,創(chuàng)建一個(gè)響應(yīng)式代理,它跟蹤其自身 property 的響應(yīng)性沟涨,但不執(zhí)行嵌套對(duì)象的深層響應(yīng)式轉(zhuǎn)換(深層還是原生對(duì)象)恤批。比如:對(duì)象里面還有對(duì)象,如果我們希望外面的屬性改變才是響應(yīng)式的裹赴,里面深層對(duì)象的改變不是響應(yīng)式的喜庞,這時(shí)候可以用shallowReactive。
- shallowReadonly:淺層只讀棋返,創(chuàng)建一個(gè) proxy赋荆,使其自身的 property 為只讀,但不執(zhí)行嵌套對(duì)象的深度只讀轉(zhuǎn)換(深層還是可讀可寫(xiě)的)懊昨。
toRefs
如果我們使用ES6的解構(gòu)語(yǔ)法窄潭,對(duì)reactive返回的對(duì)象進(jìn)行解構(gòu)獲取值,那么之后無(wú)論是修改結(jié)構(gòu)后的變量酵颁,還是修改reactive返回的state對(duì)象嫉你,數(shù)據(jù)都不再是響應(yīng)式的。
這是因?yàn)榻鈽?gòu)其實(shí)就相當(dāng)于重新聲明變量然后賦值躏惋,如下:
//解構(gòu)
const { name, age } = state;
//相當(dāng)于如下
const name = "why";
const age = 18;
那么有沒(méi)有辦法讓我們解構(gòu)出來(lái)的屬性是響應(yīng)式的呢幽污?
Vue為我們提供了一個(gè)toRefs函數(shù),可以將reactive返回的對(duì)象中的屬性都轉(zhuǎn)成ref簿姨,那么我們?cè)俅芜M(jìn)行結(jié)構(gòu)出來(lái)的 name 和 age 本身都是 ref的距误。
然后在模板中我們直接使用name、age就行扁位,因?yàn)槟0逯袝?huì)自動(dòng)解構(gòu)准潭,但是在下面的邏輯代碼中我們還是要使用 .value 來(lái)修改值:
const changeAge = () => {
age.value++;
}
這種做法相當(dāng)于已經(jīng)在 state.name 和 ref.value 之間建立了鏈接,任何一個(gè)修改都會(huì)引起另外一個(gè)變化域仇。
toRef
上面的toRefs是將reactive對(duì)象中的所有屬性都轉(zhuǎn)成ref刑然,建立鏈接,但是有時(shí)候有些屬性我們用不到暇务,這就額外增加了不必要的開(kāi)銷(xiāo)泼掠。
如果我們只希望轉(zhuǎn)換一個(gè)reactive對(duì)象中的屬性為ref怔软,那么可以使用toRef的方法:
//普通的解構(gòu)
let { name } = state;
//解構(gòu)age,并建立連接,第一個(gè)參數(shù)是對(duì)象,第二個(gè)參數(shù)是對(duì)象的屬性名
let age = toRef(state, "age");
ref其他的API
-
unref:如果我們想要獲取一個(gè)ref引用中的value,那么也可以通過(guò)unref方法择镇。如果參數(shù)是一個(gè) ref挡逼,則返回內(nèi)部值,否則返回參數(shù)本身腻豌,這是
val = isRef(val) ? val.value : val
的語(yǔ)法糖函數(shù)家坎。 - isRef:判斷值是否是一個(gè)ref對(duì)象。
- shallowRef:創(chuàng)建一個(gè)淺層的ref對(duì)象饲梭。
- triggerRef:手動(dòng)觸發(fā)和 shallowRef 相關(guān)聯(lián)的副作用乘盖。
默認(rèn)情況下焰檩,不管是reactive還是ref創(chuàng)建的響應(yīng)式對(duì)象都是深層次的憔涉,如下:
const info = ref({name: "why"})
const changeInfo = () => {
//通過(guò)value拿到原對(duì)象,再修改原對(duì)象的值,這時(shí)候界面的數(shù)據(jù)也會(huì)改變,這就是深層次的響應(yīng)式
info.value.name = "james";
}
如果我們不希望深層次的響應(yīng)式,只希望改變外面大的對(duì)象的時(shí)候才是響應(yīng)式的析苫,改變里面的屬性值不是響應(yīng)式的兜叨,我們就可以使用shallowRef。
如果我們又想觸發(fā)響應(yīng)式了衩侥,就可以調(diào)用triggerRef国旷,來(lái)手動(dòng)觸發(fā)相關(guān)聯(lián)的副作用,這時(shí)候界面就又變成響應(yīng)式的了茫死。
//淺層次的響應(yīng)式
const info = shallowRef({name: "why"})
const changeInfo = () => {
//這時(shí)候修改里面的屬性值,界面就不會(huì)變化了
info.value.name = "james";
//如果我們又想觸發(fā)響應(yīng)式,就可以調(diào)用triggerRef,來(lái)觸發(fā)相關(guān)聯(lián)的副作用,這時(shí)候界面就又變成響應(yīng)式的了
triggerRef(info);
}
customRef
下面代碼跪但,我們?cè)谳斎肟蛑休斎胛淖郑旅骘@示的文字會(huì)立馬更新:
<template>
<div>
<input v-model="message"/>
<h2>{{message}}</h2>
</div>
</template>
<script>
import ref from 'vue';
export default {
setup() {
const message = ref("Hello World");
return {
message
}
}
}
</script>
<style scoped>
</style>
如果我們不想更新這么頻繁峦萎,比如輸入后300ms才更新屡久,那么使用ref就做不到了,所以我們需要自定義ref爱榔。
創(chuàng)建一個(gè)自定義的ref被环,并對(duì)其依賴項(xiàng)跟蹤和更新觸發(fā)進(jìn)行顯示控制。它需要一個(gè)工廠函數(shù)详幽,該函數(shù)接受 track 和 trigger 函數(shù)作為參數(shù)筛欢,并且應(yīng)該返回一個(gè)帶有 get 和 set 的對(duì)象。
自定義ref的useDebounceRef.js文件代碼如下:
import { customRef } from 'vue';
// 自定義ref
export default function(value, delay = 300) {
let timer = null;
return customRef((track, trigger) => {
return {
get() {
//收集依賴
track();
return value;
},
set(newValue) {
//如果在300ms內(nèi)又輸入值了唇聘,就把定時(shí)器清空版姑,也就是取消觸發(fā)更新
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
//觸發(fā)更新
trigger();
}, delay);
}
}
})
}
然后使用我們自定義的ref,就可以達(dá)到我們想要的延遲效果了迟郎,這樣做可以提升一點(diǎn)點(diǎn)性能漠酿。
<template>
<div>
<input v-model="message"/>
<h2>{{message}}</h2>
</div>
</template>
<script>
import debounceRef from './hook/useDebounceRef';
export default {
setup() {
const message = debounceRef("Hello World");
return {
message
}
}
}
</script>
<style scoped>
</style>