2. 自定义槽位基础原理
2.1 槽位的本质:从模板到虚拟节点的桥梁
无论是 Vue 的 slot 还是 React 的 children,它们都解决了同一个核心问题:父组件如何向子组件“注入”自定义渲染内容。底层机制依赖于模板编译阶段对槽位标记的解析,以及运行时虚拟节点(VNode)树的构建与分支挂载。本节将拆解三个关键环节:模板编译、Slot/Children 机制、虚拟节点树生成。
2.2 模板编译阶段:Slot 标记的解析
Vue 的 SFC(单文件组件)在编译时,编译器会识别 <slot> 标签,将其转换为特殊的 _slot 渲染函数调用;React 的 JSX 中 {children} 则在 Babel 编译时被保留为 props.children 属性。两个框架的编译产物本质相同:将槽位声明转换为运行时可处理的虚拟节点占位符。
| 阶段 | Vue (2/3) | React (16+) |
|---|---|---|
| 编译输入 | <slot name="header" /> | <div>{children}</div> |
| AST 标记 | Slot 节点 + name 属性 | JSXExpressionContainer 包裹 children |
| 生成代码 | _c('div', [_t("header")]) | React.createElement('div', null, children) |
| 运行时函数 | _t() → renderSlot() | children 直接作为 props 传递 |
关键洞察:编译阶段不会“展开”槽位内容,而是保留占位符。子组件的渲染函数在运行时才通过父组件的渲染函数获取真实内容。
2.3 运行时 Slot/Children 机制
在运行时,子组件通过调用 父组件提供的渲染函数 来获取槽位内容。Vue 使用 $slots 对象存储编译生成的渲染函数;React 则通过 props.children 直接传递已经生成的 ReactElement 或函数。两者的核心差异在于:
- Vue 作用域隔离:默认在父作用域编译,但可通过
v-bind:$slots或slot-scope(Vue 2)/v-slot:default="props"(Vue 3)实现子向父的数据传递。 - React 函数式 children:使用
children作为渲染函数(render props 模式)时,能更灵活地传递回调与状态。
实战提示:在 BottomNavigation 组件中,每个标签的内容(图标 + 文字 + 角标)都应通过槽位可自定义。若使用 Vue,推荐 v-slot:tab="{ item, active }";React 则采用 <Tab renderIcon={(active) => ...} /> 模式。
2.4 虚拟节点树构建与更新路径
父组件的渲染函数执行时,子组件的内容会被延迟求值。Vue 在 patchVNode 阶段通过 invokeInsertHook 触发插槽渲染;React 则通过更新队列 beginWork 中的 placeChild 逻辑挂载子节点。下图展示了从模板到 DOM 的完整链路:
2.5 自定义渲染的核心控制
通过槽位机制,BottomNavigation 组件可以实现以下自定义渲染能力:
- 内容替换:完全替换默认的图标/文字,例如使用 SVG 图标代替字体图标。
- 样式覆盖:通过 CSS-in-JS 或 Scoped CSS 定制每个标签的悬停、激活、禁用状态。
- 事件传递:父组件可通过
v-slot/renderProps将点击、拖拽等事件注入到槽位内部。 - 状态共享:子组件通过
v-bind=$slots或React.cloneElement将内部状态(activeIndex、动画参数)传递给自定义模板。
2.6 TypeScript 类型增强
为槽位添加类型约束能大幅提升开发体验:
// Vue 3 + TS 插槽类型示例
defineSlots<{
default(props: { item: TabItem; active: boolean }): void
icon(props: { name: string; size: number }): void
}>()
// React + TS children 类型示例
type TabProps = {
children: (props: { isActive: boolean; onClick: () => void }) => ReactNode
}
2.7 性能优化简要原则
- 避免每次渲染都创建新函数:Vue 中使用
v-slot的slotProps中避免内联匿名函数;React 使用useCallback包装渲染函数。 - 稳定 key:当槽位内存在列表时,给每个槽位分配稳定的
key可阻止不必要的重渲染。 - 惰性渲染:仅当槽位被实际使用时(
$slots.default存在)才执行渲染函数,减少计算开销。
常见错误:在 Vue 2 中直接修改 $slots 对象或试图 v-if 拦截 slot-scope 会导致渲染异常。正确的做法是使用 v-slot 的 v-if 控制父组件是否传递内容,而非在子组件内过滤。