話說,真的很傻眼餒 ~~~ ,為什麼報名錯的項目不能取消阿,害我多一個空項目在那邊。今天講的是命名插槽啦,比昨天還要簡單的多,因為 BootstrapVue 還滿常看到這樣的組件的,所以稍微說說具名插槽,這樣在使用一些別人設計好的組件的時候才比較有概念。
參考資料:
補充一下局部組件也可以這樣註冊,上一章有類似這個部分但並沒有特別提出。
Vue.component('transfer-attrs-listeners', {
props: ['talfin'],
components: {
'receieve-aka-allen': {
props: ['akaAllen'],
template:
`<div style= "border: solid 1px red;">
akaAllen: {{ akaAllen }} <br><br>
$attrs : {{ $attrs }} <br><br>
<input
v-on="$listeners"
/>
</div>`,
created () {
console.log('this.$listeners: ', this.$listeners)
}
}
},
template: `
<div style= "border: solid 2px green;">
<label> {{ talfin }} </label>
<receieve-aka-allen
v-bind="$attrs"
v-on="$listeners"
>
</receieve-aka-allen>
</div>
`
})
'receieve-aka-allen' 寫在 components 裡面的方式,個人比較不喜歡這樣,當局部組件比較多的時候,寫在外面的方式我覺得比較好閱讀。
今天的正題,一般在使用具名插槽的時候,就是直接給他一個 name,然後我們再父級作用域用 <template v-slot:取的name> 把他接出來,這次用局部組件。
let liftYouUp = {
template: `
<div>
<h1><slot name="almighty"></slot></h1>
</div>
`
}
記得在Vue實例內
componenets: {
// 做局部組件的註冊
liftYouUp
}
<lift-you-up>
<template v-slot:almighty>
God
</template>
</lift-you-up>
具名插槽就這麼簡單,知道原理後我們來 si si BootstrapVue 的 Jumbotron,這個可以拿來當作 Hero image,下面是他的 HTML 及效果,等等我們依照這樣的模式仿造一個隨意的版本。
<div>
<b-jumbotron>
<template v-slot:header>BootstrapVue</template>
<template v-slot:lead>
This is a simple hero unit, a simple jumbotron-style component for calling extra attention to
featured content or information.
</template>
<hr class="my-4">
<p>
It uses utility classes for typography and spacing to space content out within the larger
container.
</p>
<b-button variant="primary" href="#">Do Something</b-button>
<b-button variant="success" href="#">Do Something Else</b-button>
</b-jumbotron>
</div>
slot 小分析:
參照 BV 的概念,如下可以模仿做出我們自己想要的內容,預留一個空白插槽不帶有 name 屬性, 此插槽會被視為默認插槽,在父層沒有指定 name 的內容, 都會被視為默認插槽的內容 :
let mJumbotron = {
template: `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1><slot name="header"></slot></h1>
<p><slot name="lead"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`
}
// 記得要註冊
...
components: { liftYouUp, mJumbotron }
<m-jumbotron>
<template v-slot:header>
農村傳奇 - 異食記
</template>
<template v-slot:lead>
村霸的食品獻祭之路
</template>
<hr style="opacity: 0.7;">
<p>在這裡可以看見村霸在兄弟們家,劈草奪食的紀錄。</p>
<p>唯一的自我拯救之法: 成為都市人</p>
<m-button variant="primary">過往逃生路線</m-button>
<m-button variant="danger">查看受害者名單</m-button>
</m-jumbotron>
就是類似這樣, 本章範例在此。
簡單一句話,父層用外面,子層用裡面,外面可共用,裡面各自用。
將異食記 template 前面加上 {{ who }},這時會出錯並且告知 who is not defined 什麼的,因為 template 標籤內所包含的內容通通等於父層,所以這邊的 {{ who }} 要發揮作用必須在 new Vue 實例 data 加上 who 的定義才行,也就是父層用外面,而且我們最常使用、可共用的 data 。
<template v-slot:header>
農村傳奇 - {{ who }}異食記
</template>
new Vue({
el: '#app',
data: {
who: '華農'
},
components: { liftYouUp, mJumbotron }
})
那如果我們要用子層,也就是裡面的資料呢 ? 那就在子層定義他自己的 data。
let mJumbotron = {
template: `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1><slot name="header"></slot></h1>
<p><slot name="lead"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`,
data () {
who: '小哥'
}
}
現在還是長這樣,若是我們想讓文字變成"農村傳奇 - 小哥異食記",該怎麼辦呢 ? 目前有兩個辦法。
因為父層用外面,子層用裡面,所以第一個辦法就是為我們 slot 添加 預設內容。而這預設內容的部分通常很容易的就是使用自己子層的 data ,像這樣 :
let mJumbotron = {
template: `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1>
<slot name="header">
農村傳奇 - {{ who }}異食記
</slot>
</h1>
<p><slot name="lead"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`,
data () {
return {
who: '小哥'
}
}
}
這時因為父層有提供內容所以我們預設的內容會被覆蓋過去,所以把我們在父層的這段引用整段移除,如此就會自動採用預設的內容。
<template v-slot:header>
農村傳奇 - {{ who }}異食記
</template>
接下來畫面就變成,咦 ?
貼錯圖了應該是這樣ㄏㄏ :
以上是第一個方法,透過 slot 預設內容存取自己子層所在的作用域 data。
接下來說說第二個辦法,就是使用 插槽 prop 的方法,把子層的 data 洩漏給父層,怎麼使用呢 ? 就是在 slot 上綁定一個名字並接子層的資料給他,如此父層可以透過綁定的名字接出子層的資料,首先將我們的子層 data 改成如下形式 :
data () {
return {
brothers: {
boss: '華農',
rookie: '小哥'
}
}
}
子層的 template 改成這樣 :
template: `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1>
<slot name="header" v-bind:country="brothers">
農村傳奇 - {{ who }}異食記
</slot>
</h1>
<p><slot name="lead"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`,
目前全部是這樣 :
let mJumbotron = {
template: `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1>
<slot name="header" v-bind:country="brothers">
農村傳奇 - {{ brothers.boss }}異食記
</slot>
</h1>
<p><slot name="lead"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`,
data () {
return {
brothers: {
boss: '華農',
rookie: '小哥'
}
}
}
}
注意子層模板的綁定方式是 :
<slot name="header" v-bind:綁訂一個名字="並接資料給他">
農村傳奇 - {{ brothers.boss }}異食記
</slot>
所以就變成
<slot name="header" v-bind:country="brothers">
農村傳奇 - {{ brothers.boss }}異食記
</slot>
在對應一下我們的 data
brothers: {
boss: '華農',
rookie: '小哥'
}
以這個情境來說,就是我們的鄉下(country)已經被兄弟(brothers)們佔據了...
子層的預設內容我用 "農村傳奇 - {{ brothers.boss }}異食記" ,所以現在從小哥變回
改成這樣的形式就有比較多應用方法,我們前面說到 "在 slot 上綁定一個名字並接子層的資料給他,如此父層可以透過綁定的名字接出子層的資料",所以接下來透過父層接出資料的辦法,來改變我們的預設內容,把父層的 template 寫回來並且參考如下接出方式:
<template v-slot:插槽名字="為要接出來的資料取個名字"></template>
像這樣
<template v-slot:header="destroyer">
{{ destroyer }}
</template>
畫面變這樣,可以看見我們子層的資料透過這樣的方式拿出來了,所以若是現在要讓小哥掌權,最簡單的就是直接存取這個物件像這樣 :
<template v-slot:header="destroyer">
農村傳奇 - {{ destroyer.country.rookie }}奪權
</template>
可以透過物件解構的方式 :
<template v-slot:header="{country}">
農村傳奇 - {{ country.rookie }}奪權
</template>
巢狀解構改名 :
<template v-slot:header="{country:{rookie:king}}">
農村傳奇 - {{ king }}奪權
</template>
子層 data 成員是方法也可以
data () {
return {
brothers: {
boss: '華農',
rookie: '小哥',
omg: function (who) {
console.log('遭到外來統治者' + who + '襲擊')
}
}
}
}
<template v-slot:header="{country:{omg:attackBy}}">
{{ attackBy('成亟思和') }}
</template>
下一章要講的是動態組件,下下一章正式進入 CLI 。不過在這之前,看見 BV Table 的這段 template 用法覺得很有趣,我覺得很適合當作進入 CLI 前可以先拿來熟悉一下 .vue 檔的模式。大家先看下面這段標準的 .vue 檔內容 :
<template>
<div>
<b-table small :fields="fields" :items="items" responsive="sm">
<!-- A virtual column -->
<template v-slot:cell(index)="data">
{{ data.index + 1 }}
</template>
<!-- A custom formatted column -->
<template v-slot:cell(name)="data">
<b class="text-info">{{ data.value.last.toUpperCase() }}</b>, <b>{{ data.value.first }}</b>
</template>
<!-- A virtual composite column -->
<template v-slot:cell(nameage)="data">
{{ data.item.name.first }} is {{ data.item.age }} years old
</template>
<!-- Optional default data cell scoped slot -->
<template v-slot:cell()="data">
<i>{{ data.value }}</i>
</template>
</b-table>
</div>
</template>
<script>
export default {
data() {
return {
fields: [
// A virtual column that doesn't exist in items
'index',
// A column that needs custom formatting
{ key: 'name', label: 'Full Name' },
// A regular column
'age',
// A regular column
'sex',
// A virtual column made up from two fields
{ key: 'nameage', label: 'First name and age' }
],
items: [
{ name: { first: 'John', last: 'Doe' }, sex: 'Male', age: 42 },
{ name: { first: 'Jane', last: 'Doe' }, sex: 'Female', age: 36 },
{ name: { first: 'Rubin', last: 'Kincade' }, sex: 'Male', age: 73 },
{ name: { first: 'Shirley', last: 'Partridge' }, sex: 'Female', age: 62 }
]
}
}
}
</script>
以上是標準 .vue 檔格式,通常由 template 標籤 script 標籤 style 標籤,三個標籤組合成為一個 .vue檔。
我們先前使用的組件是全域註冊、局部註冊這兩種,而且都是寫在同一個檔案,這裡比較特殊的是我們所寫的組件不管是全域、局部,通通給你抽出來變成 .vue 檔,變成一種外部的組件檔,等需要用的時候使用 import 的方式引用進來。
而開發這種 .vue 檔,90%跟我們原本寫組件的方式一樣,差別在於我們 template: '...' 的內容通通搬進 template 標籤內,而剩下的設定就通通留在 export default 內,我們只要開發好我們要用的組件( .vue檔 ),隨後在需要的地方進行 import 使用,CLI 有包裝好的 Loader 會自動幫忙編譯 .vue 檔的內容並好好地顯示在頁面上,妥妥的。
接下來回到 BV 的 Table,你應該有注意到一些特殊的插槽像是 :
咦 ? 我們有教過這種用法嗎 ? 怎麼 cell 後面還多了個帶引數的括號 ???
這邊千萬不要像我這樣被騙了老半天阿,他只是設計好讓插槽名字剛好長那樣,那整串就是名字啊,傻眼 ~~,而且要注意一下,這個是 table 組件,專門拿來顯示資料用的,這個設計方法必須要有額外傳進來的資料,他的額外資料在這:
我們可以依照他的觀念稍微改寫我們的 Jumbotron 額外傳進一些資料,假設我們是一本書 :
<m-jumbotron :book-name="countryDominator">
所以我們現在要定義 countryDominator在 Vue 實例內
in Vue data: {} ...
countryDominator: {
dominator: {
key: 'dominator',
name: '小哥'
}
}
接著學 BV 的模式重新設計我們的 header 插槽
template = `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1>
<slot :name="[getDominator(bookName.dominator.key)]" v-bind="bookName">
農村傳奇 - {{ brothers.boss }}異食記
</slot>
</h1>
<p><slot name="lead"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`
:name="[getDominator(bookName.dominator.key)]"
這是 動態參數 的用法,記得要用 :(bind) 符號,否則引號內通通會變成字串。
動態參數的規則是規則是 :xxx=["apple"] 會等於 :xxx="apple", 方括號內我用方法替代,最後會 return 我需要的參數字串。
組件子層的地方就要給他一個方法來回傳我們要的內容
methods: {
getDominator (anyway) {
return `cell(${anyway})`
}
}
透過方法回傳 cell(${anyway})
,:name="[getDominator(bookName.dominator.key)]"
就變成
:name="[
cell(dominator)]"
,照規則就變成 name=cell(dominator)
,所以在父層就可以
用這樣的方法接出來,再學 BV = "data"
<template v-slot:cell(dominator)="data">
農村傳奇 - {{data.dominator.name}}奪權
</template>
透過這樣的方法,現在可依樣畫葫蘆,改寫一下我們 lead 插槽。
注意我原本 v-bind:country 的地方不要了,各位把他加回去交互測試,父層的地方單單使用 {{ data }},就可以知道差別了
template: `
<div class="m-Jumbotron">
<div class="m-Jumbotron__title">
<h1>
<slot :name="[getDominator(bookName.dominator.key)]" v-bind="bookName">
農村傳奇 - {{ brothers.boss }}異食記
</slot>
</h1>
<p><slot :name="[getDominator(bookName.lead.key)]" v-bind="bookName"></slot></p>
</div>
<div class="m-Jumbotron__others"><slot></slot></div>
</div>
`
另一方面,我們的 countryDominator 也要新增相應的資料
in Vue data:{} ...
countryDominator: {
dominator: {
key: 'dominator',
name: '小哥'
},
lead: {
key: 'lead',
text: '村霸的食品獻祭之路'
}
}
這樣父層也可以用一模一樣的方法
接出來
<template v-slot:cell(lead)="data">
{{ data.lead.text }}
</template>
如果照著一步步坐下來,m-jumbotron 的部份現在應該長這樣
<m-jumbotron :book-name="countryDominator">
<template v-slot:cell(dominator)="data">
農村傳奇 - {{data.dominator.name}}奪權
</template>
<template v-slot:cell(lead)="data">
{{ data.lead.text }}
</template>
<hr style="opacity: 0.7;">
<p>在這裡可以看見村霸在兄弟們家,劈草奪食的紀錄。</p>
<p>唯一的自我拯救之法: 成為都市人</p>
<m-button variant="primary">過往逃生路線</m-button>
<m-button variant="danger">查看受害者名單</m-button>
</m-jumbotron>
並且圖片是:
透過這樣的設計,應該可以發現我們可以符合括號內都是 countryDominator 內成員的 key值 這樣的方式,與 BV 的用法有異曲同工之妙,並且你可以專注在提供給組件的數據上,單單的管理 countryDominator 就好了,你可以隨便更換鄉村的統治者,把 name: '小哥' 換成任何人,把 text: '村霸的食品獻祭之路' ,換成任何敘述。
countryDominator: {
dominator: {
key: 'dominator',
name: '波蘭翼騎兵'
},
lead: {
key: 'lead',
text: '歐洲的守護者'
}
}
今日內容
事實上到現在為止,可以發現我們的 template:'...' 有增大的跡象,以這樣的方式開發有個致命缺點,就是他沒有語法高量,除非有套件可以支援這方面 ( 還是已經有了 ? 我不知道 XD ),否則這樣的方式只適合用來做小東西而已,不然要維護的話真的太困難了。
下一章動態組件,為進入 CLI 前的最後一章
沒事也可以逛逛我們其他團隊成員的文章啦 ~~
eien_zheng: 前端小嘍嘍的Golang學習旅程_The journey of learning Golang 系列
PollyPO技術: 前端設計轉前端工程師-JS踩坑雜記 30 天 系列
阿電: 忍住不打牌位,只要30天VueJS帶你上A牌 系列
喬依司: 實作經典 JavaScript 30 系列