深入淺析Vue中的slots/scoped slots
一直對Vue中的slot插槽比較感興趣,下面是自己的一些簡單理解,希望可以幫助大家更好的理解slot插槽
下面結(jié)合一個(gè)例子,簡單說明slots的工作原理
dx-li子組件的template如下:
<li class="dx-li">
<slot>
你好!
</slot>
</li>
dx-ul父組件的template如下:
<ul>
<dx-li>
hello juejin!
</dx-li>
</ul>
結(jié)合上述例子以及vue中相關(guān)源碼進(jìn)行分析
dx-ul父組件中template編譯后,生成的組件render函數(shù):
module.exports={
render:function (){
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
// 其中_vm.v為createTextVNode創(chuàng)建文本VNode的函數(shù)
return _c('ul',
[_c('dx-li', [_vm._v("hello juejin!")])],
1)
},
staticRenderFns: []
}
傳遞的插槽內(nèi)容'hello juejin!'會被編譯成dx-li子組件VNode節(jié)點(diǎn)的子節(jié)點(diǎn)。
渲染dx-li子組件,其中子組件的render函數(shù):
module.exports={
render:function (){
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
// 其中_vm._v 函數(shù)為renderSlot函數(shù)
return _c('li',
{staticClass: "dx-li" },
[_vm._t("default", [_vm._v("你好 掘金!")])],
2
)
},
staticRenderFns: []
}
初始化dx-li子組件vue實(shí)例過程中,會調(diào)用initRender函數(shù):
function initRender (vm) {
...
// 其中_renderChildren數(shù)組,存儲為 'hello juejin!'的VNode節(jié)點(diǎn);renderContext一般為父組件Vue實(shí)例
這里為dx-ul組件實(shí)例
vm.$slots = resolveSlots(options._renderChildren, renderContext);
...
}
其中resolveSlots函數(shù)為:
/**
* 主要作用是將children VNodes轉(zhuǎn)化成一個(gè)slots對象.
*/
export function resolveSlots (
children: ?Array<VNode>,
context: ?Component
): { [key: string]: Array<VNode> } {
const slots = {}
// 判斷是否有children,即是否有插槽VNode
if (!children) {
return slots
}
// 遍歷父組件節(jié)點(diǎn)的孩子節(jié)點(diǎn)
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節(jié)點(diǎn)data = {attrs:{slot: "abc"}, slot: "abc"},所以這里刪除該節(jié)點(diǎn)attrs的slot
*/
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot
}
/* 判斷是否為具名插槽,如果為具名插槽,還需要子組件/函數(shù)子組件渲染上下文一致。主要作用:
*當(dāng)需要向子組件的子組件傳遞具名插槽時(shí),不會保持插槽的名字。
* 舉個(gè)栗子:
* 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>
* 此時(shí)main渲染的結(jié)果:
* <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數(shù)組
(slots.default || (slots.default = [])).push(child)
}
}
// 忽略僅僅包含whitespace的插槽
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots
}
然后掛載dx-li組件時(shí),會調(diào)用dx-li組件render函數(shù),在此過程中會調(diào)用renderSlot函數(shù):
export function renderSlot (
name: string, // 子組件中slot的name,匿名default
fallback: ?Array<VNode>, // 子組件插槽中默認(rèn)內(nèi)容VNode數(shù)組,如果沒有插槽內(nèi)容,則顯示該內(nèi)容
props: ?Object, // 子組件傳遞到插槽的props
bindObject: ?Object // 針對<slot v-bind="obj"></slot> obj必須是一個(gè)對象
): ?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生成相應(yīng)的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
)
}
// 設(shè)置父組件傳遞插槽的VNode._rendered,用于后面判斷是否有重名slot
slotNodes._rendered = true
}
// 如果沒有傳入插槽,則為默認(rèn)插槽內(nèi)容VNode
nodes = slotNodes || fallback
}
// 如果還需要向子組件的子組件傳遞slot
/*舉個(gè)栗子:
* 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
}
}
scoped slots理解
dx-li子組件的template如下:
<li class="dx-li">
<slot str="你好 掘金!">
hello juejin!
</slot>
</li>
dx-ul父組件的template如下:
<ul>
<dx-li>
<span slot-scope="scope">
{{scope.str}}
</span>
</dx-li>
</ul>
結(jié)合例子和Vue源碼簡單作用域插槽
dx-ul父組件中template編譯后,產(chǎn)生組件render函數(shù):
module.exports={
render:function (){
var _vm=this;
var _h=_vm.$createElement;
var _c=_vm._self._c||_h;
return _c('ul', [_c('dx-li', {
// 可以編譯生成一個(gè)對象數(shù)組
scopedSlots: _vm._u([{
key: "default",
fn: function(scope) {
return _c('span',
{},
[_vm._v(_vm._s(scope.str))]
)
}
}])
})], 1)
},
staticRenderFns: []
}
其中 _vm._u函數(shù):
function resolveScopedSlots (
fns, // 為一個(gè)對象數(shù)組,見上文scopedSlots
res
) {
res = res || {};
for (var i = 0; i < fns.length; i++) {
if (Array.isArray(fns[i])) {
// 遞歸調(diào)用
resolveScopedSlots(fns[i], res);
} else {
res[fns[i].key] = fns[i].fn;
}
}
return res
}
子組件的后續(xù)渲染過程與slots類似。scoped slots原理與slots基本是一致,不同的是編譯父組件模板時(shí),會生成一個(gè)返回結(jié)果為VNode的函數(shù)。當(dāng)子組件匹配到父組件傳遞作用域插槽函數(shù)時(shí),調(diào)用該函數(shù)生成對應(yīng)VNode。
總結(jié)
其實(shí)slots/scoped slots 原理是非常簡單的,我們只需明白一點(diǎn)vue在渲染組件時(shí),是根據(jù)VNode渲染實(shí)際DOM元素的。
slots是將父組件編譯生成的插槽VNode,在渲染子組件時(shí),放置到對應(yīng)子組件渲染VNode樹中。
scoped slots是將父組件中插槽內(nèi)容編譯成一個(gè)函數(shù),在渲染子組件時(shí),傳入子組件props,生成對應(yīng)的VNode。最后子組件,根據(jù)組件render函數(shù)返回VNode節(jié)點(diǎn)樹,update渲染真實(shí)DOM元素。同時(shí),可以看出跨組件傳遞插槽也是可以的,但是必須注意具名插槽傳遞。
相關(guān)文章
vue實(shí)現(xiàn)tab欄點(diǎn)擊高亮效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)tab欄點(diǎn)擊高亮效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-08-08
Vue如何用this.$set改變數(shù)組里的某個(gè)值
這篇文章主要介紹了Vue用this.$set改變數(shù)組里的某個(gè)值,文中通過示例代碼介紹了vue中this.$set()的用法----更新數(shù)組和對象的值,需要的朋友可以參考下2022-12-12
Vue項(xiàng)目前端部署詳細(xì)步驟(nginx方式)
Nginx(engine x)是一個(gè)高性能的HTTP和反向代理web服務(wù)器,是部署前端項(xiàng)目的首選,這篇文章主要給大家介紹了關(guān)于Vue項(xiàng)目前端部署(nginx方式)的相關(guān)資料,需要的朋友可以參考下2023-09-09

