vue slot(插槽)
slot 美[slɑːt] V. 投放; 插入
用法说明
slot是合成组件的一种方式, 简单来说: 子组件中使用slot会被父组件包裹的内容覆盖。主要用法如下:
基本
- 组件
navigation-link
<navigation-link></navigation-link>
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
- 引用组件可以在组件中加入自定义内容合成到组件中
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
支持文本、HTML标签、其他组件
后备内容(默认内容)
- 接上例
<a
v-bind:href="url"
class="nav-link"
>
<slot>默认内容: 如果父级不传入slot内容, 则显示我</slot>
</a>
具名插槽
实际开发中, 组件显然不止一处可供父级灵活传入, 多个
slot
就要起名字区分了
- 有如下需要, 在不同位置设置多个
slot
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
- 使用slot属性
name
实现区分
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
可以有一个slot不起名字, 其会带有隐含的名字
default
- 父级使用时在一个
<template>
元素上使用v-slot
指令, 并以v-slot
的参数的形式提供其名称, 如:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<!-- 未指定v-slot的即为default -->
<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>
<template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot
的 <template>
中的内容都会被视为默认插槽的内容。当然, 我们知道默认插槽其名字就是default, 直接写上也未尝不可:
<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插入数据时在父组件编辑, 访问所有数据都以父组件为准, 即编写的时候这一块儿(slot)还是父组件的内容
作用域插槽
现在, 期望插槽内容可以访问子组件中的内容, 例如, 有<current-user>
组件如下:
<span>
<slot>{{ user.lastName }}</slot>
</span>
父组件希望显示user.firstName
<current-user>
{{ user.firstName }}
</current-user>
但是, 显然这是无法工作的, 根据编译作用域中提到的父组件不可能知道子组件的内容, 按传统的方法可以使用$emit
将子组件内容传回, 但是显然太麻烦了.
为了让 user
在父级的插槽内容中可用,
- 我们可以将
user
作为<slot>
元素的一个 attribute 绑定上去:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
注意: 这里在
<slot></slot>
上加了v-bind:user="user"
- 绑定在
<slot>
元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的v-slot
来定义我们提供的插槽 prop 的名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
重要说明:
- 在这个例子中,我们选择将包含所有插槽 prop 的对象命名为
slotProps
,但你也可以使用任意你喜欢的名字。slotProps
是一个对象, 这意味着你可以在子组件中绑定更多的值slotProps
是一个对象, 使用解构的方式取值当然也可以
独占默认插槽的缩写语法
上文中可知当只有默认slot
时, 可以省略<template v-slot:default></template>
, 此时想使用slot的prop
属性, 就要写<template v-slot:default="slotProps"></template>
, 那么是否可以省略呢, 当然可以, 可以把slot:default="slotProps"
写到组件上即可, 例如:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
甚至可以进一步省略(当然是一直被省略的default
):
<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>
的语法.
动态插槽名
动态指令参数也可以用在 v-slot
上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
具名插槽的缩写
v-slot
的缩写为#
, 例如:
-
v-slot:header
缩写为:#header
-
v-slot:default
缩写为:#default
<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 渲染出不同的内容。**这在设计封装数据逻辑同时允许父级组件自定义部分布局的可复用组件时是最有用的。
例如,我们要实现一个 <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>
在主流前端盖框架中的应用
主流框架中的应用非常普遍, 几乎每一个组件都有slot
, 基本上都有默认slot
查看一些具名的, 例如ant vue打开任意个组件(如图像), 查看代码:
<template>
<a-avatar :size="64">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar size="large">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar>
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar size="small">
<template #icon><UserOutlined /></template>
</a-avatar>
<br />
<a-avatar shape="square" :size="64">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square" size="large">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square">
<template #icon><UserOutlined /></template>
</a-avatar>
<a-avatar shape="square" size="small">
<template #icon><UserOutlined /></template>
</a-avatar>
</template>
<script lang="ts">
import { UserOutlined } from '@ant-design/icons-vue';
import { defineComponent } from 'vue';
export default defineComponent({
components: {
UserOutlined,
},
});
</script>
其中 <template #icon>...</template>
就是.
有了这些了解, 相信在工作中对这些框架有更多的理解, 用起来也会更加游刃有余.