前言
你是不是習慣了Vue2的賦值即響應式影钉?Vue2還有個Vue.observable但你從沒用過豫缨?結(jié)果Vue3像跳跳糖一樣跳出來這么多的響應式API系吩,你有沒有懵逼的感覺?
不慌调缨,挨個學。由于官方文檔寫的晦澀難懂吆你,所以我寫下這篇弦叶。原創(chuàng):簡書microkof。
首先說明妇多,全文提到的“基本數(shù)據(jù)”是指“數(shù)據(jù)類型為基本數(shù)據(jù)類型的數(shù)據(jù)”伤哺,“原始數(shù)據(jù)”是指被轉(zhuǎn)變?yōu)轫憫綌?shù)據(jù)之前的純對象或基本數(shù)據(jù)。
reactive
Vue 3的根基者祖。返回對象的響應式副本立莉,響應式轉(zhuǎn)換是“深層”的——它影響所有嵌套property。返回Proxy對象七问,不等于原始對象蜓耻。建議只操作Proxy對象,不要操作原始對象械巡。
官方建議刹淌,對來自于服務器的數(shù)據(jù)或者注定要響應式的數(shù)據(jù)執(zhí)行reactive之前饶氏,最好不要用臨時變量儲存原始數(shù)據(jù),因為沒有意義有勾,而且兩個變量容易讓初學者引起誤操作疹启。
<template>
<button @click="r.a++">count is: {{ r.a }}</button>
</template>
<script>
import { reactive } from "vue";
export default {
setup() {
let r = reactive({ a: 1 });
return {
r,
};
},
};
</script>
ref
ref說白了就是reactive({value: 原始數(shù)據(jù)})
。下方代碼如果打印r對象蔼卡,會得到RefImpl(ref)對象喊崖,它有一個value屬性指向基礎(chǔ)類型值30。
<template>
<button @click="r++">count is: {{ r }}</button>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
let r = ref(30);
return {
r,
};
},
};
</script>
為什么似乎Proxy已經(jīng)解決所有問題菲宴,還要有ref API呢贷祈?
因為ES的Proxy API是為引用數(shù)據(jù)類型服務的,它無法為基本數(shù)據(jù)類型提供代理喝峦。如果強行代理势誊,Vue會有提示:value cannot be made reactive: 30
。
那么為什么Vue2的defineproperty并沒有區(qū)分基本數(shù)據(jù)類型和引用數(shù)據(jù)類型呢谣蠢?
因為defineproperty就是Object的靜態(tài)方法粟耻,它只是為對象服務的,甚至無法對數(shù)組服務眉踱,因此Vue 2弄了一個data根對象來存放基本數(shù)據(jù)類型挤忙,這樣無論什么類型,都是根對象的property谈喳,所以也就能代理基本數(shù)據(jù)類型册烈。而Proxy能對所有引用類型代理,Vue 3也不再用data根對象婿禽,而是一個個的變量赏僧,所以帶來了新問題,如何代理基本數(shù)據(jù)類型呢扭倾?并沒有原生辦法淀零,只能構(gòu)建一個{value: Proxy Object}結(jié)構(gòu)的對象,這樣Proxy也就能代理了膛壹。
問題來了驾中,同樣是響應式結(jié)構(gòu),ref跟reactive的區(qū)別是什么模聋?
ref與reactive的區(qū)別
對比 | ref | reactive |
---|---|---|
返回數(shù)據(jù)類型 | RefImpl對象(也叫ref對象) | Proxy對象 |
傳入基本類型返回 | {value: 基本類型} |
禁止這么做 |
傳入引用類型返回 | {value: Proxy對象} |
Proxy對象 |
兩者分別適用場合:
ref
可以為基本類型添加響應式肩民,也可以為引用類型添加響應式,reactive
只能為引用類型添加響應式撬槽。對于引用類型此改,什么時候用
ref
,什么時候用reactive
侄柔?簡單說共啃,如果你只打算修改引用類型的一個屬性占调,那么推薦用reactive
,如果你打算變量重賦值移剪,那么一定要用ref
究珊。具體見下文。
使用組合式API的話纵苛,請一定了解“重賦值自身”和“重賦值自身屬性”的區(qū)別
這一點非常重要剿涮。先看配置項式API中重賦值Proxy的范例,跟Vue 2沒有區(qū)別攻人,頁面會渲染出[ { "name": "趙六", "age": 21 } ]
字符:
<template>
<div>
{{ jsonData }}
</div>
</template>
<script>
export default {
data() {
return {
jsonData: [
{
name: "牛二",
age: 13,
},
],
};
},
created() {
this.jsonData = [
{
name: "王五",
age: 19,
},
];
},
mounted() {
this.jsonData = [
{
name: "趙六",
age: 21,
},
];
},
};
</script>
而使用組合式API取试,你會發(fā)現(xiàn),reactive后的Proxy在onMounted中重賦值無法觸發(fā)渲染怀吻,最終頁面顯示[ { "name": "王五", "age": 19 } ]
而不是趙六:
<template>
<div>
{{ jsonData }}
</div>
</template>
<script>
import { onMounted, reactive } from "vue";
export default {
setup() {
let jsonData = reactive([
{
name: "牛二",
age: 13,
},
]);
jsonData = reactive([
{
name: "王五",
age: 19,
},
]);
onMounted(() => {
jsonData = reactive([
{
name: "趙六",
age: 100,
},
]);
});
return {
jsonData,
};
},
};
</script>
原因在于jsonData盡管是響應式的瞬浓,但是響應式的是它的屬性,而不是它自身蓬坡,重賦值它自身跟重賦值它的屬性是兩碼事猿棉。所以,想在組合式API中讓數(shù)據(jù)具備響應式屑咳,必須用ref
萨赁,因為ref
又對Proxy
包裝了一層,修改ref
其實是修改它的value
兆龙,它的value
一定是響應式的杖爽,因此視圖就正常更新了。
再多說一點紫皇,如果數(shù)據(jù)是服務器返回的LIST數(shù)據(jù)掂林,而且只顯示、不變更坝橡,那么最好是使用shallowRef
來包裝數(shù)據(jù),可以節(jié)能精置。如果會有變更计寇,那么應該用ref
。
下例中采用了Ref語法糖脂倦。頁面會顯示Michael的信息:
<template>
<div>
{{ jsonData }}
<ul>
<li v-for="item in jsonData" :key="item.name">
{{ item.name }} - {{ item.age }}
</li>
</ul>
</div>
</template>
<script setup>
import { onMounted, ref } from "vue";
ref: jsonData = [
{
name: "Jim",
age: 13,
},
];
jsonData = [
{
name: "Tom",
age: 19,
},
];
onMounted(() => {
jsonData = [
{
name: "Michael",
age: 100,
},
];
});
</script>
現(xiàn)在好像ref把引用數(shù)據(jù)類型也管起來了番宁,到底啥時候才適合用reactive呢?很簡單啊赖阻,如果你確信你只可能去改引用類型數(shù)據(jù)的屬性蝶押,那么一定要用reactive,如果還有可能要整體重賦值火欧,那還得用ref棋电。所以說:需要在組合式API里給變量重賦值的話茎截,無論什么數(shù)據(jù)類型都必須用ref,不可以用reactive赶盔。
到此企锌,我們清楚了ref與reactive都必須存在的理由,接著說于未,reactive有一套兄弟API象对,ref也有一套深胳,它們都是干什么用的?先看reactive的:
reactive與shallowReactive的區(qū)別
打印的話,乍一看沒有區(qū)別楷力,但是,shallow的中文意義是“淺層的”棍弄,shallowReactive不代理深層property檬嘀,只會指向原始對象的深層property。
注意片习,給shallowReactive傳入Proxy是沒有意義的捌肴,即便這么做,直接返回該Proxy藕咏。
shallowReactive的用途是:如果一個對象的深層不可能變化状知,那么就沒必要深層響應,這時候用shallowReactive可以節(jié)省系統(tǒng)開銷孽查。
下例中饥悴,按下第2個button不會有反應,只有又去按下第1個button之后盲再,視圖刷新西设,第二個button才有反應。
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
</div>
</template>
<script>
import { reactive, shallowReactive } from "vue";
export default {
setup() {
let r = reactive({a: 1, b: {c: 2}});
console.log(r);
let s = shallowReactive({a: 1, b: {c: 2}});
console.log(s);
return {
r,s
};
},
};
</script>
reactive與readonly的區(qū)別
reactive一般只接受ES普通的引用數(shù)據(jù)類型答朋,盡管它也可以接受Proxy對象贷揽,但是沒有意義、沒有必要梦碗,但readonly可以接受Proxy對象禽绪,而且有實際意義,它可以獲取純對象或者Proxy或者RefImpl洪规,返回原始代理的只讀代理印屁。說白了它做2步操作,先reactive斩例,然后另生成一個只讀Proxy雄人。
readonly的只讀是深層的只讀:訪問的任何嵌套property也是只讀的。
readonly存在的意義有2個念赶,一個是保護數(shù)據(jù)不被修改础钠,另一個是提升性能恰力。
下例中,第2個button點擊不會有反應珍坊。
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
</div>
</template>
<script>
import { reactive, readonly } from "vue";
export default {
setup() {
let r = reactive({a: 1, b: {c: 2}});
console.log(r);
let s = readonly({a: 1, b: {c: 2}});
console.log(s);
return {
r,s
};
},
};
</script>
readonly與shallowReadonly的區(qū)別
就像reactive與shallowReactive的一樣牺勾,shallowReadonly只會給對象的第一層property設置只讀,不去管深層property阵漏,因此深層property并沒有被代理驻民,只會指向原始對象。
下例中:
按下button1會有報錯提示:Set operation on key "c" failed: target is readonly.
履怯,因為r是深層只讀的回还。
按下button2沒有任何反應,因為shallowReadonly的深層是指向原始值的叹洲,修改原始對象不會反映到視圖上柠硕。
按下button3也會有報錯提示:Set operation on key "a" failed: target is readonly.
,因為shallowReadonly是淺層只讀的运提,a恰好是淺層property蝗柔。
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
<button @click="s.a++">count is: {{ s.a }}</button>
</div>
</template>
<script>
import { readonly, shallowReadonly } from "vue";
export default {
setup() {
let r = readonly({a: 1, b: {c: 2}});
console.log(r);
let s = shallowReadonly({a: 1, b: {c: 2}});
console.log(s);
return {
r,s
};
},
};
</script>
shallowReactive與shallowReadonly的區(qū)別
首先說,兩者對深層property的態(tài)度是一致的民泵,即“不去代理”癣丧,深層property都是指向原始對象的深層property,都允許直接修改原始對象的深層property栈妆,區(qū)別在于對待淺層property方面胁编。
shallowReactive允許修改淺層property,shallowReadonly不允許鳞尔,Vue3會阻止并給出報錯嬉橙。
isReactive、isReadonly寥假、isProxy的區(qū)別
- isReactive:Proxy是否是由reactive創(chuàng)建市框,是則返回true
- isReadonly:Proxy由readonly創(chuàng)建則返回true
- isProxy:上面兩個滿足任意一條,就返回true
上例中糕韧,如果加入這3條打印會得到什么拾给?
console.log(isReadonly(s)); // true
console.log(isReadonly(s.a)); // false,因為s.a得到的是a的值兔沃,而不是a自身,a的值當然不是響應式的
console.log(isReadonly(s.b)); // false级及,道理同上
toRaw是什么
官方已經(jīng)解釋的很清楚乒疏,返回proxy的原始對象。這是一個轉(zhuǎn)義口饮焦,2個作用:可用于臨時讀取而不會引起proxy訪問/跟蹤開銷怕吴,也可用于寫入而不會觸發(fā)視圖更新窍侧。
官方又說,不建議保留對原始對象的持久引用转绷。請謹慎使用伟件。這句話什么意思?就是說:
盡量不要把原始對象賦值給變量议经,盡量減少中間變量斧账;
將原始對象轉(zhuǎn)換為Proxy之后,如果你臨時打算操作一下原始對象煞肾,那么也不要因為這個目的就早早的把原始對象賦值給變量咧织,而是應該用
toRaw(proxy)
,以獲取原始對象籍救,比如得到一個變量R习绢,然后你可以操作R,操作完成之后就不要再碰R蝙昙,而應繼續(xù)操作Proxy闪萄。
下例中:
按下button1,會發(fā)現(xiàn)button2也跟著變奇颠,這表明Proxy的基本原理:操作Proxy會反映到原始對象身上败去。
按下button2,沒有任何反應大刊,表明操作原始對象不會反映到視圖上为迈。這時候重新按下button1,會發(fā)現(xiàn)數(shù)字跳躍了幾個數(shù)缺菌,這表明直接修改原始對象之后葫辐,Proxy對原始對象繼續(xù)代理,并不需要重新reactive伴郁。
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
</div>
</template>
<script>
import { reactive, toRaw } from "vue";
export default {
setup() {
let r = reactive({a: 1, b: {c: 2}});
console.log(r);
let s = toRaw(r);
console.log(s);
return {
r,s
};
},
};
</script>
markRaw與readonly的區(qū)別
markRaw是操作原始對象的耿战,它的意義是將原始對象或者原始對象的某個淺層或深層property標記為“永遠不允許被代理”。Vue3會給對象的第一層或某深層加一個標記__v_skip: true
焊傅,這樣剂陡,即便原始對象被reactive之后,得到的該層和更深層就不會被代理狐胎。
如果想給原始對象的某個property加markRaw鸭栖,需要執(zhí)行3步,先定義變量指向該property握巢,然后markRaw這個變量得到新對象晕鹊,然后讓源對象的property指向新對象。
如果將加了標記的原始對象當做其他原始對象的屬性,其他原始對象被reactive之后溅话,加了標記的對象依然不會被reactive晓锻。也就是說,reactive見了__v_skip: true
就繞著走飞几。
注意:雖然Vue3只會在表層加標記砚哆,但是會影響深層的property。
markRaw與readonly的區(qū)別屑墨,在于側(cè)重點不同:
markRaw允許被修改躁锁,但不允許被代理。這里盡管說允許修改绪钥,但是修改的意義不大灿里,畢竟Vue的核心思想是響應式,在添加響應式之前修改意義不大程腹。
readonly不允許被修改匣吊,但已經(jīng)被代理。
它們兩者相同點在于寸潦,從不同角度節(jié)省系統(tǒng)開銷色鸳。
markRaw的用途:
首先說,直接給某個對象全盤markRaw是沒有意義的见转,因為你就是開發(fā)者命雀,你不想讓某對象被reactive,那么你不去寫reactive就好了啊斩箫。所以markRaw的用途應該是:允許對象被reactive吏砂,但是阻止對象的部分內(nèi)容被reactive。
markRaw與shallowReactive的區(qū)別
markRaw | shallowReactive | |
---|---|---|
作用 | 阻止響應式 | 讓淺層property響應式乘客,不操作深層property |
淺層property | 阻止響應式 | 執(zhí)行響應式 |
深層property | 阻止響應式 | 不執(zhí)行響應式狐血,也不阻止 |
如果希望阻止其他程序員將對象響應式,則可以使用markRaw來保護易核。
如果剛好打算不讓從第2層到最深層的所有property響應式匈织,那么用shallowReactive可能更好。
如果想要更定制化的阻止某些property被響應式牡直,那么應當使用markRaw缀匕。例如下例中,x.b被標記碰逸,然后重復值給自己乡小,此時再將x響應式,x.b依然沒有被響應式饵史。所以點擊button不會有反應满钟。
<template>
<div>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
</div>
</template>
<script>
import { markRaw, reactive } from "vue";
export default {
setup() {
let x = {a:1, b: {c: 2}};
x.b = markRaw(x.b);
let s = reactive(x);
console.log(isReactive(s)); // true
console.log(isReactive(s.b)); // false
return {
s
};
},
};
</script>
最后我們看ref和它的兄弟API掸哑。
ref與shallowRef的區(qū)別
ref | shallowRef | |
---|---|---|
本質(zhì) | reactive({value: 原始數(shù)據(jù)}) | shallowReactive({value: 原始數(shù)據(jù)}) |
區(qū)別點 | {value: 原始數(shù)據(jù)}被深層響應式 | 只有value被響應式,原始數(shù)據(jù)沒有響應式 |
傳入基本類型 | 兩個API無差別 | 兩個API無差別零远,性能考慮盡量用shallowRef |
傳入引用類型 | value指向Proxy | value指向原始數(shù)據(jù) |
shallowRef的作用是只對value添加響應式,因此厌蔽,必須是value被重新賦值才會觸發(fā)響應式牵辣。shallowRef的出現(xiàn)主要是為了節(jié)省系統(tǒng)開銷。
下例中奴饮,點擊button1會有反應纬向,點擊button2不會有反應。關(guān)鍵是點擊button3戴卜,我們知道在<template>里逾条,如果給s重新賦值,其實相當于給s.value重新賦值投剥,由于value是響應式的师脂,這時候button2和button3都會有變化。
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
<button @click="s = { a: 10, b: { c: 20 } }">count is: {{ s.b.c }}</button>
</div>
</template>
<script>
import { ref, shallowRef } from 'vue';
export default {
setup() {
let r = ref({ a: 1, b: { c: 2 } });
let s = shallowRef({ a: 1, b: { c: 2 } });
return {
r,
s,
};
},
};
</script>
toRef是咋回事
先看看這個題目江锨,看看Proxy對象里面的基本數(shù)據(jù)是否具備響應式:
<template>
<div>
<button @click="r.a++">count is: {{ r.a }}</button>
<button>count is: {{ s }}</button>
</div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
setup() {
let r = reactive({a:1});
console.log(r);
let s = r.a;
console.log(s);
return {
r,s
};
},
};
</script>
當我點擊button1的時候吃警,你說button2會變嗎?并不會啄育。變量s就是個基本數(shù)據(jù)酌心,沒有任何響應式。很不爽是不是挑豌?現(xiàn)在我改改安券,把let s = r.a;
改成let s = toRef(r, 'a');
,然后再試試氓英?
可以看到button2的數(shù)字跟著變了侯勉!這就是toRef的作用:當一個變量指向一個對象的某個property,且這個property是基本數(shù)據(jù)類型時债蓝,必須用toRef才能變量與對象的響應式連接壳鹤。如果這個property是引用數(shù)據(jù)類型,就不需要動用toRef饰迹。
toRef的用途之一是用于傳參芳誓,可傳遞一個響應式的基本數(shù)據(jù)類型。
toRef還有一個特點是可以提前綁定啊鸭,看個例子锹淌,r的原始數(shù)據(jù)并沒有property叫c,但是我就任性赠制,我就提前讓s賦值為toRef(r, 'c')赂摆,這時候兩個button上是沒有數(shù)據(jù)的挟憔,畢竟property c是不存在的,在我點擊button1之后烟号,兩個button都顯示了3绊谭,說明提前綁定是有用的。
<template>
<div>
<button @click="r.c = 3">count is: {{ r.c }}</button>
<button>count is: {{ s }}</button>
</div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
setup() {
let r = reactive({a:{b:2}});
console.log(r);
let s = toRef(r, 'c');
console.log(s);
return {
r,s
};
},
};
</script>
ref與toRef的區(qū)別
ref | toRef | |
---|---|---|
用法 | ref(原始值) | toRef(Proxy, 'xxprop') |
返回 | ref對象 | 同左 |
誤區(qū) | 不要給ref傳入純對象的屬性汪拥,毫無意義且造成困惑达传,應傳原始值 | 不要給toRef傳入原始值,毫無意義且造成困惑迫筑,應傳Proxy |
講解一下誤區(qū)宪赶。比如下例中:
點擊button1,打印的ref對象是如期待的
{ count: 4 }
脯燃,視圖也更新為4
搂妻,但是原始值并沒有變,依然是{ count: 3 }
辕棚。這說明:給ref傳純對象的屬性會造成困惑欲主。刷新頁面,只點擊button2坟募,打印的ref對象變了岛蚤,原始值也變了,但是視圖沒有更新懈糯,還是3涤妒。說明:給toRef傳入原始值是錯誤的操作,應當傳入Proxy赚哗,但也證明toRef對傳入值是指向關(guān)系她紫。
刷新頁面,只點擊button3屿储,一切如期待贿讹,說明:上面的說法是正確的。
<template>
<div>
<p>{{ state1 }}</p>
<button @click="add1">增加</button>
<p>{{ state2 }}</p>
<button @click="add2">增加</button>
<p>{{ state3.a }} - {{ state4 }}</p>
<button @click="add3">增加</button>
</div>
</template>
<script>
import { reactive, ref, toRef } from "vue";
export default {
setup() {
const obj = { count: 3 };
const state1 = ref(obj.count);
const state2 = toRef(obj, "count");
const state3 = reactive({ a: 5 });
const state4 = toRef(state3, "a");
function add1() {
state1.value++;
console.log("原始值obj:", obj);
console.log("state1:", state1);
}
function add2() {
state2.value++;
console.log("原始值obj:", obj);
console.log("state2:", state2);
}
function add3() {
state4.value++;
console.log("state3:", state3);
console.log("state4:", state4);
}
return { state1, state2, state3, state4, add1, add2, add3 };
},
};
</script>
toRef與toRefs的區(qū)別
toRefs可以看做批量版本的toRef够掠。
toRef | toRefs | |
---|---|---|
用法 | toRef(Proxy, 'xxprop') | toRefs(Proxy) |
返回 | ObjectRefImpl對象 | 同左 |
作用 | 創(chuàng)建變量到Proxy屬性的響應式連接 | 創(chuàng)建變量每個屬性到Proxy每個屬性的響應式連接 |
連接關(guān)系 | 一對一 | 多對多 |
下例中民褂,當點擊button1時,所有button都會有反應疯潭。
<template>
<div>
<button @click="r.c = 3">count is: {{ r.c }}</button>
<button>count is: {{ s }}</button>
<button>count is: {{ t.c.value }}</button>
</div>
</template>
<script>
import { reactive, toRef, toRefs } from "vue";
export default {
setup() {
let r = reactive({a:{b:2}, c: 4});
console.log(r);
let s = toRef(r, 'c');
console.log(s);
let t = toRefs(r);
console.log(t.c)
return {
r,s,t
};
},
};
</script>
toRefs的一大用途是變相解構(gòu)Proxy赊堪。首先了解一個常識,Proxy如果解構(gòu)竖哩,基本數(shù)據(jù)會丟失響應式】蘖現(xiàn)在我既想要解構(gòu)Proxy,又不想丟失響應式相叁,怎么辦遵绰?可以使用toRefs辽幌。
下例中,變量c是基本數(shù)據(jù)椿访,它不具備響應式乌企,因此button1被點擊之后,button2不會跟著變成玫。如果將let {c} = r;
改成let {c} = toRefs(r);
逛犹,則變量c具備了響應式,button2會跟著變梁剔。
<template>
<div>
<button @click="r.c = 3">count is: {{ r.c }}</button>
<button>count is: {{ c }}</button>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";
export default {
setup() {
let r = reactive({a:{b:2}, c: 4});
let {c} = r;
return {
r,c
};
},
};
</script>
toRefs的簡潔用法:
return {...toRefs(Proxy), others}
可用于返回解構(gòu)的Proxy,而不需要創(chuàng)建一個臨時變量舞蔽。
如果組件只需要返回一個解構(gòu)的Proxy荣病,可以更簡略:return toRefs(Proxy)
。
customRef是什么
customRef跟ref渗柿、toRef个盆、toRefs有很大區(qū)別,它生成的ref對象會自定義get和set朵栖。
customRef的主要用途至少有2個:
- 時機上說颊亮,可以控制視圖更新的時機,可以延遲更新陨溅。其他ref兄弟API都做不到终惑。
- 內(nèi)容上說,可以修改傳入的原始數(shù)據(jù)门扇,讓原始數(shù)據(jù)與返回值不相同雹有。其他兄弟API也做不到。
<template>
<div>
{{text}} - <input v-model="text" />
</div>
</template>
<script>
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue + 1
console.log(value)
trigger()
}, delay)
}
}
})
}
export default {
setup() {
return {
text: useDebouncedRef('hello')
}
}
}
</script>
測試:在<input>里快速敲入一串字符臼寄,左邊的{{text}}
位置會延遲出現(xiàn)結(jié)果霸奕,而且會節(jié)流,而且console.log(value)也會延遲打印吉拳。
解釋:track和trigger质帅,其中track用于追蹤,寫在return之前留攒。trigger是觸發(fā)煤惩,用在賦值給value語句之后。
將ref改寫成customRef應該怎么寫稼跳?去掉延時盟庞,且value = newValue
即可。
unref是什么
unref類似于toRaw汤善。unref的本質(zhì)就是解包什猖,把{value: Proxy || 基本數(shù)據(jù)}
解成Proxy || 基本數(shù)據(jù)
票彪。unref對toRefs創(chuàng)造的對象的各個屬性也起作用,因為各個屬性也是ref對象不狮。
由于Vue3的開發(fā)原則是盡量不要直接修改內(nèi)部值降铸,對ref來講就是盡量不要修改Proxy,如果某些場景下非要直接修改Proxy摇零,需要用unref臨時將ref還原為Proxy推掸。與toRaw一樣,修改完P(guān)roxy之后并不需要重新執(zhí)行ref驻仅。
下例中谅畅,點擊button1和button2,兩個按鈕都會有反應噪服。
<template>
<div>
<button @click="r.b.c++">count is: {{ r.b.c }}</button>
<button @click="s.b.c++">count is: {{ s.b.c }}</button>
</div>
</template>
<script>
import { ref, unref } from "vue";
export default {
setup() {
let r = ref({a: 1, b: {c: 2}});
let s = unref(r);
return {
r,s
};
},
};
</script>
toRaw與unref的區(qū)別
用這三行代碼就很容易說明了:
const obj = { count: 3 };
const state = ref(obj);
console.log(toRaw(state.value) === toRaw(unref(state))); // true
說明:
當變量state為ref對象時毡泻,state.value === unref(state)為真,兩邊都是Proxy對象粘优,也說明仇味,想得到ref的Proxy,有2種方式:.value或者unref雹顺,完全等價丹墨。
當變量state為ref對象時,toRaw(state.value) === toRaw(unref(state))兩邊都是原始值嬉愧,也說明想獲得ref對象的原始值且原始值為引用類型時贩挣,有2種方式,toRaw(state.value)或者toRaw(unref(state))没酣,如果原始值為基本類型揽惹,也有2種方式,state.value或者unref(state)四康。所以搪搏,toRaw并不是對ref沒用,ref想得到原始對象就得用toRaw闪金。
toRaw對ref對象無效疯溺,必須作用于ref.value。
triggerRef是什么
我們知道shallowRef返回的ref對象的value指向的是內(nèi)部值哎垦,如果囱嫩,我既想使用shallowRef生成ref對象(為了節(jié)省開銷),又想偶爾修改value指向的內(nèi)部值的某個property漏设,又希望那個ref對象得到響應墨闲,我該怎么辦?這時候可以用triggerRef郑口。
下例中鸳碧,如果注釋掉triggerRef(r)
盾鳞,那么點擊button不會看到任何反應,因為{a:{b:2}, c: 4}
都是非響應式的瞻离。
<template>
<div>
<button @click="onClick">count is: {{ r.c }}</button>
</div>
</template>
<script>
import { shallowRef, triggerRef } from "vue";
export default {
setup() {
let r = shallowRef({a:{b:2}, c: 4});
function onClick() {
r.value.c = 6;
triggerRef(r);
}
return {
r, onClick
};
},
};
</script>
isRef是什么
這個不說了腾仅。
Vue 2的ref在Vue 3怎么用?
這算是一個附錄套利。Vue 2的ref在Vue 3依然存在推励,依然那樣標記:
<div ref="elA">div元素</div>
但是在setup() {}
里并不是Vue 2那么用,而是定義一個頂層變量肉迫,值很簡單验辞,一律寫成ref(null)
即可,重要的是變量名喊衫,變量名必須與<div ref="elA">
的elA
一致受神,然后onMounted里才能獲取、操作elA元素格侯,elA.value就指向這個DOM元素,最后不要忘記return這個elA變量:
import { ref, onMounted } from 'vue'
export default {
setup() {
const elA = ref(null);
// 在掛載后才能通過 elA 獲取到目標元素
onMounted(() => {
elA.value.innerHTML = '內(nèi)容被修改'
})
return {elA}
}
}