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