文章目錄
寫在前面:
加入我們的需求是設置一個通用性的按鈕,按鈕的內容是使用的時候來決定,我們可能會通過一個屬性來設置按鈕的文本,示例代碼如下:
<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
使用的是父組件的數據還是子組件的數據.
答案當然就是父組件中的數據了,但是現在我們還沒有辦法演示這段代碼的結果,等後面我們介紹完插槽,再來看看這裏的結果會是怎樣.
組件作用域簡單的來說就是:
- 父組件模板的內容在父組件中編譯
- 子組件模板中的內容在子組件中編譯
所以就會有一個比較常見的錯誤,父組件模板內使用內將一個指令綁定到子組件內部的屬性/方法
示例代碼如下:
<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 不使用具名插槽的問題
在我們使用組件的時候,有時候需要在不同的位置插入不同的內容,只有一個插槽很可能不能滿足我們的需求,看示例:
我們的需求是:
- 子組件是一篇文章
- 父組件在調用子組件的時候需要給子組件插入標題,正文,時間等信息.
示例代碼如下:
<div id="app">
<my-com>
<h2>標題~~標題</h2>
<p>希望第二天的我</p>
<p>仍然對不同充滿寬容</p>
<p>繼續對未知飽含敬畏</p>
<span>2020年6月12號</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">2020年6月12號</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>2020年6月12號</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>
顯示結果:
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>2020年5月1日</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-on
和 v-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>2020年5月1日</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>