How Vue.js Implements Slots: Deep Dive into $slots, renderSlot, and v-slot
This article provides a comprehensive analysis of Vue.js slot mechanisms, covering normal slots, scoped slots, and the v-slot syntax, with detailed code examples and explanations of the underlying rendering functions and compilation processes.
This article analyzes how the commonly used Slots feature in Vue.js is designed and implemented, focusing on three parts: normal slots, scoped slots, and the v-slot syntax introduced in Vue.js 2.6.x.
1 Normal Slots
First, a simple example of using Slots is presented.
<code><template>
<div class="slot-demo">
<slot>this is slot default content text.</slot>
</div>
</template>
</code>Rendering this component produces the following result:
Next, we replace the content inside the slot.
<slot-demo>this is slot custom content.</slot-demo>After re‑rendering, the result is:
To understand the underlying logic, we examine Vue.js internal implementation of Slots.
1.1 vm.$slots
The
$slotsproperty on a component instance is defined as:
<code>$slots: { [key: string]: Array<VNode> };</code>Printing
$slotsfor the example above yields an object representation of the slot content.
1.2 renderSlot
The core rendering function for a slot is:
<code>export function renderSlot (
name: string, // slot name
fallback: ?Array<VNode>, // default content VNode array
props: ?Object, // props object
bindObject: ?Object // v‑bind object
): ?Array<VNode> {}
</code>For a normal slot, Vue.js executes:
<code>const slotNodes = this.$slots[name]
nodes = slotNodes || fallback
return nodes
</code>1.3 resolveSlots
The function that builds the
$slotsobject is:
<code>export function resolveSlots (
children: ?Array<VNode>, // parent vnode children
context: ?Component // parent component instance
): { [key: string]: Array<VNode> } {}
</code>It creates an empty
slotsobject, returns it if there are no children, otherwise iterates over the children, assigning them to named slots or the default slot, and finally removes slots that contain only whitespace.
<code>// ignore slots that contain only whitespace
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots
</code>1.4 initRender
The
initRenderfunction initializes
vm.$slotsby calling
resolveSlots:
<code>const options = vm.$options
const parentVnode = vm.$vnode = options._parentVnode // placeholder node in parent tree
const renderContext = parentVnode && parentVnode.context
vm.$slots = resolveSlots(options._renderChildren, renderContext)
</code>1.5 genSlot
The code generator creates the render function for a slot:
<code>function genSlot (el: ASTElement, state: CodegenState): string {
const slotName = el.slotName || '"default"'
const children = genChildren(el, state)
let res = `_t(${slotName}${children ? `,${children}` : ''})`
const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`
const bind = el.attrsMap['v-bind']
if ((attrs || bind) && !children) {
res += `,null`
}
if (attrs) {
res += `,${attrs}`
}
if (bind) {
res += `${attrs ? '' : ',null'},${bind}`
}
return res + ')'
}
</code>2 Scoped Slots
Scoped slots expose data from the child component to the parent. The component interface defines:
<code>$scopedSlots: { [key: string]: () => VNodeChildren };</code>Example component:
<code><template>
<div class="slot-demo">
<slot text="this is a slot demo , " :msg="msg"></slot>
</div>
</template>
<script>
export default {
name: 'SlotDemo',
data () {
return { msg: 'this is scoped slot content.' }
}
}
</script>
</code>Parent usage with a scoped slot:
<code><template>
<slot-demo>
<template slot-scope="scope">
<p>{{ scope.text }}{{ scope.msg }}</p>
</template>
</slot-demo>
</template>
</code>The compiled render function for the parent includes a call to
_u(resolveScopedSlots) and creates a function that receives the slot scope object.
<code>with(this){
return _c('div', {staticClass:'parent-slot'}, [_c('slot-demo', {scopedSlots: _u([
{key:'default', fn: function(scope){
return [_c('p', [_v(_s(scope.text))]), _c('p', [_v(_s(scope.msg))])]
}}])}])
}
</code>3 v-slot Syntax (Vue 2.6.x)
Vue 2.6 introduced the
v-slotdirective as a syntactic sugar for scoped slots. The basic usage is:
<code><slot-demo>
<template v-slot:demo>this is custom slot.</template>
<template v-slot="scope">
<p>{{ scope.text }}{{ scope.msg }}</p>
</template>
</slot-demo>
</code>Internally, the compiler parses
v-sloton
templateelements or components, extracts the slot name and scope, and stores the content in the AST as a scoped slot. The processing functions have been renamed (e.g.,
processSlot→
processSlotContent) but the overall logic remains the same, with added handling for dynamic slot arguments.
Key helper regular expressions and functions include:
dynamicArgRE = /^\[.*\]$/– matches dynamic slot arguments like
[item].
slotRE = /^v-slot(:|$)|^#/– detects
v-slotdirectives.
getAndRemoveAttrByRegex– extracts and removes attributes matching a given regex.
getSlotName– parses the slot name and determines if it is dynamic.
When a
v-slotis found on a
template, the compiler creates a new AST element with
slotTarget,
slotTargetDynamic, and
slotScopeproperties. When used on a component, the component’s children are moved into a generated
templatenode that becomes the default slot.
During code generation,
genSlotnow merges static and dynamic attributes and uses
genPropsto produce the appropriate render‑function string, supporting dynamic slot arguments.
Overall, Vue.js’s slot system evolves from simple content insertion (
$slots) to powerful scoped slots (
$scopedSlots) and finally to the concise
v-slotsyntax, all while maintaining backward compatibility through internal normalization functions.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.