slot插槽

1,718 阅读4分钟

什么是插槽?

通俗地讲插槽就是占位置的,使用组件标签的时候把内容填进去就行,实现了一套内容分发的 API,意味着它有扩展性。

新版本

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

插槽的使用

具名插槽

有时候我们会在一个组件中定义很多插槽,那无法区分哪个是哪个怎么办,就需要为插槽起名字。对于这样的情况,slot元素有一个特殊的 attribute:name

<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>

对于没有名字的插槽,就有默认的名字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中的内容都会被视为默认插槽的内容。如果想要更明确一点,可以写上默认的。

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

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

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

注意 v-slot 只能添加在 template标签上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。

后备(默认)内容插槽

有时候我们需要给插槽设置一个具体的默认内容,当别的组件没有给你内容的时候,那么默认的内容就会被渲染。

//test.vue
//在slot插槽里设置默认内容 Submit
<button>
  <slot>Submit</slot>
</button>

引用

//App.vue
<test></test>

最终会被渲染成为

<button>
   Submit
</button>

内容插槽

  • 普通文本
//test.vue
<span>
	 <slot></slot>
</span>

引用

//APP.vue
<test>
     Hello Word
</test>
  • 引用其他模块
//home.vue
<test>
    <!-- 添加一个 Font Awesome 图标 -->
    <span class="fa fa-user"></span>
    Hello Word
</test>

插槽内添加其他组件

//App.vue
<test>
    <!-- 添加一个图标的组件 -->
    <font-awesome-icon></font-awesome-icon>
    Hello Word
</test>

  • 数据
//home.vue
<test>
    <!-- 添加一个 Font Awesome 图标 -->
    <span class="fa fa-user"></span>
    Hello Word
</test>

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

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

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

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

注意

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

作用域插槽

有时让插槽内容能够访问子组件中才有的数据是很有用的,然而父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的,我们怎么才能访问子组件的数据呢?我们把需要传递的内容绑到 slot 上,然后在父组件中用v-slot设置一个值来定义我们提供插槽的名字:

//test.vue
<span>
//绑定一个属性user
  <slot v-bind:user="user"{{ user.lastName }}</slot>
</span>

引用

//APP.vue
<div>
  <test v-slot:default="slotProps">
  //接收user传过来的值
    {{slotProps.user.lastName}}
  </test>
</div>

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

<test>
  <template v-slot:default="slotProps">
    {{ slotProps.user.lastName}}
  </template>
</test>

动态插槽名(2.6.0 新增)

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

具名插槽的缩写(2.6.0 新增)

  • 新写法:v-on和v-bind有缩写一样,v-slot也有缩写(#)。
<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>

其它示例

插槽 prop 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 prop 渲染出不同的内容。这在设计封装数据逻辑同时允许父级组件自定义部分布局的可复用组件时是最有用的。

例如,我们要实现一个 组件,它是一个列表且包含布局和过滤逻辑:

<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>