创建组件虚拟节点时,会将组件的儿子的虚拟节点保存起来。当初始化组件时,通过插槽属性将儿 子进行分类 {a:[vnode],b[vnode]}
渲染组件时会拿对应的slot属性的节点进行替换操作。(插槽的作用域为父组件,插槽中HTML模板显示不显示、以及怎样显示由父组件来决定)
有name的父组件通过html模板上的slot属性关联具名插槽。没有slot属性的html模板默认关联匿名插槽。
2.作用域插槽slot-scope
作用域插槽在解析的时候,不会作为组件的孩子节点。会解析成函数,当子组件渲染时,会调用此函数进行渲染。
或者可以说成作用域插槽是子组件可以在slot标签上绑定属性值,在父组件可以拿到子组件的数据,通过子组件绑定数据传递给父组件。(插槽的作用域为子组件)
子组件:
父组件:
{{scope.nickName}}
二、源码图
三、插槽渲染分析
1.插槽
// 父组件:
const VueTemplateCompiler = require(‘vue-template-compiler’);
let ele = VueTemplateCompiler.compile(`
node
react
vue
`)
将上面组件编译后:
/**
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(`
`);
/**
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(`
{{msg.a}}
/p>
p>
`);
/p>
p>
/**
/p>
p>
with(this) {
/p>
p>
// 编译出的不是一个child,而是一个属性scopedSlots
/p>
p>
return _c(‘app’, {
/p>
p>
scopedSlots: _u([{
/p>
p>
key: “footer”,
/p>
p>
fn: function (msg) { 将子节点变成一个函数,这个函数不调用就不会去渲染这个子节点
/p>
p>
return _c(‘div’, {}, [_v(_s(msg.a))])
/p>
p>
}
/p>
p>
}])
/p>
p>
})
/p>
p>
}
/p>
p>
}
/p>
p>
在初始化的时候并不会渲染子节点
/p>
p>
*/
/p>
p>
/p>
p>
子组件:
/p>
p>
const VueTemplateCompiler = require(‘vue-template-compiler’);
/p>
p>
VueTemplateCompiler.compile(
`
/p>
p>
// 当我们在写slot去执行的时候进行渲染
/p>
p>
/p>
p>
/p>
p>
/p>
p>
`);
/p>
p>
/**
/p>
p>
with(this) {
/p>
p>
// 去找footer,找见会调用上面的那个函数,并且将属性传入到这个函数里面,这时候才会把这节点进行渲染,完成之后替换调
/p>
p>
return _c(‘div’, [_t(“footer”, null, {
/p>
p>
“a”: “1”,
/p>
p>
“b”: “2”
/p>
p>
})], 2)
/p>
p>
}
/p>
p>
**/
/p>
p>
/p>
p>
// 作用域插槽的内容会被渲染成一个函数
/p>
p>
// 作用域插槽渲染是在当前组件的内部,不是在父组件中
/p>
h3>四、源码
/h3>
p>1.initRender(初始化render,构建vm.$slots)
/p>
p>
export function initRender (vm: Component) {
/p>
p>
vm.$slots = resolveSlots(options._renderChildren, renderContext)
/p>
p>
vm.$scopedSlots = emptyObject
/p>
p>
}
/p>
p>2.resolveSlots(映射slot名字和对应的vnode)
/p>
p>
export function resolveSlots (
/p>
p>
children: ?Array
context: ?Component
): { [key: string]: Array
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
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用函数的返回值进行渲染。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/128083.html