# Vue内容分发slot

为了让组件可以组合,需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为 内容分发 (或 “transclusion” )。 Vue实现了一个内容分发 API,参照了当前 Web 组件规范草案,使用特殊的 <slot> 元素作为原始内容的插槽。

# 编译作用域

当你想在一个插槽中使用数据时,例如:

<navigation-link url="/profile">
  Logged in as {{ user.name }}
</navigation-link>

该插槽跟模板的其它地方一样可以访问相同的实例 property (也就是相同的“作用域”),而不能访问 <navigation-link> 的作用域。例如 url 是访问不到的:

<navigation-link url="/profile">
  Clicking here will send you to: {{ url }}
  <!--
  这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是
  _传递给_ <navigation-link> 的而不是
  在 <navigation-link> 组件*内部*定义的。
  -->
</navigation-link>

TIP

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

# 默认丢弃

一般地,如果子组件模板不包含 <slot> 插口,父组件的内容将会被丢弃。

<!-- parent -->
<div class="parent">
    <p>父组件</p>
    <child>
        <p>测试无 slot 插槽</p>
    </child>
</div>
<!-- child -->
<div class="child">
    <p>子组件</p>
</div>

最终渲染如下

<div class="parent">
    <p>父组件</p>
    <div class="child">
        <p>子组件</p>
    </div>
</div>

<!-- <p>测试无 slot 插槽</p> 被丢弃 -->

# 内联模板

  • inline-template这个特殊的 attribute 出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。这使得模板的撰写工作更加灵活。

WARNING

不过,inline-template 会让模板的作用域变得更加难以理解。所以作为最佳实践,在组件内优先选择 template 选项或 .vue 文件里的一个 <template> 元素来定义模板。

<div class="child">
    <p>子组件</p>
</div>
<div class="parent">
    <p>父组件</p>
    <child inline-template>
      <p>测试inline-template</p>
    </child>
</div>

最终渲染为如下内容

<div class="parent">
    <p>父组件</p>
    <p>测试inline-template</p>
</div>

# 匿名插槽

TIP

当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身

匿名slot只能作为没有slot属性的元素的插槽,有slot属性的元素如果没有配置slot,则会被抛弃




 


<!-- child组件 -->
 <div class="child">
    <p>子组件</p>
    <slot></slot>
</div>    
<!-- parent -->
<div class="parent">
    <p>父组件</p>
    <child>
      <p>测试匿名插槽slot</p>
    </child>
</div>

渲染结果





 



<div class="parent">
    <p>父组件</p>
    <div class="child">
        <p>子组件</p>
        <p>测试匿名插槽slot</p>
    </div>   
</div>

DANGER

如果出现多于1个的匿名slot,vue将报错。错误示例如下:

 <div class="child">
    <p>子组件</p>
    <slot></slot>
    <slot></slot>
</div>

Duplicate presence of slot “default” found in the same render tree - this will likely cause render errors.

# 插槽默认值

  • 最初在<slot>标签中的任何内容都被视为备用内容,或者称为默认值。
  • 备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容
<!-- child -->
<div class="child">
    <p>子组件</p>
    <slot>
        <p>我是默认值</p>
    </slot>
</div>    
<div class="parent">
    <p>父组件</p>
    <child></child>
</div>

渲染结果

<div class="parent">
    <p>父组件</p>
    <div class="child">
        <p>子组件</p>
        <p>我是插槽默认值</p>
    </div>  
</div>
  • 当slot存在默认值,且父元素在 <child> 中存在要插入的内容时,则显示设置值。
<!-- parent -->
<div class="parent">
    <p>父组件</p>
    <child>
        <p>我是设置值,会覆盖默认值</p>
    </child>
</div>

渲染结果

<div class="parent">
    <p>父组件</p>
    <div class="child">
        <p>子组件</p>
        <p>我是设置值,会覆盖默认值</p>
    </div>  
</div>

# 具名slot

  • <slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。
  • 多个 slot 可以有不同的名字。
  • 具名 slot 将匹配内容片段中有对应 slot 特性的元素
  • 一个不带 name 的 <slot> 出口会带有隐含的名字“default”
<div class="container">
    <header>
        <!-- 我们希望把页头放这里 -->
    </header>
    <main>
        <!-- 我们希望把主要内容放这里 -->
    </main>
    <footer>
        <!-- 我们希望把页脚放这里 -->
    </footer>
</div>

<!-- ===============> -->

<div class="container">
    <header>
        <slot name="header"></slot>
    </header>
    <main>
        <slot></slot>
    </main>
    <footer>
        <slot name="footer"></slot>
    </footer>
</div>

在向具名插槽提供内容的时候,我们可以在一个 <template>元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<base-layout>
    <template v-slot:header>
        <h1>Here might be a page title</h1>
    </template>

    <p>A paragraph for the main content.</p>
    <p>And another one.</p>

    <template v-slot:footer>
        <p>Here's some contact info</p>
    </template>
</base-layout>

# 具名slot缩写

TIP

2.6.0 新增

v-onv-bind 一样,v-slot 也有缩写,即把参数之前的所有内容 (v-slot:)替换为字符 #。

例如 v-slot:header 可以被重写为 #header

<!-- 和其它指令一样,该缩写只在其有参数的时候才可用。 -->
<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
  {{ user.firstName }}
</current-user>
<base-layout>
    <template #header>
        <h1>Here might be a page title</h1>
    </template>

    <p>A paragraph for the main content.</p>
    <p>And another one.</p>

    <template #footer>
        <p>Here's some contact info</p>
    </template>
</base-layout>

WARNING

如果希望使用缩写的话,你必须始终以明确插槽名取而代之:

# 作用域插槽

  • 作用域插槽是一种特殊类型的插槽,用作使用一个 (能够传递数据到) 可重用模板替换已渲染元素。
  • 在子组件中,只需将数据传递到插槽,就像将 props 传递给组件一样
  • 【可参考:element ui el-table-column 自定义】

在父级中,具有特殊属性 scope<template>元素必须存在,表示它是作用域插槽的模板。 scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象

<!-- child -->
<div class="child">
    <p>子组件</p>
    <slot xxx="msg from child"></slot>
</div>
<!-- parent -->
<div class="parent">
    <p>父组件</p>
    <child>
      <template scope="props">
        <p>hello from parent</p>
        <p>{{ props.xxx }}</p>
      </template>
    </child>
</div>

渲染结果

<div class="parent">
    <p>父组件</p>
    <div class="child">
        <p>子组件</p>
        <p>msg from parent</p>
        <p>msg from child</p>
    </div>
</div>

# 列表组件

作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项

var childNode = {
  template: `
  <ul>
    <slot name="item" v-for="item in items" :text="item.text">默认值</slot>
  </ul>
  `,
  data(){
    return{
      items:[
        {id:1,text:'第1段'},
        {id:2,text:'第2段'},
        {id:3,text:'第3段'},
      ]
    }
  }
};
var parentNode = {
  template: `
  <div class="parent">
    <p>父组件</p>
    <child>
      <template slot="item" scope="props">
        <li>{{ props.text }}</li>
      </template>
    </child>
  </div>
  `,
  components: {
    'child': childNode
  },
};

# 废弃的语法

# 带有slot attribute 的具名插槽

TIP

自 2.6.0 起被废弃。

# 带有slot-scope attribute 的作用域插槽

TIP

自 2.6.0 起被废弃。

Last Updated: 1/9/2023, 10:52:29 PM