[Vue 源码] Vue 3.2 - slots 原理

128 阅读1分钟

代码运行结果

slots.gif

代码示例

  <script src="./dist/vue.global.js"></script>

  <body>
    <div id="app"></div>
    <script>
      let { createApp, reactive, Fragment, toRefs, h, getCurrentInstance } = Vue

      const MyCpn = {
        setup(props, { emit, slots }) {
          console.log(getCurrentInstance(), 'child')

          return () =>
            h(Fragment, [
              h('div', slots.header()),
              h('div', slots.main()),
              h('div', slots.footer())
            ])
        }
      }

      const App = {
        render() {
          return h(MyCpn, null, {
            header: () => {
              return h('h1', 'header')
            },
            main: () => {
              return h('h1', 'main')
            },
            footer: () => {
              return h('h1', 'footer')
            }
          })
        }
      }

      createApp(App).mount('#app')
    </script>

第一:挂载子组件的时候,通过 h, createVNode 函数,创建虚拟 Dom 的时候,在 normalizeChildren 函数中 判断第三个参数是对象,则给子组件的示例上 ShapeFlags 标记了 SLOTS_CHILDREN。

      type = ShapeFlags.SLOTS_CHILDREN
      vnode.shapeFlag |= type

第二:挂载子组件的时候,调用 mountComponent 时,调用 initSlots 函数,来给 instance.slots 上赋值 children 属性。也就是:

{slots: {
          header: () => {
              return h('h1', 'header')
            },
          main: () => {
              return h('h1', 'main')
            },
          footer: () => {
              return h('h1', 'footer')
            }
}}
export const initSlots = (
  instance: ComponentInternalInstance,
  children: VNodeNormalizedChildren
) => {
  if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
    const type = (children as RawSlots)._
    if (type) {
      // users can get the shallow readonly version of the slots object through `this.$slots`,
      // we should avoid the proxy object polluting the slots of the internal instance
      instance.slots = toRaw(children as InternalSlots)
      // make compiler marker non-enumerable
      def(children as InternalSlots, '_', type)
    } else {
      normalizeObjectSlots(
        children as RawSlots,
        (instance.slots = {}),
        instance
      )
    }
  } else {
    instance.slots = {}
    if (children) {
      normalizeVNodeSlots(instance, children)
    }
  }
  def(instance.slots, InternalObjectKey, 1)
}

第三:在子组件上通过 调用 slots.header(), 再调用 h 函数 获得虚拟 Dom,挂载的时候调用 render 函数进行 patch(这里没有 render 函数,就会将 setup 返回的函数当作 render 函数)。

自此挂载完毕。