如何解決--在渲染函數(shù)之外調(diào)用插槽的問題
如果你是用 Vue 來開發(fā)項目的,那么,你曾經(jīng)有可能訪問 slot.default() 遇到如下問題:
Slot "default" invoked outside of the render function:
this will not track dependencies used in the slot.
Invoke the slot function inside the render function instead.
本文本中,將會解釋這個錯誤背后的原因以及如何解決這個問題。
插槽的調(diào)用需要發(fā)生在渲染函數(shù)或模板中。要抑制這個錯誤,我們只需要把代碼移到一個計算的屬性或從模板或渲染函數(shù)中調(diào)用的方法中。
“this will not track dependencies used in the slot” 指的是什么?
錯誤信息解釋了問題產(chǎn)生的本質(zhì)原因,但這個提示不是很清晰,無法幫助我們界定問題的本質(zhì)。下面,我們來詳細(xì)介紹下錯誤背后的原因產(chǎn)生。
this will not track dependencies used in the slot.
經(jīng)過一些調(diào)查,我做了一個可復(fù)現(xiàn)的代碼,并理解了在渲染函數(shù)之外使用slots.default()語法的含義。為了理解這個問題,我們先復(fù)習(xí)一下 Vue 的響應(yīng)式原理。
Vue 的響應(yīng)式性系統(tǒng)允許我們聲明屬性、數(shù)據(jù)和計算屬性,而不需要跟蹤它們的變化。響應(yīng)式性系統(tǒng)在幕后工作,確保我們的變量始終是最新的。
在Vue框架內(nèi),最常見的響應(yīng)式特征的情況是使用 computed:
計算屬性指的是一個變量,它可以被用來以有效和響應(yīng)式的方式修改和操作你的組件中的數(shù)據(jù)和屬性。
計算屬性的一個簡單例子是博客片段,我們把一篇完整的博客文章作為屬性傳遞,并把它截斷成一定數(shù)量的字符。另一個更常見的例子是一個簡單的變量,用來定義一個按鈕的文本,根據(jù)當(dāng)前的狀態(tài) "顯示 "或 "隱藏"。
舉例來說,在 "expanded"的值被改變之前,下面的屬性將永遠(yuǎn)不會再被運(yùn)行。
const buttonText = computed( () => {
return expanded.value ? 'Show less' : 'Show more';
});
除非 expanded 的值發(fā)生變化,否則上述方法不會再被觸發(fā)。Vue 在幕后所做的觀察 expanded 變量的工作就是所謂的 "跟蹤依賴性"。
你可能已經(jīng)意識到了,"跟蹤依賴" 這幾個字和Vue框架在試圖訪問插槽時產(chǎn)生的錯誤中提到的一樣。事實(shí)上,這個錯誤是為了告訴我們,在渲染函數(shù)之外使用slots.default()的語法,會使變量失去響應(yīng)性,因此它不會 "跟蹤" 任何可能影響它的變化。
拿上面的例子來說,失去依賴關(guān)系的跟蹤將意味著無論 expanded 的值是多少,按鈕都不會改變。
// 下面的代碼只是為了說明問題
// 我們只是假設(shè)了一個具有跟蹤依賴性的變量,這也是我們插槽發(fā)生的情況
const expanded = ref( false ); //Broken Tracking
console.log(buttonText)
// 輸出 "Show more"
expanded.value = true;
console.log(buttonText)
// 輸出: "Show more" 值沒有沒有改變,因為Vue無法跟蹤 expanded 的變化。
在我們的代碼庫中,未被追蹤的變量不是我們想要的東西,應(yīng)該要盡量的避免它。
如何確保 Vue 插槽被跟蹤依賴
接下來,我們分析下可以做些什么來確保我們的插槽有一個響應(yīng)式的跟蹤系統(tǒng),確保不會更新失敗
通過確保我們的槽調(diào)用發(fā)生在渲染函數(shù)和模板中,問題就可以解決了,正如錯誤信息中提到的那樣。
Invoke the slot function inside the render function
我們現(xiàn)在要介紹兩種不同的情況。第一種是在使用渲染函數(shù)時調(diào)用插槽函數(shù),第二種是在使用vue單文件組件的<template>部分。
在渲染函數(shù)中使用插槽
當(dāng)在一個有渲染函數(shù)的組件中使用插槽時,我們必須確保在渲染函數(shù)的 "return"語句中調(diào)用插槽函數(shù),而不是在 setup 中。
// 不好
import { h } from 'vue'
export default {
setup( props, { slots } ) {
const defaultSlot = slots.default();
return () => h('div', defaultSlot)
}
}
// 好
import { h } from 'vue'
export default {
setup( props, { slots } ) {
return () => h('div', slots.default())
}
}
在使用單一文件組件(SFC)時使用插槽
如果使用單文件組件并使用 <template> 塊聲明 HTML,你可能會認(rèn)為不能直接訪問渲染函數(shù),但事實(shí)并非如此。
當(dāng)我第一次遇到這個問題時,我花了一些時間試圖了解如何在渲染函數(shù)中移動插槽函數(shù),但在Spa 之后,我想起了 <template>標(biāo)簽是由編譯器為我們轉(zhuǎn)化成渲染函數(shù)的。
了解 <template> 塊和渲染函數(shù)是等價的,對我們定義解決問題的方法有很大幫助。事實(shí)上,為了消除警告并確保在我們的組件中跟蹤依賴關(guān)系,我們需要確保插槽的調(diào)用發(fā)生在HTML中(隨后被框架編譯成一個渲染函數(shù))。
舉個例子:
// 缺點(diǎn) - 如插槽改變,它將不會改變
<template>
<div :class="{ 'style-for-svg': isSvg }">
<slot></slot>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup( props, { slots } ) {
const isSvg = ref( false );
if( slots.default()[0].type === 'svg' ) {
isSvg.value = true;
}
return {
isSvg
}
}
}
</script>
// 優(yōu)點(diǎn):插槽改變,跟著變化
<template>
<div :class="{ 'style-for-svg': $slots.default()[0].type === 'svg' }">
<slot></slot>
</div>
</template>
<script>
export default {
setup( ) {
}
}
</script>
解決這個問題是很簡單的。直接在模板中加入函數(shù)調(diào)用,就可以解決我們的問題了。不幸的是,上面的解決方案代碼不夠簡潔。
那要怎么做呢?使用計算屬性。
在調(diào)查過程中,計算屬性也被編譯為渲染函數(shù)的一部分,可以用來使代碼更易讀,并且仍然保持變量的響應(yīng)式。
<template>
<div :class="{ 'style-for-svg': isSvg }">
<slot></slot>
</div>
</template>
<script>
import { computed } from 'vue'
export default {
setup( ) {
const isSvg = computed( () => {
return slots.default()[0].type === 'svg';
} );
return {
isSvg
}
}
}
</script>
總結(jié)
在開發(fā)Vue組件時,需要訪問插槽函數(shù)的情況并不常見,但如果你需要這樣做,我希望上面的解決方案能為你節(jié)省一些時間。