阿里云主机折上折
  • 微信号
Current Site:Index > The processing flow of slot content

The processing flow of slot content

Author:Chuan Chen 阅读数:22405人阅读 分类: Vue.js

Processing Flow of Slot Content

Vue3's slot mechanism allows components to receive template fragments as content and decide how to render this content within the component. The core logic of slot processing is divided into two stages: compilation and runtime. The compiler transforms slot content in templates into specific data structures, while the runtime renders based on these data.

Compilation Phase Processing

The template compiler generates special code structures when encountering slot content. For default slots:

<template>
  <ChildComponent>
    <div>Default slot content</div>
  </ChildComponent>
</template>

The compiled render function produces a structure like this:

import { createVNode as _createVNode, openBlock as _openBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(),
  _createVNode(ChildComponent, null, {
    default: () => [_createVNode("div", null, "Default slot content")],
    _: 1 /* STABLE */
  }))
}

Named slots are handled slightly differently:

<template>
  <ChildComponent>
    <template #header>
      <h1>Title</h1>
    </template>
  </ChildComponent>
</template>

The compilation result is:

{
  header: () => [_createVNode("h1", null, "Title")],
  _: 1 /* STABLE */
}

Scoped slots receive parameters:

<template>
  <ChildComponent>
    <template #item="props">
      <span>{{ props.text }}</span>
    </template>
  </ChildComponent>
</template>

The compiled output generates a function with parameters:

{
  item: (props) => [_createVNode("span", null, _toDisplayString(props.text), 1 /* TEXT */)],
  _: 1 /* STABLE */
}

Runtime Initialization

Slot-related logic is processed during component instantiation, primarily in the setupComponent function:

function setupComponent(instance) {
  // Handle props
  initProps(instance)
  
  // Handle slots
  initSlots(instance, instance.vnode.children)
  
  // Execute setup
  setupStatefulComponent(instance)
}

Core logic of the initSlots function:

function initSlots(instance, children) {
  if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
    const slots = {}
    for (const key in children) {
      slots[key] = (props) => normalizeSlotValue(children[key](props))
    }
    instance.slots = slots
  } else {
    instance.slots = {
      default: () => normalizeSlotValue(children)
    }
  }
}

Slot Rendering Process

When rendering slot content, the renderSlot function is executed:

function renderSlot(slots, name, props = {}) {
  const slot = slots[name]
  if (slot) {
    const content = slot(props)
    return createVNode(Fragment, { key: props.key }, content)
  }
}

Example of a component using slots internally:

<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot :data="internalData"></slot>
    </main>
  </div>
</template>

Corresponding render function:

export function render(_ctx, _cache) {
  return (_openBlock(),
  _createVNode("div", { class: "container" }, [
    _createVNode("header", null, [
      _renderSlot(_ctx.$slots, "header")
    ]),
    _createVNode("main", null, [
      _renderSlot(_ctx.$slots, "default", { data: _ctx.internalData })
    ])
  ]))
}

Dynamic Slot Name Handling

Vue3 supports dynamic slot names:

<template>
  <ChildComponent>
    <template #[dynamicSlotName]>
      Dynamic slot content
    </template>
  </ChildComponent>
</template>

The compilation generates dynamic properties:

const slots = {
  [dynamicSlotName]: () => [_createVNode("div", null, "Dynamic slot content")],
  _: 1 /* STABLE */
}

Runtime processing first computes the slot name:

function renderDynamicSlot(slots, name) {
  const slotName = unref(name) // Handle reactive values
  return renderSlot(slots, slotName)
}

Slot Performance Optimizations

Vue3 implements several optimizations for slots:

  1. Static Node Hoisting: Static slot content is hoisted externally to avoid repeated creation.
const _hoisted_1 = _createVNode("div", null, "Static content")

function render() {
  return _renderSlot(_ctx.$slots, "default", () => [_hoisted_1])
}
  1. Slot Marking: The compiler marks stable slot content to avoid unnecessary updates.
{
  default: () => [/* content */],
  _: 1 /* STABLE */ | 2 /* DYNAMIC */
}
  1. Scoped Slot Caching: Intelligently determines whether updates are needed when scoped slot parameters change.
function renderSlotWithCache(slots, name, props, cache, index) {
  const slot = slots[name]
  const cached = cache[index]
  if (cached && isSameProps(cached.props, props)) {
    return cached.vnode
  }
  const vnode = slot(props)
  cache[index] = { props, vnode }
  return vnode
}

Advanced Slot Patterns

  1. Render Delegation: Delegates slot rendering logic to child components.
<template>
  <DelegateRenderer :renderer="$slots.default" />
</template>
  1. Slot Forwarding: Uses v-slots syntactic sugar to forward slots.
<template>
  <WrapperComponent>
    <template v-for="(_, name) in $slots" #[name]="props">
      <slot :name="name" v-bind="props"></slot>
    </template>
  </WrapperComponent>
</template>
  1. Conditional Slots: Renders different slot content based on conditions.
function renderConditionalSlot() {
  return condition.value 
    ? renderSlot(slots, 'true')
    : renderSlot(slots, 'false')
}

Slots and the Composition API

Using slots in the setup function:

export default {
  setup(props, { slots }) {
    // Check if slot exists
    const hasHeader = computed(() => !!slots.header)
    
    // Process slot content
    const processedSlots = computed(() => {
      return slots.default?.() || []
    })
    
    return { hasHeader, processedSlots }
  }
}

Dynamically modifying slot content:

const dynamicSlot = ref(null)

provide('dynamic-slot', {
  register: (content) => dynamicSlot.value = content,
  unregister: () => dynamicSlot.value = null
})

const slots = computed(() => ({
  default: () => dynamicSlot.value ? [dynamicSlot.value] : []
}))

Slot Type System

Vue3 provides comprehensive type support for slots:

defineComponent({
  slots: Object as PropType<{
    default?: (props: { data: string }) => VNode[]
    header?: () => VNode[]
  }>,
  setup(props, { slots }) {
    // slots now has full type hints
  }
})

Example of a generic slot component:

interface SlotProps<T> {
  item: T
  index: number
}

const List = <T,>(props: { items: T[] }) => {
  return props.items.map((item, index) => 
    slots.default?.({ item, index })
  )
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.