Vue3手册译稿 - 深入组件 - 插槽

插槽

本章节需要掌握组件基础

插槽内容

Vue实现一套优秀的内容分发接口,基于WEB组件草案,使用<slot>标签提供内容分发服务。
这允许你像这样构成一个组件:

<todo-button>
  Add todo
</todo-button>

todo-button模板应该这样写:

<!-- todo-button 组件模板 -->
<button class="btn-primary">
  <slot></slot>
</button>

组件被渲染时,<slot></solt>将会被替换为:Add todo:

<!-- 渲染后的 HTML -->
<button class="btn-primary">
  Add todo
</button>

插槽替换为字符仅仅是基本功能,它可以包含任何模板代码,如HTML代码:

<todo-button>
  <!-- 添加Font Awesome图标 -->
  <i class="fas fa-plus"></i>
  Add todo
</todo-button>

甚至其他组件:

<todo-button>
  <!-- 使用一个组件来加一个图标 -->
  <font-awesome-icon name="plus"></font-awesome-icon>
  Add todo
</todo-button>

如果组件模板没有任何插槽,提供任何内容都会被拒绝:

<!-- todo-button 组件模板-->
<button class="btn-primary">
  Create a new item
</button>
<todo-button>
  <!-- 下面文字不会被渲染 -->
  Add todo
</todo-button>

渲染域

如果你想在组件内使用data,例如:

<todo-button>
  Delete a {{ item.name }}
</todo-button>

插槽有权限访问有共同实例属性(如相同域)的其余模板。

插槽没有权限访问<todo-button>域,下例中访问action将不会工作:

<todo-button action="delete">
  Clicking here will {{ action }} an item
  <!--
  `action`将会提示未定义, 因为这个内容来自于`<todo-button>`, 不会定义在`<todo-button>` 组件内。
  -->
</todo-button>

记住这条规则:

所有父模板都编译在父组件域,所有子模板都编译在子组件域。

备用内容

有些情形给插槽一个备用内容(如默认值)是比较有用的,当没有任何内容提供给插槽时,将会使用备用内容渲染。例如下面的<submit-button>组件:

<button type="submit">
  <slot></slot>
</button>

大部分时候我们都希望将button中的插槽渲染成Submit,将Submit设置为备用内容,我们可以将它放到<slot></slot>中间:

<button type="submit">
  <slot>Submit</slot>
</button>

当使用这个组件且不为插槽提供内容时:

<submit-button></submit-button>

会被渲染为:

<button type="submit">
  Submit
</button>

但如果我们提供了内容:

<submit-button>
  Save
</submit-button>

渲染结果则为:

<button type="submit">
  Save
</button>

具名插槽

使用多个插槽是经常用到的,如下面的<base-layout>组件模板:

<div class="container">
  <header>
    <!-- 想把头内容放在这里 -->
  </header>
  <main>
    <!-- 想把主要内容放在这里 -->
  </main>
  <footer>
    <!-- 想把脚内容放到这里 -->
  </footer>
</div>

这种情形,插槽有一个特殊的属性name,它可以为每个插槽定义一个唯一的ID,这样你就可以决定哪些内容渲染到哪个地方了:

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

没有指定name的插槽的名字是default
模板中使用具名插槽,我们需要使用v-slot指令,v-slot参数就是插槽的名字:

<base-layout>
  <template v-slot:header>
    <h1> 这里可能是标题</h1>
  </template>

  <template v-slot:default>
    <p> 主要内容段落</p>
    <p>另一段内容</p>
  </template>

  <template v-slot:footer>
    <p>一些联系方式</p>
  </template>
</base-layout>

现在所有模板内的元素都被传递到相应的插槽里,最终渲染结果可能如下:

<div class="container">
  <header>
    <h1>这里可能是标题</h1>
  </header>
  <main>
    <p> 主要内容段落</p>
    <p>另一段内容</p>
  </main>
  <footer>
    <p>一些联系方式</p>
  </footer>
</div>

注意:v-slot只能放在template内(有一种例外)

作用域插槽

有时,只有在子组件内部可以访问data内容是有用的。一个常用的例子就是使用组件渲染一个数组项,我们想定制数组项的渲染方式。
例如我们有一个组件,里面包含一个todo-items列表:

app.component('todo-list', {
  data() {
    return {
      items: ['Feed a cat', 'Buy milk']
    }
  },
  template: `
    <ul>
      <li v-for="(item, index) in items">
        {{ item }}
      </li>
    </ul>
  `
})

我们想把`{{ item }}`` 用一个插槽替换,以在父组件来定制:

<todo-list>
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

但这不会工作,因为只有<todo-list>组件才能够访问item和父组件提供的插槽内容。
要想让父组件提供的插槽内容能够使用item,我们可以添加<slot>标签并绑定成属性:

<ul>
  <li v-for="( item, index ) in items">
    <slot :item="item"></slot>
  </li>
</ul>

一个插槽可以绑定多个属性:

<ul>
  <li v-for="( item, index ) in items">
    <slot :item="item" :index="index" :another-attribute="anotherAttribute"></slot>
  </li>
</ul>

属性绑定到<slot>叫做slot props。现在在父组件域,我们可以使用v-slot绑定值来定义一个slot props名字:

<todo-list>
  <template v-slot:default="slotProps">
    <i class="fas fa-check"></i>
    <span class="green">{{ slotProps.item }}</span>
  </template>
</todo-list>


在这个例子中,我们选择命名一个包含全部slot props的对象slotProps,你可以使用任何名称随你喜欢。

单默认插槽缩写语法

如上例,单个默认插槽,组件模板可以使用组件标签。这允许我们直接在组件上使用v-slot

<todo-list v-slot:default="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>
</todo-list>

可以更简短,就像没有指定内容分配给默认插槽,无参数的v-solt指向默认插槽:

<todo-list v-slot="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>
</todo-list>

注意默认插槽的缩写语法不能混合具名插槽,这会导致作用域名不唯一:

<!--不合法, 会出现告警 -->
<todo-list v-slot="slotProps">
  <i class="fas fa-check"></i>
  <span class="green">{{ slotProps.item }}</span>

  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</todo-list>

无论何时有多个插槽时,需要为所有插槽使用完整的模板语法:

<todo-list>
  <template v-slot:default="slotProps">
    <i class="fas fa-check"></i>
    <span class="green">{{ slotProps.item }}</span>
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</todo-list>

解构插槽props

内部原理是,作用域插槽是插槽内含有一个参数的函数实现:

function (slotProps) {
  // ... slot content ...
}

这意味着v-slot的值可以接受任何合法的JavaScript表达式,函数定义可以出现在参数位置。所以你可以使用ES2005结构脱离特定的插槽props,像这样:

<todo-list v-slot="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

这可以使用模板看起来更清爽,特别是插槽有多个props时。这也开启了其它可能性,比如重命名props,e.g. item转成todo

<todo-list v-slot="{ item: todo }">
  <i class="fas fa-check"></i>
  <span class="green">{{ todo }}</span>
</todo-list>

你甚至可以定义一个备用,在有些场景下插槽prop未定义时。

<todo-list v-slot="{ item = 'Placeholder' }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

动态具名插槽

动态指令参数同样也适用于v-slot,允许你使用动态插槽名:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

具名插槽简写

v-on(@),v-bind(:)一样,v-slot也可以简写,用#号替换参数前(v-slot:)的所有内容。例如,v-slot:header 可以重写为#header

<base-layout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

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

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

但是,和其他指令一样,缩写仅适用于提供了参数的情形。这意味着下面的例子是不合法的:

<!-- 这会触发一个警告 -->

<todo-list #="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

取而代之你必须为简写的插槽指定一个名字:

<todo-list #default="{ item }">
  <i class="fas fa-check"></i>
  <span class="green">{{ item }}</span>
</todo-list>

posted on 2021-03-25 14:07  zhouyu  阅读(91)  评论(0编辑  收藏  举报

导航