一、概念
-
// 有name的屬於具名插槽,沒有name屬於匿名插槽
-
<app>
-
<div slot="a">xxxx</div>
-
<div slot="b">xxxx</div>
-
</app>
-
<slot name="a"></slot>
-
<slot name="b"></slot>
-
-
普通插槽渲染的位置是在它的父組件里面,而不是在子組件里面
-
作用域插槽渲染是在子組件里面
1.插槽slot
在渲染父組件的時候,會將插槽中的先渲染。
創建組件虛擬節點時,會將組件的兒子的虛擬節點保存起來。當初始化組件時,通過插槽屬性將兒 子進行分類 {a:[vnode],b[vnode]}
渲染組件時會拿對應的slot屬性的節點進行替換操作。(插槽的作用域為父組件,插槽中HTML模板顯示不顯示、以及怎樣顯示由父組件來決定)
有name的父組件通過html模板上的slot屬性關聯具名插槽。沒有slot屬性的html模板默認關聯匿名插槽。
2.作用域插槽slot-scope
作用域插槽在解析的時候,不會作為組件的孩子節點。會解析成函數,當子組件渲染時,會調用此函數進行渲染。
或者可以說成作用域插槽是子組件可以在slot標簽上綁定屬性值,在父組件可以拿到子組件的數據,通過子組件綁定數據傳遞給父組件。(插槽的作用域為子組件)
-
子組件:
-
<slot :nickName= "'wthreesix'"></slot>
-
-
父組件:
-
<slot-child>
-
<template slot-scope="scope">
-
<div>{{scope.nickName}}</div>
-
</template>
-
</slot-child>
二、源碼圖
三、插槽渲染分析
1.插槽
-
// 父組件:
-
const VueTemplateCompiler = require('vue-template-compiler');
-
let ele = VueTemplateCompiler.compile(`
-
<my-component>
-
<div slot="header">node</div>
-
<div>react</div>
-
<div slot="footer">vue</div>
-
</my-component>
-
`)
-
-
將上面組件編譯后:
-
/**
-
with(this) {
-
return _c('my-component', // _c 創建虛擬dom
-
[ // 組件中包含子節點
-
_c('div', // 第一個是一個插槽,插槽叫header
-
{
-
attrs: {
-
"slot": "header"
-
},
-
slot: "header"
-
},
-
[_v("node")] // 將里面內容存起來了(_v是createTextVNode創建文本節點)
-
),
-
_v(" "),
-
_c('div',[_v("react")]),
-
_v(" "),
-
_c('div', {
-
attrs: {
-
"slot": "footer"
-
},
-
slot: "footer"
-
}, [_v("vue")])
-
]
-
)
-
}
-
*/
-
在調render方法的時候已經將組件全部渲染好了
-
-
// 子組件
-
let ele = VueTemplateCompiler.compile(`
-
<div>
-
<slot name="header"></slot>
-
<slot name="footer"></slot>
-
<slot></slot>
-
</div>
-
`);
-
/**
-
with(this) {
-
// 渲染的時候會找header對應的是誰
-
return _c('div', [
-
_t("header"), _v(" "),
-
_t("footer"), _v(" "),
-
_t("default")], 2)
-
}
-
當找到就會換過來,如下:
-
return _c('div', [_v("node"), _v(" "), _v("vue"), _v(" "),
-
_t("default")], 2)
-
}
-
}
-
**/
-
// _t是renderSlot
-
-
插槽就是一個替換的過程,將父組件渲染好的結果直接替換到自己的上面,創建的過程相當於在父組件渲染的
2.作用域插槽
-
父組件:
-
let ele = VueTemplateCompiler.compile(`
-
<app>
-
<div slot-scope="msg" slot="footer">{{msg.a}}</div>
-
</app>
-
`);
-
/**
-
with(this) {
-
// 編譯出的不是一個child,而是一個屬性scopedSlots
-
return _c('app', {
-
scopedSlots: _u([{
-
key: "footer",
-
fn: function (msg) { 將子節點變成一個函數,這個函數不調用就不會去渲染這個子節點
-
return _c('div', {}, [_v(_s(msg.a))])
-
}
-
}])
-
})
-
}
-
}
-
在初始化的時候並不會渲染子節點
-
*/
-
-
子組件:
-
const VueTemplateCompiler = require('vue-template-compiler');
-
VueTemplateCompiler.compile( `
-
// 當我們在寫slot去執行的時候進行渲染
-
<div>
-
<slot name="footer" a="1" b="2"></slot>
-
</div>
-
`);
-
/**
-
with(this) {
-
// 去找footer,找見會調用上面的那個函數,並且將屬性傳入到這個函數里面,這時候才會把這節點進行渲染,完成之后替換調
-
return _c('div', [_t("footer", null, {
-
"a": "1",
-
"b": "2"
-
})], 2)
-
}
-
**/
-
-
// 作用域插槽的內容會被渲染成一個函數
-
// 作用域插槽渲染是在當前組件的內部,不是在父組件中
四、源碼
1.initRender(初始化render,構建vm.$slots)
-
export function initRender (vm: Component) {
-
vm.$slots = resolveSlots(options._renderChildren, renderContext)
-
vm.$scopedSlots = emptyObject
-
}
2.resolveSlots(映射slot名字和對應的vnode)
-
export function resolveSlots (
-
children: ?Array<VNode>,
-
context: ?Component
-
): { [key: string]: Array<VNode> } {
-
if (!children || !children.length) {
-
return {}
-
}
-
const slots = {}
-
for (let i = 0, l = children.length; i < l; i++) {
-
const child = children[i]
-
const 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
-
) {
-
const name = data.slot
-
const slot = (slots[name] || (slots[name] = []))
-
if (child.tag === 'template') {
-
slot.push.apply(slot, child.children || [])
-
} else {
-
slot.push(child)
-
}
-
} else {
-
(slots.default || (slots.default = [])).push(child)
-
}
-
}
-
// ignore slots that contains only whitespace
-
for (const name in slots) {
-
if (slots[name].every(isWhitespace)) {
-
delete slots[name]
-
}
-
}
-
return slots
-
}
3.normalizeScopedSlots(core/vdom/helpers/normalize-scoped-slot.js)
-
export function normalizeScopedSlots (
-
slots: { [key: string]: Function } | void,
-
normalSlots: { [key: string]: Array<VNode> },
-
prevSlots?: { [key: string]: Function } | void
-
): any {
-
let res
-
const hasNormalSlots = Object.keys(normalSlots).length > 0
-
const isStable = slots ? !!slots.$stable : !hasNormalSlots
-
const key = slots && slots.$key
-
if (!slots) {
-
res = {}
-
} else if (slots._normalized) {
-
// fast path 1: child component re-render only, parent did not change
-
return slots._normalized
-
} else if (
-
isStable &&
-
prevSlots &&
-
prevSlots !== emptyObject &&
-
key === prevSlots.$key &&
-
!hasNormalSlots &&
-
!prevSlots.$hasNormal
-
) {
-
// fast path 2: stable scoped slots w/ no normal slots to proxy,
-
// only need to normalize once
-
return prevSlots
-
} else {
-
res = {}
-
for (const key in slots) {
-
if (slots[key] && key[0] !== '$') {
-
res[key] = normalizeScopedSlot(normalSlots, key, slots[key]) // 作用域插槽
-
}
-
}
-
}
-
// expose normal slots on scopedSlots
-
for (const key in normalSlots) {
-
if (!(key in res)) {
-
res[key] = proxyNormalSlot(normalSlots, key) // 普通插槽
-
}
-
}
-
// avoriaz seems to mock a non-extensible $scopedSlots object
-
// and when that is passed down this would cause an error
-
if (slots && Object.isExtensible(slots)) {
-
(slots: any)._normalized = res
-
}
-
def(res, '$stable', isStable)
-
def(res, '$key', key)
-
def(res, '$hasNormal', hasNormalSlots)
-
return res
-
}
4.proxyNormalSlot(將slot代理到scopeSlots上)
-
function proxyNormalSlot(slots, key) {
-
return () => slots[key]
-
}
5.normalizeScopedSlot(將scopeSlots對應屬性和方法掛載到scopeSlots)
-
function normalizeScopedSlot(normalSlots, key, fn) {
-
const normalized = function () {
-
let res = arguments.length ? fn.apply(null, arguments) : fn({})
-
res = res && typeof res === 'object' && !Array.isArray(res)
-
? [res] // single vnode
-
: normalizeChildren(res)
-
return res && (
-
res.length === 0 ||
-
(res.length === 1 && res[0].isComment) // #9658
-
) ? undefined
-
: res
-
}
-
// this is a slot using the new v-slot syntax without scope. although it is
-
// compiled as a scoped slot, render fn users would expect it to be present
-
// on this.$slots because the usage is semantically a normal slot.
-
if (fn.proxy) {
-
Object.defineProperty(normalSlots, key, {
-
get: normalized,
-
enumerable: true,
-
configurable: true
-
})
-
}
-
return normalized
-
}
6.最后調用renderSlot用函數的返回值進行渲染。