HTML 元素允许相互组合,由此构建复杂的页面。
如果不是普通的 HTML 元素,而是 Svelte 组件,同样支持让组件作为容器,为其添加子组件,以此组合出更大更强的组件,这种容器被称为 插槽
(Slots)。
1、插槽
元素可以有子级节点...
<div>
<p> p 元素是 div 的子元素 </p>
</div>
..组件也可以有。但是,组件接收子级节点之前,它需要知道应该将子级节点放置何处。我们使用<slot>
元素来应对此操作。
假设我们有一个名为 Box.svelte
的组件:
Box.svelte
<style>
.box {
width: 300px;
border: 1px solid #aaa;
padding: 20px;
}
</style>
<div class="box">
<!-- 内容应该注入到此处 -->
</div>
我们在使用端引入 Box 组件:
App.svelte
<script>
import Box from './Box.svelte';
</script>
<Box>
<!-- 将内容放置于此 -->
</Box>
然后我们在Box.svelte内部加入插槽:
Box.svelte
<div class="box">
<slot></slot>
</div>
你现在可以放置一些东西在这个 Box 中了:
App.svelte
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
2、插槽缺省内容
组件可以指定<slot>
元素当内容为空的缺省情况应该默认显示什么内容:
Box.svelte
<div class="box">
<slot>
<em>使用端没有传递任何内容时,将会展示此处的内容</em>
</slot>
</div>
现在我们再创建一个没有任何子级节点的<Box>
看看:
App.svelte
<Box>
<h2>Hello!</h2>
<p>This is a box. It can contain anything.</p>
</Box>
<Box /> <!-- 没有提供内容,则展示默认缺省情况的内容 -->
3、命名插槽
上一个例子中,我们包含了一个默认插槽,用于直接代替组件的子节点。不过有些时候,你需要对放置的内容有更多的控制能力,例如使用下方示例中的<ContactCard>
组件,我们可以使用 命名插槽 :
ContactCard.svelte
<style>
.contact-card { width: 300px; border: 1px solid #aaa; padding: 1em; }
</style>
<article class="contact-card">
<h3>
<slot>Unknown name</slot>
</h3>
<div>
<slot>Unknown address</slot>
</div>
</article>
默认的展示效果:
插槽演示
在ContactCard.svelte
中,向每个插槽添加一个name
属性:
<style>
.contact-card { width: 300px; border: 1px solid #aaa; padding: 1em; }
</style>
<article class="contact-card">
<h3>
<slot name="name">Unknown name</slot>
</h3>
<div>
<slot name="address">Unknown address</slot>
</div>
</article>
然后在 App.svelte 中的<ContactCard>
组件内添加相应的slot="..."
属性:
App.svelte
<script>
import ContactCard from './ContactCard.svelte';
</script>
<ContactCard>
<span slot="name">P. Sherman</span>
<span slot="address">42 Wallaby Way Sydney</span>
</ContactCard>
某些情况下,我们可能会在 ContactCard.svelte
中,使用两个相同名称的 <slot>
,当你需要在两个地方都展示一模一样的内容时,它就十分有用.
可以看到邮件列表的顶部和底部,都有相同的操作按钮和分页导航:
这种重复渲染的情况,我们通过使用相同名称的 <slot>
即可:
ContactCard.svelte
<style>
.contact-card { width: 300px; border: 1px solid #aaa; padding: 1em; }
</style>
<article class="contact-card">
<h3>
<slot name="name">Unknown name</slot>
</h3>
<div>
<slot name="address">Unknown address</slot>
</div>
<!-- 下方又添加了一个名称为 'name' 的 slot -->
<h3>
<slot name="name">Unknown name</slot>
</h3>
</article>
展示的结果是:
但是,在引用了 ContactCard 组件的使用端,你不允许提供两个相同名称相同的内容:
App.svelte
<script>
import ContactCard from './ContactCard.svelte';
</script>
<ContactCard>
<span slot="name">P. Sherman</span>
<!-- 不能提供两个 slot 为 'name' 的内容 -->
<span slot="name">P. Sherman</span>
<span slot="address">42 Wallaby Way Sydney</span>
</ContactCard>
4、检测插槽内容
在某些情况下,你可能希望根据父级组件是否已提供某个插槽的内容来控制组件内的各个部分如何展示。
也许这个插槽被包裹在其他元素中,并且如果父级没有提供该插槽的内容,你可能这种情况下,不想渲染组件的某一部分。
或者你只想在插槽不为空的情况下,才应用某个样式类。
这种情况下,你可以使用专门用于检查插槽的变量 $$slots
。
$$slots
是一个对象,对象的属性是父组件传入的所有插槽的名称
。如果父级组件将插槽置空,$$slots
中不会有该插槽的名称。
例如下方的文章组件:
Article.svelte
<script>
export let title = ''
</script>
<article>
<h2>{title}</h2>
<hr />
<div><slot name="content" /></div>
<div style="margin-top: 30px;">
评论:
<slot name="comments" />
</div>
</article>
App.svelte
<script>
import Article from './Article.svelte'
</script>
<Article title="出师表">
<div slot="content">
先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也……
</div>
</Article>
可以看到,上方使用 Article
组件时,没有给出任何评论部分的内容,但是 “评论:”
还是展示了,我们要使用$$slots
来确认仅在父组件<App>
已传入comments
插槽在有内容时才渲染这几个字。
我们编写一个if
块,块的范围包括了comments
评论插槽以及插槽所在的整个<div>
,在if
块中需要检测$$slots
中的comments
插槽是否存在内容:
Article.svelte
<script>
export let title = ''
</script>
<article>
<h2>{title}</h2>
<hr />
<div><slot name="content" /></div>
{#if $$slots.comments}
<div style="margin-top: 30px;">
评论:
<slot name="comments" />
</div>
{/if}
</article>
现在,当<App>
没有提供comments
插槽任何内容时,整个评论部分的 UI 都不会渲染了。
5、插槽属性
假设现在要写一个简单的组件,该组件跟踪鼠标当前是否在其之上,假设命名为<Hoverable>
:
Hoverable.svelte
<script>
let hovering;
function enter() { hovering = true; }
function leave() { hovering = false; }
</script>
<div on:mouseenter={enter} on:mouseleave={leave}>
<slot></slot>
</div>
App.svelte
<script>
import Hoverable from './Hoverable.svelte';
</script>
<style>
div { padding: 1em; margin: 0 0 1em 0; background-color: #eee; }
.active { background-color: #ff3e00; color: white; }
</style>
<Hoverable>
<div class:active>
{#if active}
<p>I am being hovered upon.</p>
{:else}
<p>Hover over me!</p>
{/if}
</div>
</Hoverable>
在 Hoverable
组件中,进入了 div
后会更新 hovering
的值。
Hoverable 需要将结果 回传 给父组件去,以便我们可以更新插槽中的内容。
于是我们用上了 插槽属性,在 Hoverable.svelte
中,将 hovering
的值传给插槽:
<div on:mouseenter={enter} on:mouseleave={leave}>
<slot hovering={hovering}></slot>
</div>
再次安利你一下可以用
{hovering}
简写形式。
然后,我们要接受从<Hoverable>
组件暴露的 hovering
的值,则可使用let
指令:
<Hoverable let:hovering={hovering}>
<div class:active={hovering}>
{#if hovering}
<p>I am being hovered upon.</p>
{:else}
<p>Hover over me!</p>
{/if}
</div>
</Hoverable>
你可根据需要重命名这个变量,我们姑且在父组件中称之为active
吧:
<Hoverable let:hovering={active}>
<div class:active>
{#if active}
<p>I am being hovered upon.</p>
{:else}
<p>Hover over me!</p>
{/if}
</div>
</Hoverable>
你可以任意使用这类组件,并且插槽属性将仍然是在它们声明所在的组件中。
命名插槽也可以有属性;在具有slot="..."
属性的元素上使用let
指令,而不是在组件自身上使用。
总结
Svelte 的插槽,对比 Vue 的插槽而言,近似度很高,不过,目前 Svelte 不支持在组件中直接访问其插槽中的子组件,但功能已经在更新列表中,相信指日可待。