前言:
針對(duì)vue3
官網(wǎng)中, 響應(yīng)式:進(jìn)階
API 中, 我們?cè)谏弦徽轮薪o大家講解了shallowRef
, shallowReactive
, shallowReadonly
幾個(gè)API的使用.
本章主要對(duì)剩下的API 進(jìn)行講解, 我們先看一下官網(wǎng)中進(jìn)階API 都有哪些
對(duì)于剩下這些API, 你需要了解他們創(chuàng)建目的, 是為了解決之前的API存在的那些痛點(diǎn)問(wèn)題, 這樣你就能更好的了解使用他們的細(xì)節(jié).工作中就可以有的放矢的選擇不同的API.
1. triggerRef
我們首先來(lái)分析一下triggerRef
API 的使用
1.1. triggerRef 針對(duì)的痛點(diǎn)問(wèn)題
我們先看一個(gè)痛點(diǎn)問(wèn)題:
對(duì)于ref
響應(yīng)式數(shù)據(jù)的變化, vue
幫我們處理副作用. 比如,頁(yè)面的更新, watchEffect
偵聽器回調(diào)函數(shù)的調(diào)用等.
但對(duì)于淺層響應(yīng)數(shù)據(jù), 比如shallowRef
創(chuàng)建的數(shù)據(jù), 其深層并不具有響應(yīng)性, 也就是說(shuō)vue
并沒(méi)有監(jiān)測(cè)這些數(shù)據(jù)的變化, 當(dāng)對(duì)深層數(shù)據(jù)進(jìn)行修改時(shí), 并不會(huì)觸發(fā)副作用, 比如頁(yè)面不會(huì)自動(dòng)刷新.
triggerRef
API 就是為了解決shallowRef
淺層響應(yīng)式數(shù)據(jù)深層修改問(wèn)題.
當(dāng)深層修改時(shí), 會(huì)強(qiáng)制觸發(fā)依賴于一個(gè)淺層 ref
的副作用偎血,這通常在對(duì)淺引用的內(nèi)部值進(jìn)行深度變更后使用。
1.2. triggerRef 類型
類型:
function triggerRef(ref: ShallowRef): void
triggerRef
API 函數(shù)接收一個(gè)shallowRef
API 創(chuàng)建的數(shù)據(jù), 作用就是強(qiáng)制觸發(fā)這個(gè)淺層ref
數(shù)據(jù)的副作用.
1.3. triggerRef 使用示例
示例:
<template>
<div>
<h3>shallowReadonly</h3>
<div>{{ count }}</div>
<div>{{ count2 }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, readonly, ref, shallowReadonly, shallowRef, triggerRef, watchEffect } from 'vue'
export default defineComponent({
setup() {
const count = ref({ num: 0 })
const count2 = shallowRef({ num: 0 })
// 對(duì)于ref 數(shù)據(jù), 是深層響應(yīng)式,
// 因此當(dāng)我們通過(guò)count.value.num++ 修改數(shù)據(jù)時(shí),依然會(huì)觸發(fā)watchEffect副作用函數(shù)
watchEffect(() => {
console.log('count.value.num', count.value.num)
})
// 因?yàn)閟hallowRef 數(shù)據(jù)不是深層響應(yīng)式, 只有.value 整體修改才會(huì)觸發(fā)響應(yīng)式
// 因?yàn)楫?dāng)我們通過(guò)count2.value.num++ 修改數(shù)據(jù)時(shí),不會(huì)出發(fā)watchEffect 副作用函數(shù)
// 同時(shí)視圖也不會(huì)發(fā)生更改
watchEffect(() => {
console.log('count2.value.num', count2.value.num)
})
// 修改數(shù)據(jù)
const change = () => {
// count.value.num++
count2.value.num++
// 如果希望shallowRef 深層數(shù)據(jù)修改后,觸發(fā)視圖更新
// 那么就需要使用triggerRef 手動(dòng)觸發(fā)更新
triggerRef(count2) // 手動(dòng)更新count2
}
return { count, count2, change }
}
})
</script>
通過(guò)示例的運(yùn)行結(jié)果, 你也可以看出. shallowRef
創(chuàng)建響應(yīng)式數(shù)據(jù), 在深層數(shù)據(jù)發(fā)生變化時(shí), 不會(huì)觸發(fā)頁(yè)面更新 和watchEffect
的處理函數(shù). 因?yàn)樯顚硬痪哂许憫?yīng)性.
當(dāng)我們手動(dòng)調(diào)用triggerRef
函數(shù), 并將shallowRef
創(chuàng)建數(shù)據(jù)作為參數(shù), 就是告訴vue
, 我們需要強(qiáng)制執(zhí)行shallowRef
數(shù)據(jù)的副作用. 此時(shí)頁(yè)面將會(huì)更新, watchEffect
處理函數(shù)也會(huì)自動(dòng)執(zhí)行
1.4. triggerRef 使用小結(jié)
在理解triggerRef
API 的使用后, 針對(duì)該API, 我做了以下小結(jié)
-
triggerRef
常與shallowRef
搭配使用 -
triggerRef
會(huì)強(qiáng)制更新以shallowRef
數(shù)據(jù)作為依賴的副作用盯漂,ref
數(shù)據(jù)會(huì)自動(dòng)觸發(fā)這些副作用
我們需要注意的是: vue3
只提供了triggerRef
這個(gè)方法颇玷,但沒(méi)有提供triggerReactive
的方法。 也就是說(shuō)triggerRef
【不可以】去更改 shallowReactive
創(chuàng)建的數(shù)據(jù)
2. toRaw
根據(jù)一個(gè) Vue 創(chuàng)建的代理返回其原始對(duì)象
2.1. toRaw 針對(duì)的問(wèn)題
在vue3
中, 我們通過(guò)reactive()
就缆、readonly()
帖渠、shallowReactive()
shallowReadonly()
四個(gè)API 創(chuàng)建的響應(yīng)式數(shù)據(jù), 本質(zhì)上就是通過(guò)Proxy
創(chuàng)建的代理對(duì)象.
但有時(shí)我們?cè)谧鰯?shù)據(jù)傳輸時(shí), 我們并不需要傳響應(yīng)式數(shù)據(jù), 我們只想傳最基本的原始對(duì)象.
toRaw
API 的作用就是返回 reactive()
、readonly()
竭宰、shallowReactive()
,shallowReadonly()
創(chuàng)建的代理對(duì)應(yīng)的原始對(duì)象空郊。
2.2. toRaw 類型
toRaw 函數(shù)簽名
function toRaw<T>(proxy: T): T
toRaw
API 函數(shù)接收一個(gè)Proxy
代理對(duì)象(響應(yīng)式對(duì)象)作為參數(shù),
2.3. toRaw 使用示例
示例:
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ user }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRaw } from 'vue'
export default defineComponent({
setup() {
// 代理目標(biāo)對(duì)象
const obj = { name: '張三', age: 18 }
// reactive 處理的代理對(duì)象
const user = reactive(obj)
// 控制觸發(fā)代理對(duì)象
console.log('user', user)
// 使用toRaw, 參數(shù)是代理對(duì)象, 返回代理對(duì)象的目標(biāo)對(duì)象
console.log('toRaw(user)', toRaw(user))
console.log('toRaw(user) === obj', toRaw(user) === obj) // true
// 修改數(shù)據(jù)
const change = () => {
user.name = '李四'
}
return { user, change }
}
})
</script>
通過(guò)控制臺(tái)輸出結(jié)果, 你可以看出, toRaw
就是獲取代理對(duì)象的原目標(biāo)對(duì)象.
這是一個(gè)可以用于臨時(shí)讀取而不引起代理訪問(wèn)/跟蹤
開銷,或是寫入而不觸發(fā)更改的特殊方法切揭。不建議保存對(duì)原始對(duì)象的持久引用狞甚,請(qǐng)謹(jǐn)慎使用。
這句話來(lái)自于官網(wǎng), 這句話你可以這么理解,
代理對(duì)象具有響應(yīng)性, 可以理解為vue
在監(jiān)測(cè)這個(gè)數(shù)據(jù)的變化, 這個(gè)監(jiān)測(cè)會(huì)消耗性能. 如果你的操作不要觸發(fā)副作用, 就沒(méi)有必要 使用具有響應(yīng)性的代理對(duì)象.
比如調(diào)用接口時(shí)傳入的參數(shù), 就可以使用toRaw
去掉代理對(duì)象的外殼, 獲取到原始對(duì)象傳入接口.
3. markRaw
markRaw
函數(shù)的作用就是將一個(gè)對(duì)象轉(zhuǎn)為不可代理對(duì)象.
如果使用reactive
API , 也不會(huì)代理markRaw
函數(shù)返回的對(duì)象, 會(huì)直接返回原對(duì)象.
示例:
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ user }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { defineComponent, markRaw, reactive } from 'vue'
export default defineComponent({
setup() {
// 代理目標(biāo)對(duì)象
const obj = { name: '張三', age: 18 }
// 將obj原始對(duì)象標(biāo)記為不可代理
const markObj = markRaw(obj)
// reactive 處理的代理對(duì)象
const user = reactive(markObj)
// user 不是代理對(duì)象
console.log('user', user)
// 修改數(shù)據(jù)
const change = () => {
user.name = '李四'
}
return { user, change }
}
})
</script>
控制臺(tái)輸出:
通過(guò)控制臺(tái)輸出結(jié)果, 可以看出, 通過(guò)markRaw
處理過(guò)的對(duì)象具有一個(gè)__v_skip
的屬性, 用于標(biāo)記這個(gè)對(duì)象不能創(chuàng)建代理對(duì)象, 即響應(yīng)式數(shù)據(jù).
盡管你將該對(duì)象傳入reactive
, 返回的也不是一個(gè)代理對(duì)象, 而是原對(duì)象.
既然不是響應(yīng)數(shù)據(jù),修改user.name
時(shí), 就不會(huì)觸發(fā)視圖更新
該API的作用就是, 幫助你給一些你不希望創(chuàng)建為代理對(duì)象的原始對(duì)象添加標(biāo)記.
4. effectScope
4.1. effectScope 作用
在vue3
的使用過(guò)程中,我們可能會(huì)針對(duì)同一個(gè)響應(yīng)式數(shù)據(jù)創(chuàng)建多個(gè)副作用.比如computed
, watch
, watchEffect
等.
再次過(guò)程中, 如果關(guān)閉某個(gè)副作用, 比如watch
創(chuàng)建的偵聽器, 就需要通過(guò)返回值關(guān)閉. 那么多個(gè)副作用你就需要一個(gè)一個(gè)關(guān)閉. 使用相對(duì)麻煩
effectScope
字面意思就是副作用作用域, 可以理解為, 該函數(shù)創(chuàng)建一個(gè)作用域, 將所有的副作用放在共同一個(gè)作用域中, 如果以后想統(tǒng)一關(guān)閉副作用, 就可以使用作用域整體關(guān)閉.
4.2. effectScope
類型
function effectScope(detached?: boolean): EffectScope
interface EffectScope {
run<T>(fn: () => T): T | undefined // 如果作用域不活躍就為 undefined
stop(): void
}
effectScope
函數(shù)返回一個(gè)作用域?qū)ο? 即EffectScope
類型.
該作用域?qū)ο笊暇哂?code>run, stop
方法, 同時(shí)run
方法接收一個(gè)回調(diào)函數(shù)作為參數(shù).
4.3. effectScope 使用方式
通過(guò)effectScope
函數(shù)創(chuàng)建一個(gè) effect
作用域伴箩,可以捕獲其中所創(chuàng)建的響應(yīng)式副作用 (即計(jì)算屬性和偵聽器)入愧,這樣捕獲到的副作用可以一起處理鄙漏。
示例:
<template>
<div>
<h3>shallowReactive</h3>
<div>{{ count }}</div>
<button @click="change">修改數(shù)據(jù)源</button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, effectScope, markRaw, reactive, ref, shallowReactive, toRaw, watch, watchEffect } from 'vue'
export default defineComponent({
setup() {
// 創(chuàng)建ref 數(shù)據(jù)
const count = ref(10)
// 創(chuàng)建副作用作用域
const scope = effectScope()
// 控制臺(tái)輸出 effect 作用域
console.log("scope", scope);
// 收集運(yùn)行的副作用
scope.run(() => {
// 計(jì)算屬性副作用
const computedCount = computed(() => count.value * 2
)
// watch 偵聽副作用
watch(
count,
() => {
console.log('computedCount', computedCount.value)
console.log('watch count', count.value)
}
)
// watchEffect 副作用
watchEffect(() => {
console.log('watchEffect count', count.value)
})
})
console.log('scope', scope)
// 2秒以后關(guān)閉所有的副作用
setTimeout(() => {
scope.stop()
}, 2000)
// 修改數(shù)據(jù)
const change = () => {
count.value++
}
return { count, change }
}
})
</script>
控制臺(tái)輸出結(jié)果:
通過(guò)控制臺(tái)輸出的effect
作用域?qū)ο? 你可以看到, 作用域?qū)⒒卣{(diào)函數(shù)中的副作用進(jìn)行了收集, 存儲(chǔ)在effects
屬性上.
同時(shí)effect
作用域?qū)ο笤蛯?duì)象上具有run
收集副作用的方法, stop
關(guān)閉副作用的方法.
5. getCurrentScope
getCurrentScope
函數(shù)返回當(dāng)前活躍的 effect 作用域嗤谚。
在前一個(gè)API中, 給大家講解了effectScope
函數(shù), 該函數(shù)執(zhí)行后會(huì)返回一個(gè)effect
作用域, 通過(guò)調(diào)用effect
作用域?qū)ο蟮?code>run方法收集所有副作用. 我們就可以在run
方法的回調(diào)函數(shù)中, 通過(guò)getCurrentScope
函數(shù)獲取到正在活躍的effect
作用域?qū)ο?
示例:
// 創(chuàng)建副作用作用域
const scope = effectScope();
console.log("scope", scope);
// 收集運(yùn)行的副作用
scope.run(() => {
// 計(jì)算屬性副作用
const computedCount = computed(() => count.value * 2);
// watch 偵聽副作用
watch(count, () => {
console.log("computedCount", computedCount.value);
console.log("watch count", count.value);
});
// watchEffect 副作用
watchEffect(() => {
console.log("watchEffect count", count.value);
});
// 通過(guò) getCurrentScope() 獲取當(dāng)前真正活躍的 effect 作用域?qū)ο? const effectScope = getCurrentScope();
console.log("getCurrentScope", effectScope === scope);
// 控制臺(tái)輸出結(jié)果: getCurrentScope true
});
示例中, 我們通過(guò)effectScope
創(chuàng)建了一個(gè)effect
作用域?qū)ο? 當(dāng)調(diào)用該作用域?qū)ο蟮?code>run方法,傳入回調(diào)函數(shù), 會(huì)自動(dòng)執(zhí)行回調(diào)函數(shù), 收集副作用, 并將收集到的副作用保存在副作用effect
作用域中. 也就是說(shuō), 在執(zhí)行回調(diào)函數(shù)時(shí), 我們創(chuàng)建的scope
就是活躍的effect
作用域
之后,我們通過(guò)執(zhí)行getCurrentScope
函數(shù)獲取當(dāng)前活躍的副作用
作用域, 和之前我們創(chuàng)建的作用域?qū)Ρ? 發(fā)現(xiàn)getCurrentScope
獲取的就是我們創(chuàng)建的effect
作用域.
其實(shí)每一個(gè)組件都有一個(gè)effect
作用域, 用于收集組件內(nèi)所有的副作用. 組件更新函數(shù)本身也就是一個(gè)副作用. 這也就是響應(yīng)式數(shù)據(jù)變化后, 頁(yè)面會(huì)重新渲染的原因.
以及組件被銷毀后, vue3
會(huì)通過(guò)組件的effect
作用域清理組件內(nèi)收集的所有副作用
該API 在工作中并不常使用到. 甚至一個(gè)項(xiàng)目里連一次都不會(huì)用到.
6. onScopeDispose
該API 函數(shù)主要用于調(diào)試, 工作中也不怎么常用, 其作用就是在當(dāng)前活躍的副作用(effect)
作用域?qū)ο笊献?cè)一個(gè)調(diào)試的回調(diào)函數(shù). 在effect
作用域關(guān)閉時(shí), 會(huì)自動(dòng)調(diào)用注冊(cè)的回調(diào)函數(shù),.
示例:
// 創(chuàng)建副作用作用域
const scope = effectScope();
console.log("scope", scope);
// 收集運(yùn)行的副作用
scope.run(() => {
// 計(jì)算屬性副作用
const computedCount = computed(() => count.value * 2);
// watch 偵聽副作用
watch(count, () => {
console.log("computedCount", computedCount.value);
console.log("watch count", count.value);
});
// watchEffect 副作用
watchEffect(() => {
console.log("watchEffect count", count.value);
});
// 在當(dāng)前活躍的 effect 作用域?qū)ο笊献?cè)一個(gè)回調(diào)函數(shù)
onScopeDispose(() => {
console.log("當(dāng)前effectScope 停止");
});
});
// 2秒以后關(guān)閉所有的副作用
setTimeout(() => {
scope.stop();
}, 2000);
示例中, 我們?cè)?code>effectScope收集副作用時(shí), 通過(guò)onScopeDispose
函數(shù)注冊(cè)了一個(gè)回調(diào)函數(shù).
在effectScope
副作用作用域, 即scope
對(duì)象調(diào)用stop
方法時(shí), 會(huì)自動(dòng)執(zhí)行注冊(cè)的回調(diào)函數(shù). 多用于功能調(diào)試
7. 結(jié)語(yǔ)
至此, 就把vue3
中響應(yīng)式進(jìn)階API 中剩余的API函數(shù)給大家講完了, 這里比較常用的API 有triggerRef
, toRaw
, markRaw
, effectScope
, 其余兩個(gè)API 函數(shù)并不怎么常用.
這里尤其要注意effectScope
, 使用好了可以給代碼增色不少.