你不知道的vue3:使用runWithContext实现在非 setup 期间使用inject
前言
日常開(kāi)發(fā)時(shí)有些特殊的場(chǎng)景需要在非 setup 期間調(diào)用inject函數(shù),比如app中使用provide注入的配置信息需要在發(fā)送http請(qǐng)求時(shí)帶上傳給后端。對(duì)此我們希望不在每個(gè)發(fā)起請(qǐng)求的地方去修改,而是在發(fā)起請(qǐng)求前的攔截進(jìn)行統(tǒng)一處理,對(duì)此我們就需要在攔截請(qǐng)求的函數(shù)中使用inject拿到app注入的配置信息。
為什么只能在setup 期間調(diào)用inject函數(shù)
inject的用法大家應(yīng)該都清楚,是一個(gè)用于注入依賴的函數(shù),它可以將父組件或根組件 app 中通過(guò) provide 提供的相同 key 的值注入到當(dāng)前組件中。
我們先來(lái)看看簡(jiǎn)化后的provider和inject的源碼,其實(shí)非常簡(jiǎn)單。
provider函數(shù)源碼
我們先來(lái)看看簡(jiǎn)化后的provider函數(shù)源碼,其實(shí)很簡(jiǎn)單:
export function provide(
key,
value,
) {
//拿到當(dāng)前組件的vue實(shí)例提供的provides對(duì)象
let provides = currentInstance.provides
//拿到父組件的vue實(shí)例提供的provides對(duì)象
const parentProvides =
currentInstance.parent && currentInstance.parent.provides
// 如果父組件和當(dāng)前組件的provides對(duì)象相等
if (parentProvides === provides) {
// 基于父組件的provides對(duì)象拷貝出一個(gè)新的對(duì)象
provides = currentInstance.provides = Object.create(parentProvides)
}
// 如果provides對(duì)象中有相同的key,那么就會(huì)直接覆蓋。
provides[key] = value
}
在初始化一個(gè)vue實(shí)例的時(shí)候會(huì)將父組件的provides對(duì)象賦值給當(dāng)前實(shí)例的provides對(duì)象,所以當(dāng)?shù)谝淮?code>provide方法被調(diào)用后,會(huì)判斷當(dāng)前的provides對(duì)象是否等于父組件provides對(duì)象,如果相等就會(huì)基于父組件實(shí)例的provides對(duì)象拷貝一個(gè)新的provides對(duì)象。
此時(shí)父組件和子組件的provides對(duì)象經(jīng)過(guò)Object.create(parentProvides)后就已經(jīng)不是同一個(gè)對(duì)象了。如果子組件和父組件provide對(duì)象中都有相同的key,經(jīng)過(guò)provides[key] = value后就會(huì)將原本父組件賦值的相同key的值“覆蓋”掉。因?yàn)楦附M件的provides對(duì)象是從他的父組件provides對(duì)象拷貝的而來(lái),所以子組件包含了父組件鏈上的所有的provide提供的值。
機(jī)智如你現(xiàn)在應(yīng)該能夠理解為什么官網(wǎng)會(huì)說(shuō)“父組件鏈上多個(gè)組件對(duì)同一個(gè) key 提供了值,那么離得更近的組件將會(huì)“覆蓋”鏈上更遠(yuǎn)的組件所提供的值”。
inject函數(shù)源碼
現(xiàn)在我們?cè)賮?lái)看看簡(jiǎn)化后的inject函數(shù)源碼,同樣也非常簡(jiǎn)單:
export function inject(
key,
) {
//currentInstance是一個(gè)存儲(chǔ)當(dāng)前vue實(shí)例的全局變量,在vue組件初始化時(shí)會(huì)賦值。
//初始化完成后會(huì)被重置為null
const instance = currentInstance
if (instance || currentApp) {
// 拿到父組件或者currentApp中提供的provides對(duì)象
const provides = instance
? instance.parent.provides
: currentApp!._context.provides
// 從provides對(duì)象中拿到相同key的值
if (provides && key in provides) {
return provides[key]
}
} else if (__DEV__) {
// 不是在setup中或者runWithContext中調(diào)用,就會(huì)發(fā)出警告
warn(`inject() can only be used inside setup() or functional components.`)
}
}
我們首先來(lái)看看currentInstance這個(gè)全局變量,setup只會(huì)在初始化vue實(shí)例的時(shí)候執(zhí)行一次,在setup期間currentInstance會(huì)被賦值為當(dāng)前組件的vue實(shí)例。等vue實(shí)例初始化完成后currentInstance就會(huì)被賦值為null。
前面我們已經(jīng)介紹了組件的provides對(duì)象中是包含了父組件鏈上的所有provides的key,所以我們這里只需要從當(dāng)前vue實(shí)例instance的parent中的provides對(duì)象中就可以取出注入相同key的值。
看到這里相信你已經(jīng)知道了為什么只能在setup 期間調(diào)用調(diào)用inject方法了。因?yàn)橹挥性?code>setup期間currentInstance全局變量的值為當(dāng)前組件的vue實(shí)例對(duì)象,當(dāng)vue實(shí)例初始化完成后currentInstance已經(jīng)被賦值為null。所以當(dāng)我們?cè)诜?code>setup 期間調(diào)用inject方法會(huì)警告:inject() can only be used inside setup() or functional components.
至于currentApp其實(shí)是另外一個(gè)全局變量,在調(diào)用app.runWithContext方法時(shí)會(huì)給它賦值,這個(gè)下一節(jié)我們講app.runWithContext的時(shí)候會(huì)詳細(xì)講。
使用app.runWithContext()打破inject只能在setup 期間調(diào)用的限制
app.runWithContext()的官方解釋為“使用當(dāng)前應(yīng)用作為注入上下文執(zhí)行回調(diào)函數(shù)”。這個(gè)解釋乍一看很容易一臉懵逼,不著急我慢慢給你解釋。
我們先來(lái)看看runWithContext方法接收的參數(shù)和返回的值。這個(gè)方法接收一個(gè)參數(shù),參數(shù)是一個(gè)回調(diào)函數(shù)。這個(gè)回調(diào)函數(shù)會(huì)在app.runWithContext()執(zhí)行時(shí)被立即執(zhí)行,并且app.runWithContext()的返回值就是回調(diào)函數(shù)的返回值。
我們?cè)賮?lái)看一個(gè)使用runWithContext的例子,這行代碼是攔截請(qǐng)求時(shí)才執(zhí)行。作用是拿到app中注入的userType字段,注意不是在setup期間執(zhí)行。
const userType = app.runWithContext(() => {
// 拿到app中注入的userType字段
return inject("userType");
});
按照我們前一節(jié)的分析,inject需要在setup中執(zhí)行才能拿到當(dāng)前的vue實(shí)例。但是之前還有一個(gè)currentApp變量我們沒(méi)有解釋,再來(lái)回顧一下上一節(jié)的inject源碼。如果我們拿不到當(dāng)前的vue實(shí)例,就會(huì)去看一下全局變量currentApp是否存在,如果存在那么就從currentApp中去拿provides對(duì)象。這個(gè)currentApp就是官方解釋的“注入的上下文”,所以我們才可以在非setup期間執(zhí)行inject,并且還可以拿到注入的值。
if (instance || currentApp) {
// 拿到父組件或者currentApp中提供的provides對(duì)象
const provides = instance
? instance.parent.provides
: currentApp!._context.provides
// 從provides對(duì)象中拿到相同key的值
if (provides && key in provides) {
return provides[key]
}
}
我們?cè)賮?lái)看看runWithContext的源碼,其實(shí)非常簡(jiǎn)單。
runWithContext(fn) {
// 將調(diào)用runWithContext方法的對(duì)象賦值給全局對(duì)象currentApp
currentApp = app
try {
// 立即執(zhí)行傳入的回調(diào)函數(shù)
return fn()
} finally {
currentApp = null
}
}
這里的app就是調(diào)用runWithContext方法的對(duì)象,你可以簡(jiǎn)單的理解為this。調(diào)用app.runWithContext()就會(huì)將app對(duì)象賦值給全局變量currentApp,然后會(huì)立即執(zhí)行傳入的回調(diào)fn。當(dāng)執(zhí)行到回調(diào)中的inject("userType")時(shí),由于我們?cè)谏弦恍写a已經(jīng)給全局變量currentApp賦值為app了,所以就可以從app中拿到對(duì)應(yīng)key的provider值。
總結(jié)
這篇文章我們先介紹了由于inject執(zhí)行期間需要拿到當(dāng)前的vue實(shí)例,然后才能從父組件提供的provides對(duì)象中找到相同key的值。如果我們?cè)诜?setup 期間執(zhí)行,那么就拿不到當(dāng)前vue實(shí)例。也找不到父組件,當(dāng)然inject也沒(méi)法拿到注入的值。
在一些場(chǎng)景中我們確實(shí)需要在非 setup 期間執(zhí)行inject,這時(shí)我們就可以使用app.runWithContext()將app對(duì)象作為注入上下文執(zhí)行回調(diào)函數(shù)。然后在inject執(zhí)行期間就能從app中拿到提供的provides對(duì)象中相同key的值。
如果我的文章對(duì)你有點(diǎn)幫助,歡迎關(guān)注公眾號(hào):【歐陽(yáng)碼農(nóng)】,文章在公眾號(hào)首發(fā)。你的支持就是我創(chuàng)作的最大動(dòng)力,感謝感謝!
總結(jié)
以上是生活随笔為你收集整理的你不知道的vue3:使用runWithContext实现在非 setup 期间使用inject的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: GeckoFx v45浏览器控件实现文件
- 下一篇: 进击的IOSer