1. 场景
- 在开发中,经常需要向一个组件传递内容:
<!-- 渲染会出错 -->
<alert-box>Something bad happened.</alert-box>
- 插槽就是Vue实现的一套内容分发的API,将元素作为承载分发内容的出口;
2. 插槽内容
- 插槽内可以包含任何模板代码,包括 HTML和其他组件;
<!-- navigation-link组件定义 -->
<a v-bind:href="url" class="nav-link">
<slot></slot>
</a>
<!-- 1.slot中解析文本,navigation-link中的slot将会被替换成'Your Profile' -->
<navigation-link url="/profile">
Your Profile
</navigation-link>
<!-- 2.slot中放入html标签 -->
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
<!-- 3.slot中放入其他组件 -->
<navigation-link url="/profile">
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
- 如果
<navigation-link>
的template
中没有包含一个<slot>
元素,则navigation-link
标签中的任何内容都会被抛弃;
3. 编译作用域
(1) 场景
在插槽中使用数据时
(2) 作用范围
- 父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的;
- 举例
<!-- my-component.vue中 -->
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
- 该插槽跟模板的其它地方一样可以访问相同的实例属性(即
navigation-link
标签对中只能访问my-component
的属性),而不能访问<navigation-link>
的作用域;
<navigation-link url="/profile">
Clicking here will send you to: {{ url }}
<!--
这里的 `url` 会是 undefined,因为其 (指该插槽的) 内容是_传递给_
<navigation-link> 的而不在 <navigation-link> 组件*内部*定义的。-->
</navigation-link>
4. 后备内容
- 在没有提供slot内容的时候被渲染
<!-- submit-button组件定义 -->
<button type="submit">
<!-- 当使用sumit-button组件并且不提供任何插槽内容时候,默认使用Submit -->
<slot>Submit</slot>
</button>
<!-- 按钮默认显示Submit -->
<submit-button></submit-button>
<!-- 后备内容“Submit”将会被渲染 -->
<button type="submit">Submit</button>
5. 具名插槽
(1) 场景
- 需要用多个插槽时
<div class="container">
<header>
<!-- 希望把页头放这里 -->
</header>
<main>
<!-- 希望把主要内容放这里 -->
</main>
<footer>
<!-- 希望把页脚放这里 -->
</footer>
</div>
(2) 使用
<slot>
有一个特殊的属性:name
,用来定义额外的插槽:- 一个不带 ·name· 的
<slot>
出口会带有隐含的名字default
; - 向具名插槽提供内容时,可以在
<template>
上使用v-slot
指令,并以v-slot
的参数的形式提供slot的名称; - 任何没有被包裹在带有
v-slot
的<template>
中的内容都被视为默认插槽(default
)的内容; - 【注意!!!】
v-slot
只能添加在<template>
上 (只有一种例外情况,即独占默认插槽),这一点和已经废弃的 slot attribute 不同;
<!-- 1.组件定义 -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<!-- 带有隐含的名字: default -->
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!-- 2.使用 -->
<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>
<!-- 3.渲染结果 -->
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
6.作用域插槽
(1) 场景
让插槽内容能够访问子组件中的数据;举例:
<!-- current-user组件定义 -->
<span>
<slot>{{ user.lastName }}</slot>
</span>
<!-- 希望在使用时访问current-user组件内部,显示user对象的firstName -->
<current-user>{{ user.firstName }}</current-user>
- 上述代码不会正常工作,因为只有
<current-user>
组件可以访问到 user 而提供的内容是在父级渲染的;
(2) 定义
- 绑定在
<slot>
元素上的属性被称为插槽prop,可以实现在使用插槽时访问子组件内部的数据;
<!-- current-user组件 -->
<span>
<!-- 将user作为<slot>元素的属性绑定 -->
<slot v-bind:user="user">{{ user.lastName }}</slot>
</span>
(3) 使用
- 在父级作用域中,可以使用带值的
v-slot
来定义提供的插槽prop名字:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
(4) 独占默认插槽的缩写语法
- 当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用,这样可以把
v-slot
直接用在组件上,不带参数的v-slot
被假定对应默认插槽;
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
<!-- 或:不带参数的`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>
的语法:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
7. 解构插槽prop
- 作用域插槽的内部工作原理是将插槽内容包裹在一个拥有单个参数的函数里:
function (slotProps) {
// 插槽内容
}
v-slot
的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),也可以使用 ES2015 解构来传入具体的插槽 prop:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
- 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>
8.动态插槽名
- 动态指令参数也可以用在
v-slot
上,来定义动态的插槽名:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
9.具名插槽的缩写
v-slot
的缩写即把参数之前的所有内容 (v-slot:
) 替换为字符#
。例如v-slot:header
可以被重写为#header
<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>
-该缩写只在其有参数的时候才可用,后面不能跟等号,如果你希望使用缩写的话,必须始终以明确插槽名取而代之:
<!-- 无效写法,会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
<!-- 需要使用明确的插槽名 -->
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
10.完整实例
实现一个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-list v-bind:todos="todos">
<!-- 访问todo-list内部的todo -->
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>