Svelte系列 --- 插槽

1,411 阅读3分钟

之前的组件都是单标签组件,例如<Cpn />, 但是页面通常需要允许HTML元素和组件之间相互组合,由此构建复杂的页面

Svelte中的组件,同样支持让组件作为容器,为其添加子组件,以此组合出更大更强的组件,这种容器被称为 插槽(Slots)。这和Vue中slot的作用是十分类似的。

缺省插槽

我们知道html元素是可以有子节点

<div>
  <p> p 元素是 div 的子元素 </p>
</div>

Svelte中组件也可以作为容器在其中存放内容,但是在定义组件的时候,并不知道需要放置的内容是什么

所以Svelte使用slot标签来进行占位操作

和Vue不同的是,Svelte支持多个相同名字的插槽一起使用,例如在子组件中可以有多个缺省插槽。

但是在传值的时候,只能有一个传入,对应的所有的插槽中的内容都会被更新

<!-- 子组件 -->
<div class="box">
  <!-- 
    这是占位的内容 <slot></slot> 或 <slot /> 皆可
    这种没有指定名字的插槽叫做 缺省插槽或默认插槽
    父组件传递过来的值默认会自动替换到缺省插槽的位置
    如果父组件并没有实现插槽内容,且插槽也没有默认值的时候
    插槽所对应的位置将不会显示任何的内容
  -->
  <slot></slot> 
</div>

<!--------------------------------->

<!-- 父组件 -->
<script>
  import Box from './Box.svelte';
</script>

<Box>
  <!-- 将内容放置于此 -->
</Box>

插槽缺省内容

插槽缺省内容(slot fallback)可以指定<slot>元素当内容为空的缺省情况应该默认显示什么内容

<!-- 子组件 -->
<div class="box">
  <slot>
    <em>default value</em>
  </slot>
</div>

<!-- 父组件 -->
<Box>
  <p>不使用缺省内容</p>
</Box>

<!-- 使用缺省内容 -->
<Box />

命名插槽

虽然我们有了缺省插槽,但是我们有的时候,需要对放置的内容有更多的控制能力。这个时候可以使用命名插槽(具名插槽)

<!-- 子组件 ContactCard.svelte -->
<style>
  .contact-card { width: 300px; border: 1px solid #aaa; padding: 1em; }
</style>

<article class="contact-card">
  <h3>
    <!-- 使用name属性给组件起名字 -->
    <slot name="name">Unknown name</slot>
  </h3>

  <div>
    <slot name="address">Unknown address</slot>
  </div>
</article>

<!--------------------------------------------------------->

<!-- 父组件 -->
<script>
  import ContactCard from './ContactCard.svelte';
</script>

<ContactCard>
  <!-- 使用slot属性指定使用哪个slot -->
  <span slot="name">P. Sherman</span>
  <span slot="address">42 Wallaby Way Sydney</span>
</ContactCard>
<!-- 子组件 -->
<article class="contact-card">
  <h3>
    <slot name="name">Unknown name</slot>
  </h3>

  <div>
    <slot name="address">Unknown address</slot>
  </div>

  <!-- 
    子组件中可以有多个同名slot,在被替换的时候会被替换为同样的内容
    在该案例中,2个h3下的slot的内容都会被替换为P. Sherman
  -->
  <h3>
    <slot name="name">Unknown name</slot>
  </h3>
</article>

<!--------------------------------------------------------->

<!-- 父组件 -->
<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>

检测插槽内容

在某些情况下,你可能希望根据父级组件是否已提供某个插槽的内容来控制组件内的各个部分如何展示

这种情况下,你可以使用专门用于检查插槽的变量 $$slots

$$slots 是一个对象,对象的属性是父组件传入的所有插槽的名称

如果父级组件将插槽置空,$$slots 中不会有该插槽的名称。

<!-- 子组件 -->
<script>
  export let title = ''
</script>

<article>
  <h2>{title}</h2>
  <hr />
  <div><slot name="content" /></div>
	
  <!-- 判断一下,父组件对comments插槽进行传值 -->
  {#if $$slots.comments}
  <div style="margin-top: 30px;">
    评论:
    <slot name="comments" />
  </div>
  {/if}

</article>

<!----------------------------------------------->

<!-- 父组件 -->
<script>
  import Article from './Article.svelte'
</script>

<Article title="测试标题">
  <div slot="content">
    这里是测试内容
  </div>
</Article>

插槽属性

有的时候,子组件需要将状态 回传 给父组件,在父组件中进行逻辑判断,这个时候可以使用插槽属性

子组件 Hoverable.svelte

<script>
  let hovering;

  function enter() {
    hovering = true;
  }

  function leave() {
    hovering = false;
  }
</script>

<div on:mouseenter={enter} on:mouseleave={leave}>
  <!-- hovering属性会被传递给实现这个插槽的元素上 -->
  <slot {hovering}></slot>
</div>

父组件

<script>
   import Hoverable from './Hoverable.svelte';
</script>

<!--
  缺省插槽在组件上 使用let关键字来进行接收
  相对于在Hoverable组件范围内 let hovering = hovering
-->
<Hoverable let:hovering>
  <div class:active={hovering}>
     {#if hovering}
        <p>I am being hovered upon.</p>
     {:else}
        <p>Hover over me!</p>
     {/if}
    </div>
</Hoverable>

<style>
  div {
   padding: 1em;
   margin: 0 0 1em 0;
   background-color: #eee;
  }

  .active {
    background-color: #ff3e00;
    color: white;
  }
</style>

当然,为了便于使用,我们还可以在接收的时候,为子组件传入的状态起别名

<!-- 
    将 hovering起别名为active 
    相对于在Hoverable组件范围内 let active = hovering
-->
<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>
<!-- 子组件 -->
<div on:mouseenter={enter} on:mouseleave={leave}>
   <slot {hovering} name='box'></slot>
</div>

<!-- 父组件 -->
<Hoverable >
  <!-- 
    如果接受的是具名属性的值, 在具有 slot="..." 属性的元素上使用 let 指令,而不是在组件自身上使用。
  -->
  <div slot="box" let:hovering>
    {#if hovering}
       <p>I am being hovered upon.</p>
    {:else}
       <p>Hover over me!</p>
    {/if}
  </div>
</Hoverable>

插槽A上的插槽属性回传给父组件,传递的状态的作用域仅仅只是在插槽A的使用范围内部。

子组件

<div on:mouseenter={enter} on:mouseleave={leave}>
   <slot {hovering} name='box'></slot>
   <slot>default slot value</slot>
</div>

父组件

<Hoverable >
   <div slot="box" let:hovering>
     {#if hovering}
        <p>I am being hovered upon.</p>
     {:else}
        <p>Hover over me!</p>
     {/if}
   </div>

   <!-- 下边的这个代码是会报错的 -->
   <div>
     {#if hovering}
        <p>I am being hovered upon.</p>
     {:else}
        <p>Hover over me!</p>
     {/if}
    </div>
</Hoverable>