介绍
插槽基础
如下所示,为插槽最基础的用法。首先学习了解这些基础用法,才能更好的理解后续的作用域插槽。
插槽基础功能:父组件向子组件传递数据,将父组件的数据插入到子组件中。
当我们想跟使用HTMl标签一样,向向组件传递内容时,并显示传递的内容时,就需要用到插槽。如下所示:
<alert-box>
somethong bad happened
</alert-box>
如果想让somethong bad happened 显示出来,就需要在Vue组件< alert-box >中使用元素,来实现向组件中通过如上方式插入内容。如下所示,为< alert-box >的实现方式。在组件中添加< slot>< /slot >标签。
Vue.component('alert-box',{
template:`
<div class ="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
`
})
如上所示,只需在需要的地方加入 < slot>< /slot>插槽,就可以像使用html标签一样,传入想要显示的数据。
在具名插槽和作用域插槽引入新的统一语法,即v-slot指令,取代了slot和slot-scope
插槽内容
插槽内容可以包含任何模板代码,包括HTML、其它组件,如下例所示:
// <navigation-link>模板内容如下所示:
<a v-bind:href = 'url' class ='nav-link'>
<slot></slot>
</a>
// 插入普通文本,如下所示,<navigation-link>模板中的<slot></slot>
// 会被Your Profile替换
<navagation-link url="/profile">
Your Profile
</navigation-link>
//插入HTML
<navigation-link url="/profile">
<span class ="fa fa-user"></span>
</navigation-link>
// 插入其它组件
<navigation-link url="/profile">
// 添加一个图标组件
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
如果 < navigation-link > 没有包含一个 < slot > 元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
编译作用域
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
即父级模板有自己的作用域,它只能访问自己作用域中的数据;子模版有自己的作用域,子模版只能访问自己作用域中的数据。
如下例子所示:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="index.css"/>
<script src="vue.js"></script>
</head>
<body>
<div>
<h1>--编译作用域--</h1>
// user为父模板中的数据,在插槽中可以访问
<div id="example2">
<navigation-link url="/profile">
{{user.name}}
</navigation-link>
</div>
<script>
Vue.component('navigation-link', {
props: {
url: String
},
template: '\
<a\
v-bind:href="url"\
class="nav-link"\
>\
<slot></slot>\
</a>\
'
})
var example2 = new Vue({
el:'#example2',
data: {
user: {name: 'Vue'}
}
})
</script>
</div>
</body>
</html>
如果在父级访问子级的url就会报错,如把对应html修改为:
<navigation-link url="/profile">
{{ url }}
</navigation-link>
后备内容(即默认值)
可以给插槽提供一个默认值,在没有提供内容时默认显示后备内容,如果提供了插槽内容,则显示插槽内容。
如下所示:
// 如下<submit-button>组件,在组件中设置一个默认值Submit,当
// 没有提供插槽内容时,可以默认显示
<button type="submit">
<slot>Submit</slot>
</button>
// 使用默认值
<submit-button></submit-button> // 默认显示插槽内容Submit
<submit-button>Save</submit-button>// 显示Save
具名插槽
注意:在具名插槽和作用域插槽引入新的统一语法,即v-slot指令,取代了slot和slot-scope
有时需要多个插槽,如下模板:
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
可以使用元素的attribute :name 属性,给插槽命名。如下所示:
// <base-layout> 模板
<div class = "container">
<header>
// 给第一个插槽命名为header
<slot name="headr"></slot>
</header>
<main>
// 不带name的默认插槽,其name为default
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
// 使用方法如下:
<base-layout>
<template v-slot:header>
// 该内容被放入name=header的插槽
<h1>Here might be a page title</h1>
</template>
// 该内容放在name=default的插槽
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
// 该内容被放入name=footer的插槽
<p>Here's some contact info</p>
</template>
</base-layout>
// 也可以明确写出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>
// 最终渲染结果
<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>
如上所示,中的内容都被放入相应的插槽中,任何没有被带有v-slot 的template 中的内容都被视为默认插槽内容。
注意 v-slot 只能添加在 上 (只有一种例外情况),这一点和已经废弃的 slot attribute不同。
作用域插槽
主要功能:使得父组件可以访问子组件的内容,是子组件将自己的数据暴漏给父组件使用。
有时让插槽内容能够访问子组件中才有的数据是有用的。
如下所示为< current-user >模板中的插槽
// user 为<current-user>中的数据
<span>
<slot>{{ user.lastname}}</slot>
</slot>
当想在父组件中替换<current-user>中的数据,使用名字而非姓来显示时:
按照如下方式,是无法调用<current-user>中的数据的。
只有<current-user>组件可以访问user(为子组件中的数据),在父组件中无法访问
<current-user>
{{ user.firstName }}
</current-user>
为了让user能够在父级的插槽内容可用,可以将user作为元素的一个attribute进行绑定:
<span>
<slot v-bind:user="user">
{{ user.lastname }}
</slot>
</span>
绑定在 元素上的 attribute 被称为插槽 prop。
在父作用域中,可以使用v-slot定义提供的插槽prop的名字:
// default 代表默认插槽
// slotProps 代表子组件提供的插槽prop ,slotProps为一个对象,里面包含
// 组件插槽中定义的插槽prop。
<current-user>
<template v-slot:default="slotPops">
{{ slotProps.user.firstName}}
</template>
</current-user>
上述中,选择将包含所有 插槽 prop 的对象命名为 slotProps,也可以使用其它名字。
独占默认插槽的缩写语法
在上述情况下,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用,这样我们就可以把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 }}
<tempalte 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>
解构插槽Prop
作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:
function (slotProps){
// 插槽内容
}
意味着 v-slot 值世界上可以是任何能够作为函数定义中的参数的JavasScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 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是undefinde的情形:
<current-user v-slot="{ user = { firstName:'Guest'}}">
{{ user.firstName }}
</current-user>
动态插槽名
动态指令参数也可以用在 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>
<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>
插槽示例:
插槽 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 定义一个不一样的 作为替代方案,并且可以从子组件获取数据:
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">¥</span>
{{ todo.text }}
</todo-list>
废弃了的语法
v-slot 指令自 Vue 2.6.0 起被引入,提供更好的支持 slot 和 slot-scope attribute 的 API
替代方案.在接下来所有的 2.x 版本中 slot 和 slot-scope attribute 仍会被支持,但已经被官方废弃且不会出现在
Vue 3 中。
带有slot attribute的具名插槽
在< template > 上使用特殊的 slot attribute,可以将内容从父级传给具名插槽:
<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>
// 上述渲染的结果为
<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>
带有 slot-scope attribute 的作用域插槽
在 < template > 上使用特殊的 slot-scope attribute ,可以接受传递给插槽的prop ,如下所示:
<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
这里的slot-scope 生命了被接受的prop 对象会作为 slotProps变量存在于 作用域中。
这里的slot=“default” 可以被忽略为 隐形写法
<slot-example>
<template slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
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>