The implementation principle of scoped slots
Basic Concepts of Scoped Slots
Scoped slots allow child components to pass data to parent components, enabling the parent to determine how to render this data. This mechanism breaks the unidirectional data flow of traditional slots, achieving child-to-parent data communication. In Vue 3, scoped slots are implemented using the v-slot
directive, relying on the underlying logic of render functions.
// Child component
const ChildComponent = {
template: `
<div>
<slot :user="user" :age="age"></slot>
</div>
`,
setup() {
const user = ref('Alice')
const age = ref(25)
return { user, age }
}
}
// Parent component usage
<ChildComponent v-slot="{ user, age }">
<p>{{ user }} is {{ age }} years old</p>
</ChildComponent>
Compilation Phase Processing
When the Vue compiler encounters a scoped slot, it performs special processing. The template compiler converts the slot content into a render function while preserving references to the scoped data. This process primarily occurs in the @vue/compiler-sfc
package.
For the example above, the compiled render function roughly looks like this:
// Compiled result of the child component
function render(_ctx, _cache) {
return {
type: 'div',
children: [
{
type: 'slot',
props: {
user: _ctx.user,
age: _ctx.age
}
}
]
}
}
Runtime Implementation Mechanism
Vue 3's runtime core handles slots through the createSlots
function. Scoped slots are converted into function forms that receive parameters and return VNodes. This process occurs in runtime-core/src/componentSlots.ts
.
Key implementation code:
function createSlots(slots, dynamicSlots) {
const slot = (props) => {
return normalizeSlotValue(slots(props))
}
slot._ = 1 // Marks it as a scoped slot
return slot
}
Data Propagation Path of Scoped Slots
The complete path of data propagation from child to parent components:
- The child component collects slot props when executing the render function.
- Exposes the slot function via
setupContext.slots
. - The parent component passes props when calling the slot function.
- The slot content is rendered based on the props.
// Simplified runtime flow
const childVNode = {
type: ChildComponent,
children: {
default: (props) => h('p', `${props.user} is ${props.age}`)
}
}
// Execution inside the child component
const slots = childVNode.children
const slotContent = slots.default({ user: 'Alice', age: 25 })
Performance Differences Compared to Regular Slots
Scoped slots have additional performance overhead compared to regular slots:
- The slot function must be re-executed on each render.
- Dependency collection is more complex, requiring tracking of slot prop changes.
- Higher memory usage due to maintaining scope closures.
Vue 3 mitigates this overhead with the following optimizations:
- Static hoisting of unchanged slot content during compilation.
- Caching the results of slot function execution.
- Reduced dependency tracking costs via the Proxy-based reactivity system.
Implementation of Dynamic Scoped Slots
Dynamic slot names can be combined with scoped slots for more flexible API design:
<template>
<MyComponent>
<template v-slot:[dynamicSlotName]="props">
<p>{{ props.message }}</p>
</template>
</MyComponent>
</template>
<script setup>
const dynamicSlotName = ref('header')
</script>
The compiled code handles dynamic slot names:
function render(_ctx) {
return h(MyComponent, null, {
[_ctx.dynamicSlotName]: (props) => h('p', props.message)
})
}
Type System for Scoped Slots
In TypeScript environments, Vue 3 provides robust type support. Generics and type inference ensure type safety for slot props:
// Child component defines prop types
defineComponent({
slots: {
default: (props: { user: string; age: number }) => VNode[]
}
})
// Parent component gets type hints when using
<ChildComponent v-slot="{ user, age }">
<!-- `user` and `age` have correct type inference -->
</ChildComponent>
Advanced Usage of Scoped Slots
Scoped slots can be nested to create complex component abstractions. For example, implementing a composable list component:
// List component
const List = {
template: `
<ul>
<li v-for="(item, index) in items" :key="index">
<slot :item="item" :index="index"></slot>
</li>
</ul>
`,
props: ['items']
}
// Composition usage
<List :items="users" v-slot="{ item, index }">
<UserProfile :user="item" :rank="index + 1"/>
</List>
Comparison with React Render Props
Scoped slots are similar to React's render props pattern but have key differences:
- More concise syntax, directly integrated into templates.
- Support for multiple slots (named slots).
- Better type inference (in TypeScript).
- Deep integration with Vue's reactivity system.
Equivalent React implementation:
<List items={users}>
{(item, index) => <UserProfile user={item} rank={index + 1} />}
</List>
Debugging Techniques for Scoped Slots
Use the following methods to debug scoped slot issues:
- Inspect the compiled render function.
- Use Vue DevTools to examine slot content.
- Add logs inside slot functions.
// Debugging example
<ChildComponent v-slot="props">
{{ console.log('Slot props:', props) }}
<!-- Content -->
</ChildComponent>
Application of Scoped Slots in Component Libraries
Modern UI libraries widely use scoped slots for flexibility. For example, customizing table cell rendering:
<DataTable :data="users">
<template #name="{ value }">
<span class="name-cell">{{ value.toUpperCase() }}</span>
</template>
<template #age="{ value }">
<ProgressBar :value="value" max="100"/>
</template>
</DataTable>
This pattern allows component libraries to provide structured UIs while letting users decide rendering details.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:过渡动画的系统集成
下一篇:组件事件系统的内部机制