前言
- 常网IT源码上线啦!
- 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
- 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
- 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。
每个人在满足了基本的生理和安全需求之后,都会渴望"尊重需求"和"自我实现需求"。
当你把对方当成英雄时,你恰恰满足了他这种深层次的心理渴望。
他会觉得,和你聊天很舒服。

一、前言
在Vue组件开发中,插槽(Slot)是实现组件复用和内容分发的核心机制。
直入正文。
插槽Slot。
二、默认插槽与具名插槽
name="footer":具名插槽,带有名字
java
<!-- 子组件 -->
<template>
<div>
<slot name="header"></slot>
<slot>默认内容</slot>
<slot name="footer"></slot>
</div>
</template>
<!-- 父组件 -->
<ChildComponent>
<template v-slot:header>
<h1>自定义标题</h1>
</template>
<p>主要内容区域</p>
<template v-slot:footer>
<footer>页脚信息</footer>
</template>
</ChildComponent>
条件插槽:智能内容显示
<slot v-if="$slots.group" name="group"></slot>
检测插槽内容是否存在,避免渲染空内容。
三、作用域插槽:子传父数据流
数据传递机制
java
<!-- 子组件 -->
<slot name="group" :group="groupData"></slot>
<!-- 父组件 -->
<template v-slot:group="slotProps">
{{ slotProps.group.name }} ({{ slotProps.group.members }}人)
</template>
比如我们最常用的动态表格渲染。
java
<!-- Table组件 -->
<template>
<table>
<tr v-for="(item, index) in items" :key="index">
<slot name="row" :item="item"></slot>
</tr>
</table>
</template>
<!-- 使用 -->
<Table :items="users">
<template v-slot:row="{ item }">
<td>{{ item.name }}</td>
<td>{{ item.email }}</td>
<td>{{ item.role }}</td>
</template>
</Table>
四、事件传递:父组件触发子组件事件
子组件
java
<slot
name="group"
:group="group"
:on-click="handleGroupClick"> <!-- 注意:传递函数引用而非调用 -->
</slot>
<script>
export default {
methods: {
handleGroupClick(group) {
console.log('分组被点击', group)
this.$set(group, 'isExpanded', !group.isExpanded)
}
}
}
</script>
父组件
java
<template v-slot:group="slotProps">
<div @click="slotProps.onClick(slotProps.group)">
{{ slotProps.group.name }}
<Icon :type="slotProps.group.isExpanded ? 'up' : 'down'" />
</div>
</template>
避免在插槽prop中使用onClick()
形式,这会导致立即执行。
可折叠表单组
实战一下,子组件实现。
java
<template>
<div v-for="(group, index) in groups" :key="index">
<div class="group-header">
<slot v-if="$slots.group"
name="group"
:group="group"
:toggle="() => toggleGroup(group)">
</slot>
<div v-else @click="toggleGroup(group)">
{{ group.name }}
</div>
</div>
<transition name="fade">
<div v-show="group.isExpanded">
<!-- 表单内容 -->
</div>
</transition>
</div>
</template>
<script>
export default {
methods: {
toggleGroup(group) {
this.$set(group, 'isExpanded', !group.isExpanded)
}
}
}
</script>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s, max-height 0.3s;
max-height: 1000px;
overflow: hidden;
}
.fade-enter, .fade-leave-to {
opacity: 0;
max-height: 0;
}
</style>
父组件使用
java
<CollapsibleForm :groups="formGroups">
<template v-slot:group="{ group, toggle }">
<div class="group-header">
<h3>{{ group.name }}</h3>
<button @click="toggle">
{{ group.isExpanded ? '收起' : '展开' }}
<Icon :name="group.isExpanded ? 'collapse' : 'expand'" />
</button>
</div>
</template>
</CollapsibleForm>
-
条件插槽避免不必要的内容渲染
-
作用域插槽减少props传递层级
五、动态插槽名
实现原理:
-
Vue 3 使用 ES6 的
[ ]
计算属性语法实现动态插槽名 -
编译阶段将动态插槽名转换为渲染函数参数
-
运行时通过
resolveDynamicComponent
函数解析实际插槽
java
<!-- 父组件 -->
<template>
<DynamicComponent>
<template v-slot:[currentSlot]>
当前显示: {{ currentSlot }} 的内容
</template>
</DynamicComponent>
</template>
<script setup>
import { ref } from 'vue'
const currentSlot = ref('header') // 可动态切换为 'footer' 或 'content'
</script>
-
实现运行时内容切换
-
支持响应式数据驱动
-
适用于国际化、权限控制等场景
多语言内容切换用起来就舒服了。
java
<LocalizedContent>
<template v-slot:[currentLang]>
{{ messages[currentLang] }}
</template>
</LocalizedContent>
<script setup>
const currentLang = ref('zh-CN')
const messages = {
'zh-CN': '你好世界',
'en-US': 'Hello World',
'ja-JP': 'こんにちは世界'
}
</script>
多根节点
既然讲到vue3,顺便说说多根节点。
为什么 Vue 2 无法支持多根节点?
是有什么难题吗还是什么?
Vue 2 的虚拟 DOM 实现基于单根树结构,每个组件必须返回一个单一的根 VNode(虚拟节点)。这种设计简化了:
-
Diff 算法:通过递归比较单树结构,实现高效的 DOM 更新
-
生命周期管理:组件的挂载/卸载操作有明确的入口点
-
属性继承:父组件传递的属性可以明确绑定到根元素
Vue 2 的模板编译器将模板转换为渲染函数时,要求模板必须有单一根元素:
java
<!-- 有效模板 -->
<div>
<h1>标题</h1>
<p>内容</p>
</div>
<!-- 无效模板(Vue 2) -->
<h1>标题</h1>
<p>内容</p>
编译器会抛出错误:"Component template should contain exactly one root element"
父组件传递的非 prop 属性(如 class、style、事件监听器)会自动绑定到子组件的根元素:
多根节点场景下,这种自动继承机制无法确定应该应用到哪个元素。
$el
属性指向组件实例的根 DOM 元素:
java
mounted() {
console.log(this.$el) // 根DOM元素
}
多根节点情况下,$el
应该指向哪个元素?这个问题没有明确的解决方案。
Vue 3 如何突破多根节点限制?
之前vue2是因为框架设计就是如此,想要支持多根节点,得重构框架了。
Vue 3 引入了特殊的 Fragment 节点,作为多根组件的逻辑容器:
java
// 编译后的渲染函数
import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(),
_createBlock(Fragment, null, [
_createVNode("h1", null, "标题"),
_createVNode("p", null, "内容")
])
)
}
Fragment 节点特点:
-
不渲染为实际 DOM 元素
-
在虚拟 DOM 中作为逻辑容器
-
不影响实际 DOM 结构
Vue 3 重写了虚拟 DOM 的 diff 算法(称为 "patch" 算法),使其能处理 Fragment 节点:
-
支持同级多节点比较
-
使用 Map 进行 key 索引优化
-
支持片段移动检测
默认不会自动继承属性,需要显式绑定
java
<template>
<header v-bind="$attrs">标题区域</header>
<main>主要内容</main>
<footer>底部信息</footer>
</template>
<script>
export default {
inheritAttrs: false // 禁用自动继承
}
</script>
属性继承规则:
-
所有非 prop 属性存储在
$attrs
对象中 -
需要手动指定哪些元素继承属性
-
事件监听器存储在
$attrs.onClick
等形式中
插槽与多根节点的结合实践
java
<!-- LayoutSystem.vue -->
<template>
<div v-if="$slots.top" class="top-section">
<slot name="top" />
</div>
<div class="main-content">
<slot />
</div>
<div v-if="$slots.aside" class="sidebar">
<slot name="aside" />
</div>
</template>
<!-- 使用 -->
<LayoutSystem>
<template #top>
<NavigationBar />
</template>
<ArticleContent />
<template #aside>
<RecommendationPanel />
</template>
</LayoutSystem>
-
消除不必要的包裹元素
-
更自然的语义化模板
-
灵活控制属性继承
不像vue2,还要外层用一个div元素包裹起来。
动态门户组件
动态插槽名 + 多根节点的一种实践。
PortalComponent.vue
java
<template>
<component :is="containerElement" v-for="(slot, index) in activeSlots" :key="index">
<slot :name="slot" />
</component>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
slots: Array,
containerElement: {
type: String,
default: 'div'
}
})
const activeSlots = computed(() =>
props.slots.filter(slot => useSlots()[slot])
)
</script>
使用
java
<PortalComponent :slots="['header', 'notification', 'footer']">
<template #header>...</template>
<template #notification>...</template>
</PortalComponent>
六、Vue 2 vs Vue 3 插槽差异
特性 | Vue 2 | Vue 3 |
---|---|---|
作用域插槽 | slot-scope | v-slot 统一语法 |
默认插槽 | slot | v-slot:default |
动态插槽名 | 不支持 | v-slot:[dynamicName] |
$slots API |
this.$slots | useSlots() 组合式API |
碎片支持 | 单根节点限制 | 支持多根节点 |
一些个人的建议:
-
命名规范 :使用kebab-case命名插槽(如
action-buttons
) -
作用域控制:仅暴露必要的数据和方法
-
性能优化 :对动态内容使用
v-if="$slots.name"
-
文档注释:清晰说明插槽的预期结构和可用属性
至此撒花~
后记
插槽可以让Vue组件具备了极强的灵活性和可扩展性。可以构建出既高度复用又充分定制的组件体系,大幅提升开发效率和代码质量。
我们在实际项目中或多或少遇到一些奇奇怪怪的问题。
自己也会对一些写法的思考,为什么不行🤔,又为什么行了?
最后,祝君能拿下满意的offer。
我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车
👍 如果对您有帮助,您的点赞是我前进的润滑剂。
以往推荐
vue2和Vue3和React的diff算法展开说说:从原理到优化策略
前端哪有什么设计模式(14k+)