Vue3 源码解读系列(十一)——插槽 slot

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 { }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Mseven

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值