Vue插槽的認識和基本使用


寫在前面:
加入我們的需求是設置一個通用性的按鈕,按鈕的內容是使用的時候來決定,我們可能會通過一個屬性來設置按鈕的文本,示例代碼如下:

<div id="app">
        <my-com value="註冊"></my-com>
        <my-com value="提交"></my-com>
        <my-com value="重置"></my-com>
    </div>

    <template id="mycom">
        <button>{{value}}</button>
    </template>
    <script>
        let MyCom = ({
            props:["value"],
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

此時我們在調用子組件的時候,設置不同的 value 值就會有不同的按鈕.
在頁面中的顯示結果如下圖所示:
在這裏插入圖片描述
上面的方法雖然也可以滿足我們的需求,但其實通過我們學習html ,我們其實更習慣下面的這種寫法:

    <my-com>註冊</my-com>
    <my-com>提交</my-com>
    <my-com>重置</my-com>

想要這麼寫,我們就必須使用到 slot 插槽.在此之前,我們先來看一下編譯作用域

1.編譯作用域

通過前面的學習,其實我們已經瞭解到父子組件都有自己不同的模板和獨立的作用域,每一個組件在實例化的時候作用域都是獨立的.

那麼我們動一動我們可愛的小腦瓜,就會想到假如我們在父組件中使用子組件時,子組件中嵌套的內容會在哪個作用域裏編譯呢??

那我們就來看看下面的例子:

 <div id="app">
        <my-com>
            {{msg}}
        </my-com>
    </div>

    <template id="mycom">
        <button>{{msg}}</button>
    </template>
    <script>
        let MyCom = ({
            data(){
                return{
                    msg:"hello world"
                }
            },
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                msg:"你好,世界"
            },
            components:{
                MyCom,
            }
        })
    </script>

此時組件中嵌套的 msg 究竟是在父組件中的作用域中編譯還是在子組件作用域編譯呢?這涉及到 msg 使用的是父組件的數據還是子組件的數據.

答案當然就是父組件中的數據了,但是現在我們還沒有辦法演示這段代碼的結果,等後面我們介紹完插槽,再來看看這裏的結果會是怎樣.

組件作用域簡單的來說就是:

  1. 父組件模板的內容在父組件中編譯
  2. 子組件模板中的內容在子組件中編譯

所以就會有一個比較常見的錯誤,父組件模板內使用內將一個指令綁定到子組件內部的屬性/方法
示例代碼如下:

   <div id="app">
        <my-com v-show = "isShow"></my-com>
    </div>

    <template id="mycom">
        <button>按鈕</button>
    </template>
    <script>
        let MyCom = ({
            data(){
                return{
                   isShow:true,
                }
            },
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

報錯信息如下圖:
在這裏插入圖片描述
這樣做會報錯的原因是: isShow 是我們定義在子組件中的數據,父組件中並不能直接找到子組件的數據.

如果需要綁定到子組件作用域內的數據,就可以將指令綁定在子組件的根節點上.
如下:

<div id="app">
        <my-com></my-com>
    </div>
    <template id="mycom">
        <button  v-show = "isShow">按鈕</button>
    </template>
    <script>
        let MyCom = ({
            data(){
                return{
                   isShow:false,
                }
            },
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

這樣做就不會報錯.

自己可以慢慢體會一下這兩種寫法,通過上面的例子,我們就可以對 編譯作用域 有一點點的瞭解.

那麼想想我們開篇提出的問題,在父組件模板中使用子組件時,子組件中嵌套的內容是屬於父組件的編譯,但是我們希望這個內容能在子組件中使用,此時我們就需要一種方式來混合父組件的內容與子組件自己的模板,這個過程被稱爲內容分發.

2.插槽的基本使用

原來我們在組件裏面是不能寫內容的,因爲寫了之後也會被模板整體替換,現在有了插槽之後,我們就可以在組件裏定義內容了.

2.1 插入基本的文本內容

還是上面的例子,我們子組件中嵌套的文本在子組件模板中使用,我們就需要在子組件模板中使用插槽

 <div id="app">
        <my-com>按鈕</my-com>
        <my-com>{{value}}</my-com>
    </div>
    <template id="mycom">
        <button >
            <slot></slot>
        </button>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                value:"註冊"
            },
            components:{
                MyCom,
            }
        })
    </script>

使用插槽之後的結果:
在這裏插入圖片描述
通過這種方法,我們就可以很方便的在父組件中,通過slot插槽向子組件中分發內容
那麼我們想想,既然可以插入文本內容,那是不是也可以在裏面插入 DOM 標籤呢?

2.2 插入DOM標籤

示例代碼如下:

 <div id="app">
        <my-com>
            <h3>你好啊!我是依古比古</h3>
        </my-com>
        <my-com>
            <h2>你好,我是瑪卡巴卡</h2>
        </my-com>
    </div>
    <template id="mycom">
       <div>
           <slot></slot>
       </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

結果如下圖:
在這裏插入圖片描述
通過上面的這段代碼,我們發現插槽不僅僅可以插入文本內容,還可以插入 DOM 標籤.
插槽的更多作用我們往下面繼續看.

3.插槽的默認內容

我們並不是每一次使用子組件的時候都會給裏面插入內容,如果我們不給裏面插入內容,那麼裏面將會是什麼也沒有,所以我們需要設置默認內容.

如果我們需要在使用子組件時未插入內容時. 顯示一段默認的內容,我們就可以將默認的內容嵌套在slot 標籤中

這就是插槽的備用內容, 備用內容在子組件的作用域內編譯. 並且只有在宿主元素爲空,且沒有要插入的內容時才顯示備用內容。

還是使用按鈕的那個例子,看下面的代碼:

 <div id="app">
        <my-com></my-com>
        <my-com>
            {{value}}
        </my-com>
    </div>
    <template id="mycom">
        <button>
            <slot>我是默認內容</slot>
        </button>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                value:"註冊"
            },
            components:{
                MyCom,
            }
        })
    </script>

顯示的結果如下圖:
在這裏插入圖片描述
這樣做就算子組件沒有給我們分發內容,我們也可以使用默認內容,不至於讓顯示的內容爲空.

4.具名插槽

4.1 不使用具名插槽的問題

在我們使用組件的時候,有時候需要在不同的位置插入不同的內容,只有一個插槽很可能不能滿足我們的需求,看示例:

我們的需求是:

  1. 子組件是一篇文章
  2. 父組件在調用子組件的時候需要給子組件插入標題,正文,時間等信息.

示例代碼如下:

 <div id="app">
        <my-com>
            <h2>標題~~標題</h2>
            <p>希望第二天的我</p>
            <p>仍然對不同充滿寬容</p>
            <p>繼續對未知飽含敬畏</p>
            <span>2020612</span>
        </my-com>

    </div>
    <template id="mycom">
        <div class = "article">
            <div class="title">
                <slot></slot>
            </div>
            <div class="content">
                <slot></slot>
            </div>
            <div class="time">
                <slot></slot>
            </div>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            data:{
                value:"註冊"
            },
            components:{
                MyCom,
            }
        })
    </script>

在這裏插入圖片描述
這個時候我們可以看到,我們要插入的內容被重複插入了三次,此時每一個插槽都插入了所有的內容,不符合我們的預期.

那麼我們要怎樣才能將分發的內容制定到具體的插槽上呢???

這個時候就需要具名插槽

4.2 使用具名插槽

<slot> 元素有一個特殊的 name 屬性, 使用 name 來進一步配置如何分發內容,每個插槽都可以有具體的名字.具名插槽將匹配內容片段中有對應的 slot 元素.

沒有使用 name 屬性的 slot 插槽被稱爲 匿名插槽,也可以稱之爲 默認插槽,我們在子組件中仍然可以有一個額匿名插槽,作爲找不到匹配內容片段的備用插槽,若果沒有默認插槽,這些找不到匹配的內容片段就會被拋棄.

看下面的示例代碼:

   <div id="app">
        <my-com>
            <h2 slot = "title">標題~~標題</h2>
            <p>希望第二天的我</p>
            <p>仍然對不同充滿寬容</p>
            <p>繼續對未知飽含敬畏</p>
            <span slot = "time">2020612</span>
        </my-com>

    </div>
    <template id="mycom">
        <div class = "article">
            <div class="title">
                <slot name = "title"></slot>
            </div>
            <div class="content">
                <slot >這裏是默認內容</slot>
            </div>
            <div class="time">
                <slot name = "time"></slot>
            </div>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

在這裏插入圖片描述
可以看到,完全就是我們想要的效果.分發的內容都正確的插到了插槽上面.

過上面的例子我們就知道了,slot 如果沒有顯示的使用 name 屬性指定插槽的名字,那麼slot默認有個名字default,默認插槽,如果在分發內容時,沒有指定插槽,所有的內容都將默認插到默認插槽上.

5.作用域插槽

5.1 作用域插槽的理解和基本使用

通過前面的學習,我們已經知道,插槽的內容是最後在子組件模板上渲染的,那麼就會在有些時候在分發內容中使用子組件中才會有的數據,這樣的話我們該怎麼辦呢???

這個時候就要用到作用域插槽了.

作用域插槽是一種特殊類型的插槽,用作一個(能被傳遞數據的)可重用模板來代替已經渲染好的元素.

簡而言之,就是利用slot 標籤將子組件的數據傳遞到分發內中上,就像prop傳遞數據給組件一樣

在父級中,具有特殊特性 slot-scope<template> 元素必須存在,表示它是作用域插槽的模板。slot-scope 的值將被用作一個臨時變量名,此變量接收從子組件傳遞過來的 props 對象:

當我們使用作用域插槽後,slot-scope 屬性接受的 props 是一個對象,我們來驗證一下:

   <my-com>
      <template slot-scope="props">
        {{props}}
     </template>
   </my-com>

頁面的顯示結果如下圖:
在這裏插入圖片描述
下面我們就來看看作用域插槽的使用案例:

 <div id="app">
        <my-com>
        <!-- slot-scope 不能直接在組件上使用,只能在DOM元素上使用-->
            <tempalte slot-scope="props">
                {{props.msg}}
                {{props.value}}
            </tempalte>
        </my-com>
    </div>

    <template id="mycom">
        <div>
            <slot :msg = "msg" :value="value"></slot>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
            data(){
                return{
                    msg:"今天天氣真不錯",
                    value:"你的衣服真好看"
                }
            }
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })

在 2.5.0+,slot-scope 能被用在任意元素或組件中而不再侷限於 <template>

也就是說我們還能像下面這樣寫:

    <my-com>
         <div slot-scope = "props">
            <h2>{{props.msg}}</h2>
            <h3>{{props.value}}</h3>
        </div>
    </my-com>

結果如下圖:
在這裏插入圖片描述

5.2 作用域插槽也可以解構使用

既然我們前面已經提到了props 中接收的數據是對象,那麼我們就可以通過解構來使用數據,如下:

<my-com>
   <div slot-scope = "{msg,value}">
      <h2>{{msg}}</h2>
      <h3>{{value}}</h3>
   </div>
</my-com>

頁面中的結果如下:
在這裏插入圖片描述
但是非常遺憾的是,作用域插槽和具名插槽的用法會在不久的將來被廢棄.
那麼之前我們提到的問題難道就沒有辦法解決了嗎?
當然不是的,我們需要繼續往下看.

6.v-slot 命令

在 2.6.0 中,我們爲具名插槽和作用域插槽引入了一個新的統一的語法 (即 v-slot 指令)。它取代了 slot 和 slot-scope 這兩個目前已被廢棄但未被移除且仍在文檔中的 attribute。

6.1 使用v-slot解決具名插槽問題

Vue 實現了一套內容分發的 API ,將 <slot>元素作爲承載分發內容的出口。

在向具名插槽提供內容的時候,我們可以在一個 元素上使用 v-slot 指令,並以 v-slot 的參數的形式提供其名稱.

繼續使用之前文章的案例:

  <div id="app">
        <my-com>
            <h2 v-slot:title>標題~~標題</h2>
            <p>希望第二天的我</p>
            <p>仍然對不同充滿寬容</p>
            <p>繼續對未知飽含敬畏</p>
            <span v-slot:time>2020612</span>
        </my-com>

    </div>
    <template id="mycom">
        <div class = "article">
            <div class="title">
                <slot name = "title"></slot>
            </div>
            <div class="content">
                <slot >這裏是默認內容</slot>
            </div>
            <div class="time">
                <slot name = "time"></slot>
            </div>
        </div>
    </template>
    <script>
        let MyCom = ({
            template:"#mycom",
        })

        let vm = new Vue({
            el:"#app",
            components:{
                MyCom,
            }
        })
    </script>

結果如下:
在這裏插入圖片描述
現在<template>元素中的所有內容都將會被傳入相應的插槽。任何沒有被包裹在帶有 v-slot<template> 中的內容都會被視爲默認插槽的內容。

如果你希望更明確一些,仍然可以在一個 中包裹默認插槽的內容:

 <template v-slot:default>
 </template>

6.2 使用v-slot處理作用域插槽問題

綁定在<slot> 元素上的 attribute 被稱爲插槽 prop。現在在父級作用域中,我們可以使用帶值的v-slot 來定義我們提供的插槽 prop 的名字:

<div id="app">
    <!-- 使用組件 -->
    <my-child >
        <template v-slot:default="props">
            <button>{{ props.text }}</button>
        </template>
    </my-child>
</div>

<!-- 組件模板 -->
<template id="mychild">
    <div>
        <slot :text="text"></slot>
    </div>
</template>

<script>
    //  組件選項對象
    let MyChild = {
        template: `#mychild`,
        data(){
            return {
                text: "提交"
            }
        }
    };

    //  實例中註冊組件
    const vm = new Vue({
        el:"#app",
        components: {
            MyChild
        }
    })

</script>

顯示結果:
center

6.3 作用域插槽的特殊使用

在上述情況下,當被提供的內容只有默認插槽時,組件的標籤纔可以被當作插槽的模板來使用。這樣我們就可以把 v-slot 直接用在組件上:

<div id="app">
     <!-- 使用組件 -->
     <my-child v-slot:default="props">
            <button>{{ props.text }}</button>
     </my-child>
</div>

這種寫法還可以更簡單。就像假定未指明的內容對應默認插槽一樣,不帶參數的 v-slot 被假定對應默認插槽:

<div id="app">
     <!-- 使用組件 -->
     <my-child v-slot="props">
            <button>{{ props.text }}</button>
     </my-child>
</div>

這用這種簡單語法的情況就是在組件中只有一個默認插槽,一但有多個插槽,請使用完整的語法

7.動態插槽

動態參數也可以用在v-slot上,用來定義動態的插槽名.
示例:

<div id="app">
    <!-- 使用組件 -->
    <my-child >
        <template v-slot:[head]>
            <h2>這是一篇介紹vue插槽的文章</h2>
        </template>
        <p>這是文章的第一段</p>
        <p>這是文章的第二段內容</p>
        <p>這是文章的第三段內容</p>
        <template v-slot:[food]>
            <span>202051</span>
        </template>
    </my-child>
</div>
<!-- 組件模板 -->
<template id="mychild">
    <div class="article">
        <div class="title">
            <slot name="title">這裏是標題內容的插槽</slot>
        </div>
        <div class="contont">
            <slot>這裏是默認插槽</slot>
        </div>
        <div class="time">
            <slot name="time">這裏是時間的插槽</slot>
        </div>
    </div>
</template>

<script>

    //  組件選項對象
    let MyChild = {
        template: `#mychild`
    };

    //  實例中註冊組件
    const vm = new Vue({
        el:"#app",
        data:{
            head:"title",
            food:"time"
        },
        components: {
            MyChild
        }
    })
</script>

8.具名插槽的縮寫

v-onv-bind 一樣,v-slot 也有縮寫,即把參數之前的所有內容 (v-slot:) 替換爲字符 #。例如 v-slot:header可以被重寫爲 #header

<div id="app">
    <!-- 使用組件 -->
    <my-child >
        <template #title>
            <h2>這是一篇介紹vue插槽的文章</h2>
        </template>
        <p>這是文章的第一段</p>
        <p>這是文章的第二段內容</p>
        <p>這是文章的第三段內容</p>
        <template #time>
            <span>202051</span>
        </template>    
    </my-child>
</div>

然而,和其它指令一樣,該縮寫只在其有參數的時候纔可用。這意味着以下語法是無效的:

<my-child >
    <!-- 這種寫法無效 -->
    <template #="props">
        <h2>這是一篇介紹vue插槽的文章</h2>
    </template>
</my-child>

如果你希望使用縮寫的話,你必須始終以明確插槽名取而代之:

<my-child >
    <!-- 這種寫法有效,因爲有指令參數 -->
    <template #deatule="props">
        <h2>這是一篇介紹vue插槽的文章</h2>
    </template>
</my-child>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章