slot
插槽的实现实际上就是一种 延时渲染,把父组件中编写的插槽内容保存到一个对象上,并且把具体渲染 DOM 的代码用函数的方式封装,然后在子组件渲染的时候,根据插槽名在对象中找到对应的函数,然后执行这些函数做真正的渲染。
/**
* 创建 vnode
*/
function createVNode(type, props = null, children = null) {
if (props) {
// 处理 props 相关逻辑,标准化 class 和 style
}
// 对 vnode 类型信息编码
// 创建 vnode 对象
const vnode = {
type,
props,
// 其他一些属性
}
// 标准化子节点,把不同数据类型的 children 转成数组或者文本类型
normalizeChildren(vnode, children)
return vnode
}
/**
* 标准化子节点,以及获取 vnode 节点类型 shapeFlag,shapeFlag 最终为 SLOTS_CHILDREN | STATEFUL_COMPONENT
*/
function normalizeChildren(vnode, children) {
let type = 0
const { shapeFlag } = vnode
// 没有子节点
if (children === null) {
children = null
}
// 子节点为数组
else if (isArray(children)) {
type = 16 /* ARRAY_CHILDREN */
}
// 子节点为对象
else if (typeof children === 'object') {
// 子节点为元素或 teleport
if ((shapeFlag & 1/* ELEMENT */ || shapeFlag & 64 /* TELEPORT */) && children.default) {
normalizeChildren(vnode, children.default())
return
}
// 子节点为 slot
else {
type = 32/* SLOTS_CHILDREN */
const slotFlag = children._
if (!slotFlag && !(InternalObjectKey in children)) {
children._ctx = currentRenderinglnstance
}
// 处理类型为 FORWARDED 的情况
else if (slotFlag === 3 /* FORWARDED */ && currentRenderinglnstance) {
// 动态插槽
if (currentRenderingInstance.vnode.patchFlag & 1024/* DYNAMIC_SLOTS */) {
children._ = 2/* DYNAMIC */
vnode.patchFlag |= 1024 /* DYNAMIC SLOTS */
}
// 静态插槽
else {
children._ = 1/* STABLE */
}
}
}
}
// 子节点为函数
else if (isFunction(children)) {
children = { default: children, _ctx: currentRenderinglnstance }
type = 32/* SLOTS_CHILDREN */
}
// 其他子节点
else {
children = String(children)
// teleport 类型
if (shapeFlag & 64/* TELEPORT */) {
type = 16/* ARRAY_CHILDREN */
children = [createTextVNode(children)]
}
// 文本类型
else {
type = 8/* TEXT_CHILDREN */
}
}
vnode.children = children
vnode.shapeFlag |= type
}
/**
* 初始化 Slots
*/
const initSlots = (instance, children) => {
if (instance.vnode.shapeFlag & 32/* SLOTS_CHILDREN */) {
const type = children._
if (type) {
instance.slots = children
def(children, '_', type)
} else {
normalizeObjectSlots(children, (instance.slots = {}))
}
} else {
instance.slots = {}
if (children) {
normalizeVNodeSlots(instance, children)
}
}
def(instance.slots, InternalObjectKey, 1)
}
/**
* 渲染 slot DOM
* @param {Object} slots - 插槽对象 instance.slots
* @param {string} name - 插槽名
*/
function renderSlot(slots, name, props = {}, fallback) {
// 根据 name 获取对应插槽函数
let slot = slots[name]
// 通过 createBlock 创建 vnode 节点,类型为 Fragment,children 是执行 slot 插槽函数的返回值
return (openBlock(), createBlock(Fragment, { key: props.key }, slot ? slot(props) : fallback ? fallback() : [], slots._ === 1/* STABLE */ ? 64/* STABLE_FRAGMENT */ : -2/* BAIL */));
}
/**
* 保证子组件中渲染具体插槽内容,保证它的数据作用域也是父组件
*/
function withCtx(fn, ctx = currentRenderinglnstance) {
if (!ctx) return fn
return function renderFnWithContext() {
// 保存当前渲染的组件实例 owner
const owner = currentRenderingInstance
// 把 ctx 设置为当前渲染的实例
setCurrentRenderinglnstance(ctx)
// 执行 fn
const res = fn.apply(null, arguments)
// 把 ctx 设置为当前渲染的实例
setCurrentRenderingInstance(owner)
return res
}
}
/**
* 处理 <Fragment>
*/
const processFragment = (nl, n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
const fragmentStartAnchor = (n2.el = n1 ? nl.el : hostCreateText(''))
const fragmentEndAnchor = (n2.anchor = n1 ? nl.anchor : hostCreateText(''))
let { patchFlag } = n2
if (patchFlag > 0) {
optimized = true
}
// 插入节点
if (n1 == null) {
// 先在前后插入两个空文本节点
hostInsert(fragmentStartAnchor, container, anchor)
hostInsert(fragmentEndAnchor, container, anchor)
// 再挂载子节点
mountChildren(n2.children, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, optimized)
}
//更新节点
else { }
}