Vue学习笔记8-深入了解组件2-插槽

590 阅读10分钟

前言

结合官方文档、废弃用法文档、知乎、掘金等资料进行插槽知识点整理。

插槽

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。

插槽是什么

slot,也称插槽,可以类比为插卡式的FC游戏机,游戏机(子组件)暴露卡槽(插槽)让用户插入不同的游戏磁条(自定义内容),游戏机会读取并加载磁条里的游戏。

Vue的slot,是组件的一块HTML模版,这块模版由使用组件者即父组件提供。可以说是子组件暴露的一个让父组件传入自定义内容的接口

游戏机
转载自掘金:slots详解

为什么需要引入插槽

举个例子,比如我要实现一个这样的 Alert 组件:

插槽例子
它会有默认的内容和样式,比如第一行的 Default 我们只需要使用:

<alert></alert>

当需要换用不同的样式与内容时,我们希望只需要这样调用:

<alert type="success">
    <strong>Success!</strong> Looks good to me!
</alert>

要实现这个功能就可以使用slots

代码1

代码2
当外部调用<slot>标签内容的时候,如果外部调用没有提供内容,则使用自己默认的内容,否则提供外部的内容。

转载自知乎:如何理解Vue.js的组件中的slot?

插槽内容

Vue 实现了一套内容分发的 API,将 元素作为承载分发内容的出口。

<navigation-link url="/profile">
  Your Profile
</navigation-link>

在模板内部:
<a
  v-bind:href="url"
  class="nav-link"
>
  <slot></slot>
</a>

当组件渲染的时候,<slot></slot> 将会被替换为“Your Profile”。插槽内可以包含任何模板代码,包括 HTML、甚至其它的组件。

如果 <navigation-link> 的 template 中没有包含一个 <slot> 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

编译作用域

当你想在一个插槽中使用数据时

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

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

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

后备内容

有时为一个插槽设置具体的后备 (也就是默认的) 内容是很有用的,它只会在没有提供内容的时候被渲染。

我们可能希望<button> 内绝大多数情况下都渲染文本“Submit”。为了将“Submit”作为后备内容,我们可以将它放在 <slot> 标签内

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

具名插槽

有时我们需要多个插槽。

对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽

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

一个不带 name 的 <slot> 出口会带有隐含的名字“default”

在向具名插槽提供内容的时候,我们可以在一个<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>

任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。

注意 v-slot 只能添加在 <template> (只有一种例外情况->独占默认插槽的缩写语法,不带参数的 v-slot 被假定对应默认插槽,同时不支持和具名插槽混用),这一点和已经废弃的 slot attribute 不同。

作用域插槽

用于让插槽内容能够访问子组件中才有的数据

<span>
  <slot>{{ user.lastName }}</slot>//子组件内部的property
</span>
我们可能想换掉备用内容,用名而非姓来显示:
<current-user>
  {{ user.firstName }}//外部无法访问内部的数据
</current-user>

然而上述代码不会正常工作,因为只有 <current-user> 组件可以访问到 user 而我们提供的内容是在父级渲染的。

为了让 user 在父级的插槽内容中可用,我们可以将 user 作为<slot>元素的一个 attribute 绑定上去。

<span>
  <slot v-bind:user="user">//暴露在外层
    {{ user.lastName }}
  </slot>
</span>

绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字

<current-user>html
  <template v-slot:default="slotProps">//使用带值的 v-slot 来定义我们提供的插槽 prop 的名字
    {{ slotProps.user.firstName }}
  </template>
</current-user>

在这个例子中,我们选择将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。

独占默认插槽的缩写语法

在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上。

这种写法还可以更简单。就像假定未指明的内容对应默认插槽一样,不带参数的 v-slot 被假定对应默认插槽。

<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确.

<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</current-user>

只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法

解构插槽 Prop

作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里。

function (slotProps) {
  // 插槽内容
}

这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。

所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop。

<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候。它同样开启了 prop 重命名等其它可能。

将 user 重命名为 person

<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形
<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

动态插槽名

动态指令参数也可以用在 v-slot 上,来定义动态的插槽名

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

具体插槽的缩写

把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header

该缩写只在其有参数的时候才可用

<!-- 这样会触发一个警告 -->
<current-user #="{ user }">
  {{ user.firstName }}
</current-user>

如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:
<current-user #default="{ user }">
  {{ user.firstName }}
</current-user>

其他示例

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。

要实现一个 <todo-list> 组件,它是一个列表且包含布局和过滤逻辑

<ul>
  <li
    v-for="todo in filteredTodos"
    v-bind:key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

我们可以将每个 todo 作为父级组件的插槽,以此通过父级组件对其进行控制,然后将 todo 作为一个插槽 prop 进行绑定

<ul>
  <li
    v-for="todo in filteredTodos"
    v-bind:key="todo.id"
  >
    <!--
    我们为每个 todo 准备了一个插槽,
    将 `todo` 对象作为一个插槽的 prop 传入。
    -->
    <slot name="todo" v-bind:todo="todo">
      <!-- 后备内容 -->
      {{ todo.text }}
    </slot>
  </li>
</ul>

现在当我们使用 <todo-list>组件的时候,我们可以选择为 todo 定义一个不一样的 <template> 作为替代方案,并且可以从子组件获取数据。

<todo-list v-bind:todos="todos">
  <template v-slot:todo="{ todo }">
    <span v-if="todo.isComplete"></span>
    {{ todo.text }}
  </template>
</todo-list>

废弃了的语法

在接下来所有的 2.x 版本中 slot 和 slot-scope attribute 仍会被支持,但已经被官方废弃且不会出现在 Vue 3 中。

带有 slot attribute 的具名插槽

<template> 上使用特殊的 slot attribute,可以将内容从父级传给具名插槽 (把这里提到过的 <base-layout> 组件作为示例)

<base-layout>
  <template 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 slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>

或者直接把 slot attribute 用在一个普通元素上:

<base-layout>
  <h1 slot="header">Here might be a page title</h1>

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

  <p slot="footer">Here's some contact info</p>
</base-layout>

未命名插槽,也就是默认插槽,捕获所有未被匹配的内容。

类似于具名插槽

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

<template> 上使用特殊的 slot-scope attribute,可以接收传递给插槽的 prop。

<slot-example>
  <template slot="default" slot-scope="slotProps">
    {{ slotProps.msg }}
  </template>
</slot-example>
这里的 slot="default" 可以被忽略为隐性写法:
<slot-example>
  <template slot-scope="slotProps">
    {{ slotProps.msg }}
  </template>
</slot-example>

类似于作用域插槽

这里的 slot-scope 声明了被接收的 prop 对象会作为 slotProps 变量存在于<template> 作用域中。你可以像命名 JavaScript 函数参数一样随意命名 slotProps。

slot-scope attribute 也可以直接用于非 <template> 元素 (包括组件)。

slot-scope 的值可以接收任何有效的可以出现在函数定义的参数位置上的 JavaScript 表达式。这意味着在支持的环境下 (单文件组件或现代浏览器),你也可以在表达式中使用 ES2015 解构

<slot-example>
  <span slot-scope="{ msg }">
    {{ msg }}
  </span>
</slot-example>

<todo-list>等价的使用 slot-scope 的代码是

//废弃语法
<todo-list v-bind:todos="todos">
  <template slot="todo" slot-scope="{ todo }">
    <span v-if="todo.isComplete"></span>
    {{ todo.text }}
  </template>
</todo-list>

//新语法
对比于:
<todo-list v-bind:todos="todos">
  <template v-slot:todo="{ todo }">
    <span v-if="todo.isComplete"></span>
    {{ todo.text }}
  </template>
</todo-list>