Web Components 系列(五)—— 詳解 Slots

程式設計三昧發表於2022-02-12

詳解Slots.001

前言

熟悉 Vue 的同學應該都知道”插槽(slot)“的概念,通過使用插槽可以讓頁面內容的組織更加靈活。

在 Web Components 體系中也有插槽的概念,今天我們就來具體瞭解一下 Slots,本文主要包括以下內容:

  • 為什麼要用 Slots ?
  • Slots 的相關特性

Slots 的作用

我們首先來看一個模板元素:

<template>
    <div class = "header">MY CARD</div>
    <div class="details">
        My name is 程式設計三昧。
    </div>
</template>

既然是模板,那就意味著在很多地方都會使用到它,但是,這裡會存在一個問題:所有使用這個模板的地方都將顯示模板中的內容,即並不是所有人的名字都叫 ”程式設計三昧“。

在這種情況下,叫其他名字的人是沒法使用這個模板的,顯然,這就和使用模板的初衷相違背了,這個模板的使用範圍太過狹小,不存在通用性。

想要使得這個模板具有通用性,其關鍵點在於 .details 中顯示的內容是否具有通用性。

開動腦筋想一想,我們是不是可以將其中的”程式設計三昧“設為動態內容,誰使用這個模板,誰就傳入自己的名字。恰好, Slots(插槽)就可以實現這種效果,具體如下:

<!--在模板中使用 slot 進行佔位-->
<template id="cardTmp">
    <div class="header">MY CARD</div>
    <div class="details">
        My name is <slot name="userName">程式設計三昧</slot></div>
</template>

<!--在使用上面模板的自定義元素中給 slot 傳值-->
<my-card>
    <span slot="userName">插槽傳值</slot>
</my-card>

<my-card>
    <span slot="userName">web Components</slot>
</my-card>

其對應的 JS 程式碼如下:

class MyCard extends HTMLElement {
    constructor () {
        super();
        const template = document.getElementById('cardTmp');
        const templateContent = template.content;

        this.attachShadow({mode: 'open'}).appendChild(
            templateContent.cloneNode(true)
        );
    }
}
customElements.define('my-card', MyCard);

實現效果:

image-20220211221138468

通過上面的例子,我們可以用一句話總結 Slots 的作用:Slots 的作用就是給模板元素傳值,增強模板元素的靈活性和通用性。

Slots 的相關特性

對於 Slots 的相關特性,我通過問答的形式逐一解釋。

Slots 的 name 屬性有什麼作用?

帶有指定 name 的 Slots 被稱為 ”具名插槽“,name 是 slot 的唯一標識。

在引入插槽內容的元素上需要使用與 Slots.name 值相同的 slot 屬性。看下面的程式碼:

<template id="cardTmp">
    <div class="header">MY CARD</div>
    <div class="details">
        My name is <slot name="userAge">19</slot></div>
</template>
<my-card>
    <span slot="userName">程式設計三昧</slot>
</my-card>

<my-card>
    <span slot="userName">web Components</slot>
</my-card>

<script>
    class MyCard extends HTMLElement {
        constructor () {
            super();
            const template = document.getElementById('cardTmp');
            const templateContent = template.content;

            this.attachShadow({mode: 'open'}).appendChild(
                templateContent.cloneNode(true)
            );
        }
    }
    customElements.define('my-card', MyCard);
</script>

執行效果:

image-20220211231116878

因為傳入的 slot 屬性值和 Slots 的 name 屬性值對不上,所以 Slots 未被插入。

傳值時的 slot 屬性值必須和 Slots 的 name 屬性值保持一致。

不給 Slots 傳值會怎樣?

將上面兩個自定義元素 my-card 中的 span 元素去掉,不傳任何值,即改成這樣:

<my-card></my-card>

執行後的效果:

image-20220211222440918

可以看到,如果不給 Slots 傳值,那麼 Slots 會顯示它自己預設的內容

其實結合以上兩點,還可以得出一個結論:如果有引用 Slots ,那只有對應 name 的 Slots 內容會被顯示,其餘的 Slots 皆不顯示

正常 DOM 中可以使用 Slots 嗎?

這裡的”正常 DOM“ 是相對於 Shadow DOM 來說的,指的是頁面所在的文件物件。

程式碼如下:

<slot name="userName">Slots 預設值</slot>
<div slot="userName">bcsm</div>

顯示如下:

image-20220211223746968

總結:正常 DOM 中使用 Slots,它會直接渲染在頁面上,切不具備插槽效果

Slots 是不是必須用在 Templates 中?

我們前面看到的例子中,Slots 是在 Templates 中,那是不是意味著 Slots 必須要用在 Templates 中才能生效呢?

因為已經驗證過在正常 DOM 中的 Slots 是無效的,所以我們在 Shadow DOM 中做個測試,程式碼如下:

<body>
    <h1>不在 Templates 中使用 Slots</h1>
    <div id="templ">
        <slot name="userName">這是 Slots 預設值</slot>
    </div>
    <my-paragraph>
        <span slot="userName">程式設計三昧</span>
    </my-paragraph>
    <script>
        class MyParagraph extends HTMLElement {
            constructor () {
                super();
                const template = document.getElementById('templ');

                this.attachShadow({mode: 'open'}).appendChild(
                    template.cloneNode(true)
                );
            }
        }
        customElements.define('my-paragraph', MyParagraph);
    </script>
</body>

顯示效果如下:

image-20220211225448919

從顯示效果上可以看到,將包含 Slots 的正常 DOM 節點在追加到 Shadow DOM 後,Slots 顯示傳入的值,也就是說 Slots 是生效了的。

總結:Slots 在 Shadow DOM 中就可生效,並非一定要用在 Templates 中

一個自定義元素中可以使用多個同名 Slots 嗎?

看程式碼:

<template id="cardTmp">
    <div class="header">MY CARD</div>
    <div class="details">
        My name is <slot name="userName">程式設計三昧</slot></div>
</template>
<my-card>
    <span slot="userName">插槽傳值1</span>
    <span slot="userName">插槽傳值2</span>
</my-card>

<script>
    class MyCard extends HTMLElement {
        constructor () {
            super();
            const template = document.getElementById('cardTmp');
            const templateContent = template.content;

            this.attachShadow({mode: 'open'}).appendChild(
                templateContent.cloneNode(true)
            );
        }
    }
    customElements.define('my-card', MyCard);
</script>

顯示效果:

image-20220211232249789

結論:一個 Slots 可以接收多個傳入值,且都會解析顯示出來

Slots 的傳值元素必須是自定義元素的直接子元素嗎?

上面的例子中,所有給 Slots 傳值的元素都是自定義元素的子元素,那是不是非直接子元素不行呢?

程式碼如下:

<template id="cardTmp">
    <div class="header">MY CARD</div>
    <div class="details">
        My name is <slot name="userName">程式設計三昧</slot></div>
</template>
<my-card>
    <div>
        <span slot="userName">插槽傳值1</span>
    </div>
</my-card>

<script>
    class MyCard extends HTMLElement {
        constructor () {
            super();
            const template = document.getElementById('cardTmp');
            const templateContent = template.content;

            this.attachShadow({mode: 'open'}).appendChild(
                templateContent.cloneNode(true)
            );
        }
    }
    customElements.define('my-card', MyCard);
</script>

執行效果(傳值失效):

image-20220211233044205

結論:給 Slots 傳值的元素必須是自定義元素的直接子元素,否則傳值失效

結束語

以上就是我對 Slots 相關知識的一個總結,目前能想到的就這些,肯定不全面,希望大家能夠指正補充!

~

~ 本文完,感謝閱讀!

~

學習有趣的知識,結識有趣的朋友,塑造有趣的靈魂!

大家好,我是〖程式設計三昧〗的作者 隱逸王,我的公眾號是『程式設計三昧』,歡迎關注,希望大家多多指教!

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章