花3分钟来了解一下Vue3中的插槽到底是什么玩意
前言
插槽看著是一個比較神秘的東西,特別是作用域插槽還能讓我們在父組件里面直接訪問子組件里面的數據,這讓插槽變得更加神秘了。其實Vue3的插槽遠比你想象的簡單,這篇文章我們來揭開插槽的神秘面紗。
歐陽也在找工作,坐標成都求內推!
看個demo
我們先來看個常見的插槽demo,其中子組件代碼如下:
<template>
<slot></slot>
<slot name="header"></slot>
<slot name="footer" :desc="desc"></slot>
</template>
<script setup>
import { ref } from "vue";
const desc = ref("footer desc");
</script>
在子組件中我們定義了三個插槽,第一個是默認插槽,第二個是name為header的插槽,第三個是name為footer的插槽,并且將desc變量傳遞給了父組件。
我們再來看看父組件代碼如下:
<template>
<ChildDemo>
<p>default slot</p>
<template v-slot:header>
<p>header slot</p>
</template>
<template v-slot:footer="{ desc }">
<p>footer slot: {{ desc }}</p>
</template>
</ChildDemo>
</template>
<script setup lang="ts">
import ChildDemo from "./child.vue";
</script>
在父組件中的代碼很常規,分別使用v-slot指令給header和footer插槽傳遞內容。
來看看編譯后的父組件
我們在瀏覽器中來看看編譯后的父組件代碼,簡化后如下:
import {
createBlock as _createBlock,
createElementVNode as _createElementVNode,
openBlock as _openBlock,
toDisplayString as _toDisplayString,
withCtx as _withCtx,
} from "/node_modules/.vite/deps/vue.js?v=64ab5d5e";
const _sfc_main = /* @__PURE__ */ _defineComponent({
// ...省略
});
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
_openBlock(),
_createBlock($setup["ChildDemo"], null, {
header: _withCtx(
() =>
_cache[0] ||
(_cache[0] = [
_createElementVNode(
"p",
null,
"header slot",
-1
/* HOISTED */
),
])
),
footer: _withCtx(({ desc }) => [
_createElementVNode(
"p",
null,
"footer slot: " + _toDisplayString(desc),
1
/* TEXT */
),
]),
default: _withCtx(() => [
_cache[1] ||
(_cache[1] = _createElementVNode(
"p",
null,
"default slot",
-1
/* HOISTED */
)),
]),
_: 1,
/* STABLE */
})
);
}
export default /* @__PURE__ */ _export_sfc(_sfc_main, [
["render", _sfc_render],
]);
從上面的代碼可以看到template中的代碼編譯后變成了render函數。
在render函數中_createBlock($setup["ChildDemo"]表示在渲染子組件ChildDemo,并且在執行createBlock函數時傳入了第三個參數是一個對象。對象中包含header、footer、default三個方法,這三個方法對應的是子組件ChildDemo`中的三個插槽。執行這三個方法就會生成這三個插槽對應的虛擬DOM。
并且我們觀察到插槽footer處的方法還接收一個對象作為參數,并且對象中還有一個desc字段,這個字段就是子組件傳遞給父組件的變量。
方法最外層的withCtx方法是為了給插槽的方法注入當前組件實例的上下文。
通過上面的分析我們可以得出一個結論:在父組件中插槽經過編譯后會變成一堆由插槽name組成的方法,執行這些方法就會生成插槽對應的虛擬DOM。默認插槽就是default方法,方法接收的參數就是子組件中插槽給父組件傳遞的變量。但是有一點要注意,在父組件中我們只是定義了這三個方法,執行這三個方法的地方卻不是在父組件,而是在子組件。
編譯后的子組件
我們來看看編譯后的子組件,簡化后代碼如下:
import {
createElementBlock as _createElementBlock,
Fragment as _Fragment,
openBlock as _openBlock,
renderSlot as _renderSlot,
} from "/node_modules/.vite/deps/vue.js?v=64ab5d5e";
const _sfc_main = {
// ...省略
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
_openBlock(),
_createElementBlock(
_Fragment,
null,
[
_renderSlot(_ctx.$slots, "default"),
_renderSlot(_ctx.$slots, "header"),
_renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),
],
64 /* STABLE_FRAGMENT */
)
);
}
export default /*#__PURE__*/ _export_sfc(_sfc_main, [["render", _sfc_render]]);
同樣的我們觀察里面的render函數,里面的這個:
[
_renderSlot(_ctx.$slots, "default"),
_renderSlot(_ctx.$slots, "header"),
_renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),
]
對應的就是源代碼里面的這個:
<slot></slot>
<slot name="header"></slot>
<slot name="footer" :desc="desc"></slot>
在上面我們看見一個$slots對象,這個是什么東西呢?
其實useSlots就是返回的$slots對象,我們直接在控制臺中使用useSlots打印出$slots對象看看,代碼如下:
<script setup>
import { ref, useSlots } from "vue";
const slots = useSlots();
console.log(slots);
const desc = ref("footer desc");
</script>
我們來瀏覽器中看看此時打印的slots對象是什么樣的,如下圖:
從上圖中可以看到slots對象好像有點熟悉,這個對象中包含default、footer、header這三個方法,其實這個slots對象就是前面我們講的父組件中定義的那個對象,執行對象的default、footer、header方法就會生成對應插槽的虛擬DOM。
前面我們講了在父組件中會定義default、footer、header這三個方法,那這三個方法又是在哪里執行的呢?
答案是:在子組件里面執行的。
在執行_renderSlot(_ctx.$slots, "default")方法時就會去執行slots對象里面的default方法,這個是renderSlot函數的代碼截圖:
從上圖中可以看到在renderSlot函數中首先會使用slots[name]拿到對應的插槽方法,如果執行的是_renderSlot(_ctx.$slots, "footer", { desc: $setup.desc }),這里拿到的就是footer方法。
然后就是執行footer方法,前面我們講過了這里的footer方法需要接收參數,并且從參數中結構出desc屬性。剛好我們執行renderSlot方法時就給他傳了一個對象,對象中就有一個desc屬性,這不就對上了嗎!
并且由于執行footer方法會生成虛擬DOM,所以footer生成的虛擬DOM是屬于子組件里面的,同理footer對應的真實DOM也是屬于在子組件的DOM樹里面。
通過上面的分析我們可以得出一個結論就是:子組件中的插槽實際就是在執行父組件插槽對應的方法,在執行方法時可以將子組件的變量傳遞給父組件,這就是作用域插槽的原理。
總結
這篇文章我們講了經過編譯后父組件的插槽會被編譯成一堆方法,這些方法組成的對象就是$slots對象。在子組件中會去執行這些方法,并且可以將子組件的變量傳給父組件,由父組件去接收參數,這就是作用域插槽的原理。了解了這個后當我們在useSlots、jsx、tsx中定義和使用插槽就不會那么迷茫了。
關注公眾號:【前端歐陽】,給自己一個進階vue的機會
總結
以上是生活随笔為你收集整理的花3分钟来了解一下Vue3中的插槽到底是什么玩意的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HttpClient 进行soap请求
- 下一篇: tp5.1的安装与运行流程