主题
作用域插槽(Lesson13 3/3)
本页对应 Lesson13,共 6 段视频,约 1 小时 23 分。
学习目标
- 能说明作用域插槽的作用,并使用
v-slot、#插槽名等语法在父组件中获取子组件通过插槽传递的A、B、ARR等数据。 - 能根据项目预览和接口文档,安装项目依赖并通过
node server.js或npm run server启动本地服务,验证页面可访问。 - 能使用 Vue CLI 默认 Vue2 模板创建项目,并完成替换站点图标、修改页面标题、调整
src入口文件等基础整理操作。 - 能通过
vue.config.js配置入口、页面标题、publicPath、lintOnSave等选项,并说明修改配置后需重启项目才能生效。 - 能定位 ESLint 报错,通过修改
.eslintrc规则或配置lintOnSave解决“变量已定义但未使用”“组件名不符合规则”等问题。 - 能将静态页面的 HTML 结构、CSS、图片和 Less 样式迁入 Vue 项目,安装并配置
less、less-loader,修正图片路径后使首页正常渲染。
知识点
作用域插槽要解决什么问题
为什么需要:普通插槽只能让父组件把一段模板“塞”进子组件,但这段模板默认处在父组件作用域里,拿不到子组件 data 中的数据。实际开发中,子组件往往掌握数据(例如 BookList 里的 a、b、arr),父组件却想决定这些数据怎么展示,于是需要作用域插槽:子组件把数据“抛”给插槽内容,父组件在插槽里接收并使用。
怎么想:把插槽理解成一个“带参数的占位”。子组件在 <slot> 上绑定属性,父组件在使用子组件时用 v-slot 声明一个接收对象,这个对象就装子组件传出来的数据。
怎么做:先看子组件 BookList.vue,它在 slot 上绑定了 :a="a" :b="b" :arr="arr":
vue
<template>
<div>
<slot :a="a" :b="b" :arr="arr"></slot>
</div>
</template>
<script>
export default {
name: "BookList",
data(){
return {
a:1,
b:2,
arr:[1,2,3,4,5]
}
},
props:["bookList"]
}
</script>
<style scoped>
</style>易错点::a="a" 是动态绑定子组件 data 里的 a;如果写成 a="a",传出去的就是字符串。父组件访问时属性名必须和这里一致。
父组件:用 v-slot 接收插槽 props
为什么需要:子组件已经把数据挂到插槽上了,父组件必须声明一个变量来接住它,否则模板里无法使用。
怎么想:v-slot="slotProps" 中的 slotProps 名字可以自己取,它代表子组件传出来的所有插槽属性组成的对象。因此用 slotProps.a、slotProps.b、slotProps.arr 访问。
怎么做:App.vue 中保留了三种等价写法,核心代码如下:
vue
<template>
<div>
<!-- 1-->
<!-- <BookList v-slot="slotProps">-->
<!-- <div>a:{{slotProps.a}}</div>-->
<!-- <div>b:{{slotProps.b}}</div>-->
<!-- <div>arr:{{slotProps.arr}}</div>-->
<!-- </BookList>-->
<!-- 2 -->
<!-- <BookList v-slot:default="slotProps">-->
<!-- <div>a:{{slotProps.a}}</div>-->
<!-- <div>b:{{slotProps.b}}</div>-->
<!-- <div>arr:{{slotProps.arr}}</div>-->
<!-- </BookList>-->
<!-- 3-->
<BookList #default="slotProps">
<div>a:{{slotProps.a}}</div>
<div>b:{{slotProps.b}}</div>
<div>arr:{{slotProps.arr}}</div>
</BookList>
</div>
</template>
<script>
// 插槽:在子组件当中通过内置的slot组件进行占位。该占位可以呈现使用子组件时所包裹的内容。
import BookList from "@/components/BookList";
export default {
name: "App",
components: {BookList},
data(){
return {
boy:[
{
id:"1",
bookName:"这个世界很危险",
author:"叶知风"
},
{
id:"2",
bookName:"三国:我为刘禅,霄汉永灿",
author:"麻货"
}
],
girl:[
{
id:"4",
bookName:"不装了,财阀大佬暗恋我很久了",
author:"我爱干饭"
},
{
id:"5",
bookName:"误入豪门,闪婚老公每天花式吃醋",
author:"这一剑惊鸿"
}
]
}
}
}
</script>
<style lang="less">
</style>易错点:v-slot:default="slotProps" 是默认插槽的完整写法,default 可省略成 v-slot="slotProps";#default="slotProps" 是 v-slot:default 的简写。slotProps 不是固定名字,但一旦声明,模板里就要通过它访问属性,不能直接写 a。
调试过程与验证
转写稿中的推导顺序是:先在子组件里写普通 <slot>,父组件包裹一个 <div>1</div>,页面能显示 1,说明匿名插槽生效;再给插槽起名并用 #a 使用,仍能显示;接着在 <slot> 上写 :a="a" :b="b" :arr="arr",父组件用 v-slot="slotProps" 接收,页面最终显示 a:1、b:2、arr:[1,2,3,4,5]。再换成 v-slot:default 和 #default,效果一致。调试时重点检查三处:子组件 slot 是否绑定属性、父组件 v-slot 是否写在子组件标签上、访问属性名是否一致。
易错点小结
- 作用域插槽的数据来自子组件,不是父组件
data里直接拿到的。 slotProps是接收对象,访问要写slotProps.a,不要漏掉前缀。- 默认插槽可省略
default;具名插槽用v-slot:名字或#名字。 v-slot通常写在组件标签或<template>上,不要随便写在普通div上。- 子组件绑定名与父组件访问名必须对应,否则页面为空或报错。
课堂画面






代码示例
代码示例1:子组件 src/components/BookList.vue
vue
<template>
<div>
<slot :a="a" :b="b" :arr="arr"></slot>
</div>
</template>
<script>
export default {
name: "BookList",
data() {
return {
a: 1,
b: 2,
arr: [1, 2, 3, 4, 5]
};
},
props: ["bookList"]
};
</script>
<style scoped>
</style>要点解释:
<slot :a="a" :b="b" :arr="arr"></slot>是作用域插槽的关键行:子组件把data中的a、b、arr通过slot暴露给父组件。:a、:b、:arr是动态绑定,父组件拿到的是子组件的数据;如果写成a="a",父组件拿到的是字符串"a"。- 父组件不能直接访问子组件
data,必须通过作用域插槽接收。 props: ["bookList"]在本示例中未参与插槽传值,保留自笔记代码,不影响运行。
代码示例2:父组件 src/App.vue(#default 写法)
vue
<template>
<div>
<!-- 1 -->
<!-- <BookList v-slot="slotProps">
<div>a:{{slotProps.a}}</div>
<div>b:{{slotProps.b}}</div>
<div>arr:{{slotProps.arr}}</div>
</BookList> -->
<!-- 2 -->
<!-- <BookList v-slot:default="slotProps">
<div>a:{{slotProps.a}}</div>
<div>b:{{slotProps.b}}</div>
<div>arr:{{slotProps.arr}}</div>
</BookList> -->
<!-- 3 -->
<BookList #default="slotProps">
<div>a:{{slotProps.a}}</div>
<div>b:{{slotProps.b}}</div>
<div>arr:{{slotProps.arr}}</div>
</BookList>
</div>
</template>
<script>
import BookList from "@/components/BookList";
export default {
name: "App",
components: { BookList },
data() {
return {
boy: [
{
id: "1",
bookName: "这个世界很危险",
author: "叶知风"
},
{
id: "2",
bookName: "三国:我为刘禅,霄汉永灿",
author: "麻货"
}
],
girl: [
{
id: "4",
bookName: "不装了,财阀大佬暗恋我很久了",
author: "我爱干饭"
},
{
id: "5",
bookName: "误入豪门,闪婚老公每天花式吃醋",
author: "这一剑惊鸿"
}
]
};
}
};
</script>
<style lang="less">
</style>要点解释:
<BookList #default="slotProps">中#default是v-slot:default的简写,default表示默认插槽。slotProps是父组件自定义的变量名,里面包含子组件<slot>上绑定的所有属性:a、b、arr。- 模板中必须通过
slotProps.a、slotProps.b、slotProps.arr访问,不能直接写{{ a }}。 - 注释中的写法 1、2 与写法 3 等价;
App.vue里的boy、girl未参与插槽传值,不影响本示例运行。
代码示例3:父组件 src/App.vue(v-slot="slotProps" 等价写法)
vue
<template>
<div>
<BookList v-slot="slotProps">
<div>a:{{slotProps.a}}</div>
<div>b:{{slotProps.b}}</div>
<div>arr:{{slotProps.arr}}</div>
</BookList>
</div>
</template>
<script>
import BookList from "@/components/BookList";
export default {
name: "App",
components: { BookList },
data() {
return {
boy: [
{
id: "1",
bookName: "这个世界很危险",
author: "叶知风"
},
{
id: "2",
bookName: "三国:我为刘禅,霄汉永灿",
author: "麻货"
}
],
girl: [
{
id: "4",
bookName: "不装了,财阀大佬暗恋我很久了",
author: "我爱干饭"
},
{
id: "5",
bookName: "误入豪门,闪婚老公每天花式吃醋",
author: "这一剑惊鸿"
}
]
};
}
};
</script>
<style lang="less">
</style>要点解释:
<BookList v-slot="slotProps">等价于<BookList v-slot:default="slotProps">,也等价于<BookList #default="slotProps">。v-slot的值必须是父组件模板中声明的变量名,示例中为slotProps;改名为其他名字时,模板里所有引用要同步修改。- 易错点:不要写成
v-slot:default="a,b,arr",接收的是一个对象,不是多个变量。 - 易错点:作用域插槽的数据由子组件
slot上的绑定决定,父组件不能凭空访问子组件其他未绑定的数据。
常见错误
1. 父组件模板里 slotProps.a 渲染为空白
- 现象:
<BookList v-slot="slotProps">{{ slotProps.a }}</BookList>页面无内容,控制台无报错。 - 原因:子组件写成了
<slot></slot>,忘记在slot标签上绑定属性,作用域插槽的 props 对象是空的。 - 修法:子组件必须显式绑定
<slot :a="a" :b="b" :arr="arr"></slot>;绑什么名字,父组件slotProps上就只有什么名字,二者是白名单关系,不是自动透传data。
2. 同一个组件上 v-slot 与 #default 并存,编译直接报错
- 现象:
<BookList v-slot="slotProps" #default="slotProps">报 "duplicate attribute" 或插槽重复定义。 - 原因:
v-slot(不带参数)本身就是v-slot:default的简写,两种写法指向同一个插槽,属于重复声明。 - 修法:三选一,只留一种:
v-slot="slotProps"/v-slot:default="slotProps"/#default="slotProps"。只有在具名插槽(如#header)时,才必须带参数。
3. 混用 Vue2 旧语法 slot-scope 与 v-slot 报错
- 现象:
<BookList slot-scope="slotProps" #default="slotProps">编译失败。 - 原因:
slot-scope是 2.6 之前的写法,与v-slot互斥,且slot-scope不能用在组件标签本身上(只能用在 template 上)。 - 修法:统一改用
v-slot/#;确实要保留旧写法就整段统一为slot-scope,不要两种混着写。
4. new Vue() 赋给变量却未使用,项目编译不过
- 现象:控制台报
'vm' is defined but never used,页面直接编译失败、白屏。 - 原因:脚手架默认开启 ESLint,
no-unused-vars规则命中;vue-cli-service serve阶段 lint 报错会中断编译。 - 修法:优先改代码(真正使用该变量,或干脆不赋值);确需保留则在
.eslintrc.js的rules中加'no-unused-vars': 0;全局关掉可用vue.config.js里lintOnSave: false(团队项目不建议)。
5. 单单词组件名 home 触发 vue/multi-word-component-names
- 现象:新建
Home/index.vue,name: 'home'后编译报错,提示组件名应为多单词。 - 原因:官方规则要求组件名多单词,避免与 HTML 标签冲突。
- 修法:改名
my-home/HomeIndex;或在.eslintrc.js关闭该规则。
6. 改了 vue.config.js(标题、indexPath、lintOnSave)却不生效
- 现象:
title还是默认项目名,favicon还是 Vue logo,白屏或入口找不到。 - 原因:配置文件只在启动时读取一次,热更新不会重启 devServer;favicon 还受浏览器缓存影响。
- 修法:改完配置必须重启
npm run serve;图标不变化时清缓存后重新打开标签页;若改过pages.index.entry,还要确认public/index.html里的挂载点(如#app)与入口中$mount的目标一致,否则就是白屏。
7. 样式/图片搬进项目后报 Can't resolve 'less-loader' 或图片 404
- 现象:贴入首页代码后编译报缺少
less-loader,或背景图 404、控制台提示找不到文件。 - 原因:脚手架选默认配置时没装 less 相关依赖;图片路径仍是从静态页面拷来的绝对路径(如
/img/xx.png),而图片实际放在src/assets。 - 修法:
npm i less less-loader -D;路径改为相对src的引用(../assets/img/xx.png),或在src内统一用~@/assets/...;public下的资源才用/开头引用。
练习
改造作用域插槽:把笔记中的
BookList改成只接收boy、girl两个数组,通过<slot :boy="boy" :girl="girl"></slot>暴露;父组件用#default="{ boy, girl }"解构,分别渲染两组书名列表。要求同时写出三种等价写法中的两种,并说明为何不能三种一起写。多插槽 + 作用域:实现一个
DataTable组件,提供#header(暴露columns)和#default(暴露row、index)两个插槽。父组件用它在表格里自定义表头和每行内容。写出子组件slot部分与父组件调用部分。规则验证题:在项目中分别把
vue.config.js的lintOnSave设为true、'warning'、false,各制造一次no-unused-vars错误,记录三种情况下的终端输出与页面表现,并给出团队项目中你会选哪种及理由。排错题:项目启动后页面全白,终端无红色报错。请按顺序写出你的排查步骤(至少 4 步),并说明每一步要观察什么现象来判断问题所在。
工程化整理题:把一份静态 HTML 首页搬进脚手架,列出必须处理的事项清单(至少 5 项),并标注哪些资源应放
public、哪些应放src/assets,以及为什么。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 作用域插槽 | 06:10 |
| 2 | 项目预览 | 15:53 |
| 3 | 通过脚手架搭建项目 | 06:26 |
| 4 | 整理项目 | 17:38 |
| 5 | 解决eslint问题 | 11:37 |
| 6 | 放置样式 | 25:55 |
小结
- 作用域插槽的本质是子组件向插槽内容传值,
slotProps上能取到什么,完全由子组件<slot :xxx="...">绑定了什么决定,不存在隐式透传。 v-slot、v-slot:default、#default三者等价,同一插槽只能声明一次;具名插槽必须带名字,简写#后不能有空格。- 插槽内容的编译作用域仍在父组件,所以子组件数据必须通过作用域插槽显式暴露——这也是后续做权限控制、表格自定义渲染的基础。
- 脚手架默认带 ESLint,
serve阶段 lint 报错会中断编译,所以"页面白屏但看不出逻辑错误"时,第一反应应当是看终端。 - ESLint 问题的处理优先级:改代码 > 单条规则置 0 > 全局
lintOnSave: false;团队协作项目应保留检查,个人练手项目才考虑整体关闭。 vue.config.js的任何修改都需要重启 devServer 才生效,它不是一个需要频繁改动的文件。- 静态页面搬进项目的核心是三类资源的归位:全局重置/第三方压缩样式走
public+index.htmllink,业务样式与图片走src,缺失的 loader 必须补装(less、less-loader,且加-D)。 - 遇到无法定位的诡异编译错误,删掉
node_modules重新安装(网络丢包导致)往往比逐行排查更快——但这是兜底手段,不是首选手段。