.
.
.
.

# 作用域插槽 - 干嘛用

需求:老板对 子组件 的<mark>数据展示</mark>不满意,想在 根样式 里面自定义样式(如下)

由于要在父组件拿到子组件数据,就需要用到作用域插槽。

改前:

改后:

改前代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <!-- 父组件模板 -->
  <div id='app'>
    <books-info></books-info>
  </div>

  <!-- 子组件模板 -->
  <template id="booksInfo">
    <div>
      <!-- 推荐书目 -->
      <slot>
        <h2>畅销书:{{hotBook.name}}</h2>
        <hr>
      </slot>
      <!-- 展示 -->
      <slot>
        <ul>
          <li v-for="book in books">{{book}}</li>
        </ul>
        <hr>
      </slot>
      <!--总价-->
      <slot>
        <p>总价:{{totalPrice}}</p>
      </slot>
    </div>
  </template>

  <script src="../dist/vue.js"></script>
  <script> // 子组件 const booksInfo = { template: '#booksInfo', computed: { // 获取总价格 totalPrice() { return this.books.reduce((total, current) => total += current.price * current.count, 0).toFixed(2); }, // 获取畅销书(价格最高) hotBook() { return this.books.sort((a, b) => b.price - a.price)[0]; } }, data() { return { books: [{ id: 1, name: 'International Mobility Agent', date: 'Mon Nov 04 2019 19:26:59 GMT+0800 (中国标准时间)', price: 348.10, count: 4 }, { id: 2, name: 'Regional Metrics Developer', date: 'Sat Sep 28 2019 13:24:49 GMT+0800 (中国标准时间)', price: 867.67, count: 11 }, { id: 3, name: 'Product Applications Technician', date: 'Mon Nov 25 2019 01:23:04 GMT+0800 (中国标准时间)', price: 751.74, count: 2 }, { id: 4, name: 'Internal Identity Supervisor', date: 'Wed May 29 2019 23:08:32 GMT+0800 (中国标准时间)', price: 52.83, count: 3 }, { id: 5, name: 'Future Web Facilitator', date: 'Sat Sep 14 2019 13:16:19 GMT+0800 (中国标准时间)', price: 897.66, count: 5 }, { id: 6, name: 'Legacy Infrastructure Strategist', date: 'Fri Nov 08 2019 12:35:13 GMT+0800 (中国标准时间)', price: 851.90, count: 8 }, ] } } } // 父组件 const app = new Vue({ el: '#app', components: { booksInfo: booksInfo } }) </script>
</body>

</html>

# 解决方案

想要修改数据的样式,前提要拿到数据
而想要在父组件调用子组件数据,普遍做法是用事件,比较麻烦。
<mark>而且我现在只需要展示数据,那么就能用作用域槽轻松完成需求。</mark>

步骤:

  • slot 标签加上名字。如: <slot name='header'> (字符串随意)
  • slot 标签加上要绑定的数据。如:<slot name='header' :hot='hotBook'> (属性名随意)
  • 使用:<template v-slot:header='headerScope'>

改后代码:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <!-- 父组件模板 -->
  <div id='app'>
    <books-info>
      <template v-slot:header='headerScope'>
        <div style="background-color:#1abc9c">
          <h1>热卖畅销书:《{{headerScope.hot.name}}》,现在仅需 ¥{{headerScope.hot.price}}</h1>
        </div>
      </template>
      <template v-slot:default='tableScope'>
        <ul>
          <li v-for="book in tableScope.books">《{{book.name}}》 - ¥{{book.price}}</li>
        </ul>
      </template>
      <template v-slot:footer='footerScope'>
        <div>
          <h3>不用998,不用688,现在全部购买,仅需<font color='red'>{{footerScope.price}}</font>元! 仅需<font color='red'>
              {{footerScope.price}}</font>元!</h3>
        </div>
      </template>
    </books-info>
  </div>

  <!-- 子组件模板 -->
  <template id="booksInfo">
    <div>
      <!-- 推荐书目 -->
      <slot name='header' :hot='hotBook'>
        <h2>畅销书:{{hotBook.name}}</h2>
        <hr>
      </slot>
      <!-- 展示 -->
      <!--不给slot加name值,默认值为default-->
      <slot :books='books'>
        <ul>
          <li v-for="book in books">{{book}}</li>
        </ul>
        <hr>
      </slot>
      <!--总价-->
      <slot name='footer' :price='totalPrice'>
        <p>总价:{{totalPrice}}</p>
      </slot>
    </div>
  </template>

  <script src="../dist/vue.js"></script>
  <script> // 子组件 const booksInfo = { template: '#booksInfo', computed: { // 获取总价格 totalPrice() { return this.books.reduce((total, current) => total += current.price * current.count, 0).toFixed(2); }, // 获取畅销书(价格最高) hotBook() { return this.books.sort((a, b) => b.price - a.price)[0]; } }, data() { return { books: [{ id: 1, name: 'International Mobility Agent', date: 'Mon Nov 04 2019 19:26:59 GMT+0800 (中国标准时间)', price: 348.10, count: 4 }, { id: 2, name: 'Regional Metrics Developer', date: 'Sat Sep 28 2019 13:24:49 GMT+0800 (中国标准时间)', price: 867.67, count: 11 }, { id: 3, name: 'Product Applications Technician', date: 'Mon Nov 25 2019 01:23:04 GMT+0800 (中国标准时间)', price: 751.74, count: 2 }, { id: 4, name: 'Internal Identity Supervisor', date: 'Wed May 29 2019 23:08:32 GMT+0800 (中国标准时间)', price: 52.83, count: 3 }, { id: 5, name: 'Future Web Facilitator', date: 'Sat Sep 14 2019 13:16:19 GMT+0800 (中国标准时间)', price: 897.66, count: 5 }, { id: 6, name: 'Legacy Infrastructure Strategist', date: 'Fri Nov 08 2019 12:35:13 GMT+0800 (中国标准时间)', price: 851.90, count: 8 }, ] } } } // 父组件 const app = new Vue({ el: '#app', components: { booksInfo: booksInfo } }) </script>
</body>

</html>

# 后话


注意1

  • 默认插槽名为default,可以省略 default 直接写 v-slot
    缩写为 # 时不能不写参数,写成 #default (这点所有指令都一样,v-bind、v-on)
  • 多个插槽混用时, v-slot 不能省略 default
  • 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法

注意2
2.5及之前有 slot-scope 的写法。
这种写法在官网上说了,会在3.0之后完全弃用。

  • 官方 插槽 相关 API
  • 官网废弃 slot 的 说明

参考