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.
<template>
<div class="slot-demo">
<slot>this is slot default content text.</slot>
</div>
</template>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 $slots property on a component instance is defined as: $slots: { [key: string]: Array<VNode> }; Printing $slots for the example above yields an object representation of the slot content.
1.2 renderSlot
The core rendering function for a slot is:
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> {}For a normal slot, Vue.js executes:
const slotNodes = this.$slots[name]
nodes = slotNodes || fallback
return nodes1.3 resolveSlots
The function that builds the $slots object is:
export function resolveSlots (
children: ?Array<VNode>, // parent vnode children
context: ?Component // parent component instance
): { [key: string]: Array<VNode> } {}It creates an empty slots object, 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.
// ignore slots that contain only whitespace
for (const name in slots) {
if (slots[name].every(isWhitespace)) {
delete slots[name]
}
}
return slots1.4 initRender
The initRender function initializes vm.$slots by calling resolveSlots:
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)1.5 genSlot
The code generator creates the render function for a slot:
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 + ')'
}2 Scoped Slots
Scoped slots expose data from the child component to the parent. The component interface defines:
$scopedSlots: { [key: string]: () => VNodeChildren };Example component:
<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>Parent usage with a scoped slot:
<template>
<slot-demo>
<template slot-scope="scope">
<p>{{ scope.text }}{{ scope.msg }}</p>
</template>
</slot-demo>
</template>The compiled render function for the parent includes a call to _u (resolveScopedSlots) and creates a function that receives the slot scope object.
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))])]
}}])}])
}3 v-slot Syntax (Vue 2.6.x)
Vue 2.6 introduced the v-slot directive as a syntactic sugar for scoped slots. The basic usage is:
<slot-demo>
<template v-slot:demo>this is custom slot.</template>
<template v-slot="scope">
<p>{{ scope.text }}{{ scope.msg }}</p>
</template>
</slot-demo>Internally, the compiler parses v-slot on template elements 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-slot directives. getAndRemoveAttrByRegex – extracts and removes attributes matching a given regex. getSlotName – parses the slot name and determines if it is dynamic.
When a v-slot is found on a template, the compiler creates a new AST element with slotTarget, slotTargetDynamic, and slotScope properties. When used on a component, the component’s children are moved into a generated template node that becomes the default slot.
During code generation, genSlot now merges static and dynamic attributes and uses genProps to 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-slot syntax, all while maintaining backward compatibility through internal normalization functions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
