Skip to content

作用域插槽(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

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

代码示例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 下的资源才用 / 开头引用。

练习 ​

  1. 改造作用域插槽:把笔记中的 BookList 改成只接收 boy、girl 两个数组,通过 <slot :boy="boy" :girl="girl"></slot> 暴露;父组件用 #default="{ boy, girl }" 解构,分别渲染两组书名列表。要求同时写出三种等价写法中的两种,并说明为何不能三种一起写。

  2. 多插槽 + 作用域:实现一个 DataTable 组件,提供 #header(暴露 columns)和 #default(暴露 row、index)两个插槽。父组件用它在表格里自定义表头和每行内容。写出子组件 slot 部分与父组件调用部分。

  3. 规则验证题:在项目中分别把 vue.config.js 的 lintOnSave 设为 true、'warning'、false,各制造一次 no-unused-vars 错误,记录三种情况下的终端输出与页面表现,并给出团队项目中你会选哪种及理由。

  4. 排错题:项目启动后页面全白,终端无红色报错。请按顺序写出你的排查步骤(至少 4 步),并说明每一步要观察什么现象来判断问题所在。

  5. 工程化整理题:把一份静态 HTML 首页搬进脚手架,列出必须处理的事项清单(至少 5 项),并标注哪些资源应放 public、哪些应放 src/assets,以及为什么。

原视频对照 ​

#视频时长
1作用域插槽06:10
2项目预览15:53
3通过脚手架搭建项目06:26
4整理项目17:38
5解决eslint问题11:37
6放置样式25:55

小结 ​

  1. 作用域插槽的本质是子组件向插槽内容传值,slotProps 上能取到什么,完全由子组件 <slot :xxx="..."> 绑定了什么决定,不存在隐式透传。
  2. v-slot、v-slot:default、#default 三者等价,同一插槽只能声明一次;具名插槽必须带名字,简写 # 后不能有空格。
  3. 插槽内容的编译作用域仍在父组件,所以子组件数据必须通过作用域插槽显式暴露——这也是后续做权限控制、表格自定义渲染的基础。
  4. 脚手架默认带 ESLint,serve 阶段 lint 报错会中断编译,所以"页面白屏但看不出逻辑错误"时,第一反应应当是看终端。
  5. ESLint 问题的处理优先级:改代码 > 单条规则置 0 > 全局 lintOnSave: false;团队协作项目应保留检查,个人练手项目才考虑整体关闭。
  6. vue.config.js 的任何修改都需要重启 devServer 才生效,它不是一个需要频繁改动的文件。
  7. 静态页面搬进项目的核心是三类资源的归位:全局重置/第三方压缩样式走 public + index.html link,业务样式与图片走 src,缺失的 loader 必须补装(less、less-loader,且加 -D)。
  8. 遇到无法定位的诡异编译错误,删掉 node_modules 重新安装(网络丢包导致)往往比逐行排查更快——但这是兜底手段,不是首选手段。