一、slot是什么
在HTML中 slot
元素 ,作為 Web Components
技術套件的一部分,是Web組件內的一個占位符
該占位符可以在后期使用自己的標記語言填充,這樣就可以創建單獨的DOM樹,並將它與其它的組件組合在一起。
在Vue
中的概念也是如此
Slot
藝名插槽,花名“占坑”,我們可以理解為solt
在組件模板中占好了位置,當使用該組件標簽時候,組件標簽里面的內容就會自動填坑(替換組件模板中slot
位置),作為承載分發內容的出口
可以將其類比為插卡式的FC游戲機,游戲機暴露卡槽(插槽)讓用戶插入不同的游戲磁條(自定義內容)
二、使用場景
通過插槽讓用戶可以拓展組件,去更好地復用組件和對其做定制化處理
如果父組件在使用到一個復用組件的時候,獲取這個組件在不同的地方有少量的更改,如果去重寫組件是一件不明智的事情
通過slot
插槽向組件內部指定位置傳遞內容,完成這個復用組件在不同場景的應用
比如布局組件、表格列、下拉選、彈框顯示內容等
三、分類
slot
可以分來以下三種:
-
默認插槽
-
具名插槽
-
作用域插槽
默認插槽
子組件用<slot>
標簽來確定渲染的位置,標簽里面可以放DOM
結構,當父組件使用的時候沒有往插槽傳入內容,標簽內DOM
結構就會顯示在頁面
父組件在使用的時候,直接在子組件的標簽內寫入內容即可
子組件Child.vue
<template> <slot> <p>插槽后備的內容</p> </slot> </template>
父組件
<Child> <div>默認插槽</div> </Child>
具名插槽
子組件用name
屬性來表示插槽的名字,不傳為默認插槽
父組件中在使用時在默認插槽的基礎上加上slot
屬性,值為子組件插槽name
屬性值
子組件Child.vue
<template> <slot>插槽后備的內容</slot> <slot name="content">插槽后備的內容</slot> </template>
父組件
<child> <template v-slot:default>具名插槽</template> <!-- 具名插槽⽤插槽名做參數 --> <template v-slot:content>內容...</template> </child>
作用域插槽
子組件在作用域上綁定屬性來將子組件的信息傳給父組件使用,這些屬性會被掛在父組件v-slot
接受的對象上
父組件中在使用時通過v-slot:
(簡寫:#)獲取子組件的信息,在內容中使用
子組件Child.vue
<template> <slot name="footer" testProps="子組件的值"> <h3>沒傳footer插槽</h3> </slot> </template>
父組件
<child> <!-- 把v-slot的值指定為作⽤域上下⽂對象 --> <template v-slot:default="slotProps"> 來⾃⼦組件數據:{{slotProps.testProps}} </template> <template #default="slotProps"> 來⾃⼦組件數據:{{slotProps.testProps}} </template> </child>
小結:
-
v-slot
屬性只能在<template>
上使用,但在只有默認插槽時可以在組件標簽上使用 -
默認插槽名為
default
,可以省略default直接寫v-slot
-
縮寫為
#
時不能不寫參數,寫成#default
-
可以通過解構獲取
v-slot={user}
,還可以重命名v-slot="{user: newName}"
和定義默認值v-slot="{user = '默認值'}"
四、原理分析
slot
本質上是返回VNode
的函數,一般情況下,Vue
中的組件要渲染到頁面上需要經過template -> render function -> VNode -> DOM
過程,這里看看slot
如何實現:
編寫一個buttonCounter
組件,使用匿名插槽
Vue.component('button-counter', { template: '<div> <slot>我是默認內容</slot></div>' })
使用該組件
new Vue({ el: '#app', template: '<button-counter><span>我是slot傳入內容</span></button-counter>', components:{buttonCounter} })
獲取buttonCounter
組件渲染函數
(function anonymous() { with(this){return _c('div',[_t("default",[_v("我是默認內容")])],2)} })
_v
表示創建普通文本節點,_t
表示渲染插槽的函數
渲染插槽函數renderSlot
(做了簡化)
function renderSlot ( name, fallback, props, bindObject ) { // 得到渲染插槽內容的函數 var scopedSlotFn = this.$scopedSlots[name]; var nodes; // 如果存在插槽渲染函數,則執行插槽渲染函數,生成nodes節點返回 // 否則使用默認值 nodes = scopedSlotFn(props) || fallback; return nodes; }
name
屬性表示定義插槽的名字,默認值為default
,fallback
表示子組件中的slot
節點的默認值
關於this.$scopredSlots
是什么,我們可以先看看vm.slot
function initRender (vm) { ... vm.$slots = resolveSlots(options._renderChildren, renderContext); ... }
resolveSlots
函數會對children
節點做歸類和過濾處理,返回slots
function resolveSlots ( children, context ) { if (!children || !children.length) { return {} } var slots = {}; for (var i = 0, l = children.length; i < l; i++) { var child = children[i]; var data = child.data; // remove slot attribute if the node is resolved as a Vue slot node if (data && data.attrs && data.attrs.slot) { delete data.attrs.slot; } // named slots should only be respected if the vnode was rendered in the // same context. if ((child.context === context || child.fnContext === context) && data && data.slot != null ) { // 如果slot存在(slot="header") 則拿對應的值作為key var name = data.slot; var slot = (slots[name] || (slots[name] = [])); // 如果是tempalte元素 則把template的children添加進數組中,這也就是為什么你寫的template標簽並不會渲染成另一個標簽到頁面 if (child.tag === 'template') { slot.push.apply(slot, child.children || []); } else { slot.push(child); } } else { // 如果沒有就默認是default (slots.default || (slots.default = [])).push(child); } } // ignore slots that contains only whitespace for (var name$1 in slots) { if (slots[name$1].every(isWhitespace)) { delete slots[name$1]; } } return slots }
_render
渲染函數通過normalizeScopedSlots
得到vm.$scopedSlots
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
);
作用域插槽中父組件能夠得到子組件的值是因為在renderSlot
的時候執行會傳入props
,也就是上述_t
第三個參數,父組件則能夠得到子組件傳遞過來的值
轉自:https://blog.csdn.net/weixin_44475093/article/details/111244171