一直對Vue中的slot插槽比較感興趣,下面是本身的一些簡單理解,但願能夠幫助你們更好的理解slot插槽vue
<li class="dx-li">
<slot>
你好 掘金!
</slot>
</li>
複製代碼
<ul>
<dx-li>
hello juejin!
</dx-li>
</ul>
複製代碼
module.exports={
render:function (){
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
// 其中_vm.v爲createTextVNode建立文本VNode的函數
return _c('ul',
[_c('dx-li', [_vm._v("hello juejin!")])],
1)
},
staticRenderFns: []
}
複製代碼
傳遞的插槽內容'hello juejin!'會被編譯成dx-li子組件VNode節點的子節點。
module.exports={
render:function (){
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
// 其中_vm._v 函數爲renderSlot函數
return _c('li',
{staticClass: "dx-li" },
[_vm._t("default", [_vm._v("你好 掘金!")])],
2
)
},
staticRenderFns: []
}
複製代碼
初始化dx-li子組件vue實例過程當中,會調用initRender函數:function initRender (vm) {
...
// 其中_renderChildren數組,存儲爲 'hello juejin!'的VNode節點;renderContext通常爲父組件Vue實例
這裏爲dx-ul組件實例
vm.$slots = resolveSlots(options._renderChildren, renderContext);
...
}
複製代碼
其中resolveSlots函數爲:/**
* 主要做用是將children VNodes轉化成一個slots對象.
*/
export function resolveSlots (
children: ?Array<VNode>,
context: ?Component
): { [key: string]: Array<VNode> } {
const slots = {}
// 判斷是否有children,便是否有插槽VNode
if (!children) {
return slots
}
// 遍歷父組件節點的孩子節點
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
// data爲VNodeData,保存父組件傳遞到子組件的props以及attrs等
const data = child.data
/* 移除slot屬性
* <span slot="abc"></span>
* 編譯成span的VNode節點data = {attrs:{slot: "abc"}, slot: "abc"},因此這裏刪除該節點attrs的slot
*/
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot
}
/* 判斷是否爲具名插槽,若是爲具名插槽,還須要子組件/函數子組件渲染上下文一致。主要做用:
*當須要向子組件的子組件傳遞具名插槽時,不會保持插槽的名字。
* 舉個栗子:
* child組件template:
* <div>
* <div class="default"><slot></slot></div>
* <div class="named"><slot name="foo"></slot></div>
* </div>
* parent組件template:
* <child><slot name="foo"></slot></child>
* main組件template:
* <parent><span slot="foo">foo</span></parent>
* 此時main渲染的結果:
* <div>
* <div class="default"><span slot="foo">foo</span></div>
<div class="named"></div>
* </div>
*/
if ((child.context === context || child.fnContext === context) &&
data && data.slot != null
) {
const name = data.slot
const slot = (slots[name] || (slots[name] = []))
// 這裏處理父組件採用template形式的插槽
if (child.tag === 'template') {
slot.push.apply(slot, child.children || [])
} else {
slot.push(child)
}
} else {
// 返回匿名default插槽VNode數組
(slots.default || (slots.default = [])).push(child)
}
}
// 忽略僅僅包含whitespace的插槽
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots
}
複製代碼
而後掛載dx-li組件時,會調用dx-li組件render函數,在此過程當中會調用renderSlot函數:export function renderSlot (
name: string, // 子組件中slot的name,匿名default
fallback: ?Array<VNode>, // 子組件插槽中默認內容VNode數組,若是沒有插槽內容,則顯示該內容
props: ?Object, // 子組件傳遞到插槽的props
bindObject: ?Object // 針對<slot v-bind="obj"></slot> obj必須是一個對象
): ?Array<VNode> {
// 判斷父組件是否傳遞做用域插槽
const scopedSlotFn = this.$scopedSlots[name]
let nodes
if (scopedSlotFn) { // scoped slot
props = props || {}
if (bindObject) {
if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) {
warn(
'slot v-bind without argument expects an Object',
this
)
}
props = extend(extend({}, bindObject), props)
}
// 傳入props生成相應的VNode
nodes = scopedSlotFn(props) || fallback
} else {
// 若是父組件沒有傳遞做用域插槽
const slotNodes = this.$slots[name]
// warn duplicate slot usage
if (slotNodes) {
if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {
warn(
`Duplicate presence of slot "${name}" found in the same render tree ` +
`- this will likely cause render errors.`,
this
)
}
// 設置父組件傳遞插槽的VNode._rendered,用於後面判斷是否有重名slot
slotNodes._rendered = true
}
// 若是沒有傳入插槽,則爲默認插槽內容VNode
nodes = slotNodes || fallback
}
// 若是還須要向子組件的子組件傳遞slot
/*舉個栗子:
* Bar組件: <div class="bar"><slot name="foo"/></div>
* Foo組件:<div class="foo"><bar><slot slot="foo"/></bar></div>
* main組件:<div><foo>hello</foo></div>
* 最終渲染:<div class="foo"><div class="bar">hello</div></div>
*/
const target = props && props.slot
if (target) {
return this.$createElement('template', { slot: target }, nodes)
} else {
return nodes
}
}
複製代碼
<li class="dx-li">
<slot str="你好 掘金!">
hello juejin!
</slot>
</li>
複製代碼
<ul>
<dx-li>
<span slot-scope="scope">
{{scope.str}}
</span>
</dx-li>
</ul>
複製代碼
module.exports={
render:function (){
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
return _c('ul', [_c('dx-li', {
// 能夠編譯生成一個對象數組
scopedSlots: _vm._u([{
key: "default",
fn: function(scope) {
return _c('span',
{},
[_vm._v(_vm._s(scope.str))]
)
}
}])
})], 1)
},
staticRenderFns: []
}
複製代碼
其中 _vm._u函數:node
function resolveScopedSlots (
fns, // 爲一個對象數組,見上文scopedSlots
res
) {
res = res || {};
for (var i = 0; i < fns.length; i++) {
if (Array.isArray(fns[i])) {
// 遞歸調用
resolveScopedSlots(fns[i], res);
} else {
res[fns[i].key] = fns[i].fn;
}
}
return res
}
複製代碼
子組件的後續渲染過程與slots相似。scoped slots原理與slots基本是一致,不一樣的是編譯父組件模板時,會生成一個返回結果爲VNode的函數。當子組件匹配到父組件傳遞做用域插槽函數時,調用該函數生成對應VNode。數組
其實slots/scoped slots 原理是很是簡單的,咱們只需明白一點vue在渲染組件時,是根據VNode渲染實際DOM元素的。bash
slots是將父組件編譯生成的插槽VNode,在渲染子組件時,放置到對應子組件渲染VNode樹中。app
scoped slots是將父組件中插槽內容編譯成一個函數,在渲染子組件時,傳入子組件props,生成對應的VNode。最後子組件,根據組件render函數返回VNode節點樹,update渲染真實DOM元素。同時,能夠看出跨組件傳遞插槽也是能夠的,可是必須注意具名插槽傳遞。函數
以上是本人對於Slots的一些淺顯理解,關於slot還有不少其餘的知識點。但願能夠幫助你們。因爲本人水平有限,有什麼錯誤和不足,但願指出。ui