Vue 2 插槽进阶:作用域插槽的正确打开方式
年前做后台系统,有个需求:同一种数据列表,A 页面要显示成表格,B 页面要显示成卡片。我本来想传个 mode 字段在组件里 if-else,写到一半意识到——这正是作用域插槽的教科书场景。
插槽的基础用法(具名 slot)就不赘述了,这篇直接上硬核部分。
问题的本质
普通插槽有个限制:插槽内容在父组件作用域编译,访问不到子组件的数据。
<!-- 父组件模板 -->
<user-card>
<!-- ❌ 报错:user 是子组件里的数据,这里访问不到 -->
<p>{{ user.name }}</p>
</user-card>
但"数据在子组件、渲染结构由父组件定"这个需求太常见了——所有"列表/表格/树"类组件都长这样。作用域插槽就是用来打通这层数据通道的。
语法拆解
分两步:子组件把数据传出去,父组件接住数据再渲染。
子组件 UserList.vue:
<template>
<ul>
<li v-for="user in users" :key="user.id">
<!-- 把 user 作为插槽的 props 传出去 -->
<slot :user="user" :index="$index">
<!-- 兜底内容:父组件没给插槽时显示 -->
{{ user.name }}
</slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
users: [
{ id: 1, name: '张三', role: '管理员' },
{ id: 2, name: '李四', role: '运营' },
],
}
},
}
</script>
父组件接住:
<user-list>
<!-- v-slot 声明一个插槽,slotProps 接收子组件传出的所有数据 -->
<template v-slot:default="slotProps">
<strong>{{ slotProps.user.name }}</strong>
<em>({{ slotProps.user.role }})</em>
</template>
</user-list>
实际项目里大家都会解构 + 用缩写 #:
<user-list>
<template #default="{ user, index }">
{{ index + 1 }}. {{ user.name }} —— {{ user.role }}
</template>
</user-list>
核心就一句:子组件在 <slot> 上绑属性传数据,父组件用 v-slot 接数据。方向和普通 props 恰好相反,所以也有人叫它"反向传参"。
实战:一个通用的数据列表组件
回到开头的需求。做一个 DataList 组件,逻辑(数据获取、加载态、空态)全包,渲染结构完全交给使用方:
<template>
<div class="data-list">
<div v-if="loading" class="state">加载中...</div>
<div v-else-if="!list.length" class="state">暂无数据</div>
<template v-else>
<div v-for="item in list" :key="item.id" class="data-item">
<slot :item="item" />
</div>
</template>
</div>
</template>
<script>
export default {
props: {
fetcher: { type: Function, required: true },
},
data() {
return { list: [], loading: true }
},
async created() {
this.list = await this.fetcher()
this.loading = false
},
}
</script>
A 页面用表格风格渲染:
<data-list :fetcher="fetchUsers">
<template #default="{ item }">
<div class="row">
<span>{{ item.name }}</span>
<span>{{ item.role }}</span>
</div>
</template>
</data-list>
B 页面用卡片风格渲染:
<data-list :fetcher="fetchUsers">
<template #default="{ item }">
<div class="card">
<h3>{{ item.name }}</h3>
<p>{{ item.role }}</p>
</div>
</template>
</data-list>
数据逻辑零重复,展示层完全自由。这就是 Element UI 的 Table 列、iView 的 List 都在用的模式。
两个容易绕晕的点
1. 解构重命名 + 默认值
<template #default="{ item: row = {} }">
{{ row.name || '未知' }}
</template>
插槽 props 的解构和函数参数解构规则一致,可以重命名、给默认值。
2. 作用域插槽的性能细节
Vue 2 里作用域插槽的渲染和普通插槽有差异:普通插槽在父组件更新时重渲染,作用域插槽作为一个函数,在子组件更新时才会重新执行。2.6 版本引入 v-slot 语法时做过一轮优化,尽量保证它只依赖传入的数据。如果你在列表里发现插槽内容没更新,先检查是不是依赖了插槽作用域之外的数据。
小结
- 作用域插槽解决"数据在子、结构在父"的矛盾,本质是子组件向插槽反向传参
- 子组件
<slot :user="user">,父组件#default="{ user }" - 列表/表格/树类组件的标准设计:组件管数据和状态,插槽管渲染
- 记准方向:普通 props 父→子,插槽 props 子→父
理解了作用域插槽,才算真正摸到了 Vue 组件复用的精髓——逻辑和表现的彻底分离。
看到这里了?如果这篇文章帮到过你
请我喝瓶可乐就好 🥤

评论0 条
还没有评论,来抢沙发~