iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 5
3
Modern Web

Vue CLI + Firebase 雲端資料庫 30天打造簡易部落格及後臺管理系列 第 5

Day 05: 具名插槽 + 作用域 + CLI 前哨站之拆解 BootstrapVue Table cell() 假象

  • 分享至 

  • xImage
  •  

具名插槽 Named Slot

話說,真的很傻眼餒 ~~~ ,為什麼報名錯的項目不能取消阿,害我多一個空項目在那邊。今天講的是命名插槽啦,比昨天還要簡單的多,因為 BootstrapVue 還滿常看到這樣的組件的,所以稍微說說具名插槽,這樣在使用一些別人設計好的組件的時候才比較有概念。

參考資料:

插槽 - Vue.js

補充一下局部組件也可以這樣註冊,上一章有類似這個部分但並沒有特別提出。

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>

https://ithelp.ithome.com.tw/upload/images/20200913/20129819I6KGXrjWJD.jpg

slot 小分析:

  • header : 可以是某種大標語、或者站名。
  • lead ( lead paragraph ) : 前導段落 ( Wiki翻譯 ),可以說明這是個怎樣的網站,這邊的情境我覺得就是副標題的概念。
  • 其他 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>

https://ithelp.ithome.com.tw/upload/images/20200913/20129819VgtIeHH04w.jpg

就是類似這樣, 本章範例在此。

作用域

簡單一句話,父層用外面,子層用裡面,外面可共用,裡面各自用。

將異食記 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 }
})

https://ithelp.ithome.com.tw/upload/images/20200913/20129819HJOe7W2PCB.jpg

那如果我們要用子層,也就是裡面的資料呢 ? 那就在子層定義他自己的 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: '小哥'
  }
}

現在還是長這樣,若是我們想讓文字變成"農村傳奇 - 小哥異食記",該怎麼辦呢 ? 目前有兩個辦法。

https://ithelp.ithome.com.tw/upload/images/20200913/20129819IWwJUs8KZC.jpg

因為父層用外面,子層用裡面,所以第一個辦法就是為我們 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>

接下來畫面就變成,咦 ?

https://ithelp.ithome.com.tw/upload/images/20200913/20129819RlKLPOPt1G.jpg

貼錯圖了應該是這樣ㄏㄏ :

https://ithelp.ithome.com.tw/upload/images/20200913/20129819wJkSpknhMw.jpg

以上是第一個方法,透過 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 }}異食記" ,所以現在從小哥變回

https://ithelp.ithome.com.tw/upload/images/20200913/20129819b05QTKUbAh.jpg

改成這樣的形式就有比較多應用方法,我們前面說到 "在 slot 上綁定一個名字並接子層的資料給他,如此父層可以透過綁定的名字接出子層的資料",所以接下來透過父層接出資料的辦法,來改變我們的預設內容,把父層的 template 寫回來並且參考如下接出方式:

<template v-slot:插槽名字="為要接出來的資料取個名字"></template>

像這樣

<template v-slot:header="destroyer">
  {{ destroyer }}
</template>

畫面變這樣,可以看見我們子層的資料透過這樣的方式拿出來了,所以若是現在要讓小哥掌權,最簡單的就是直接存取這個物件像這樣 :

<template v-slot:header="destroyer">
	農村傳奇 - {{ destroyer.country.rookie }}奪權
</template>

https://ithelp.ithome.com.tw/upload/images/20200913/20129819VulLWzX5Mn.jpg

可以透過物件解構的方式 :

<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>

https://ithelp.ithome.com.tw/upload/images/20200913/20129819rsC20ycX2V.jpg

CLI 之 ".vue檔" 前哨站之差點被騙的 Bootstrap Table cell(xxx)

下一章要講的是動態組件,下下一章正式進入 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,你應該有注意到一些特殊的插槽像是 :

https://ithelp.ithome.com.tw/upload/images/20200913/20129819CseUGDca8N.jpg

咦 ? 我們有教過這種用法嗎 ? 怎麼 cell 後面還多了個帶引數的括號 ???

這邊千萬不要像我這樣被騙了老半天阿,他只是設計好讓插槽名字剛好長那樣,那整串就是名字啊,傻眼 ~~,而且要注意一下,這個是 table 組件,專門拿來顯示資料用的,這個設計方法必須要有額外傳進來的資料,他的額外資料在這:

https://ithelp.ithome.com.tw/upload/images/20200913/20129819ONZ7TdvEuz.jpg

我們可以依照他的觀念稍微改寫我們的 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>

並且圖片是:

https://ithelp.ithome.com.tw/upload/images/20200913/20129819yPz2pvPAwh.jpg

透過這樣的設計,應該可以發現我們可以符合括號內都是 countryDominator 內成員的 key值 這樣的方式,與 BV 的用法有異曲同工之妙,並且你可以專注在提供給組件的數據上,單單的管理 countryDominator 就好了,你可以隨便更換鄉村的統治者,把 name: '小哥' 換成任何人,把 text: '村霸的食品獻祭之路' ,換成任何敘述。

countryDominator: {
  dominator: {
    key: 'dominator',
    name: '波蘭翼騎兵'
  },
  lead: {
    key: 'lead',
    text: '歐洲的守護者'
  }
}

https://ithelp.ithome.com.tw/upload/images/20200913/20129819GGyV2iN3KK.jpg

今日內容

  • 組件的父子作用域
  • 洩漏數據給父層
  • 偷看一點 .vue 檔模式
  • 破解 table cell 秘密,如果破解有誤請不吝賜教 QQ

事實上到現在為止,可以發現我們的 template:'...' 有增大的跡象,以這樣的方式開發有個致命缺點,就是他沒有語法高量,除非有套件可以支援這方面 ( 還是已經有了 ? 我不知道 XD ),否則這樣的方式只適合用來做小東西而已,不然要維護的話真的太困難了。

下一章動態組件,為進入 CLI 前的最後一章


沒事也可以逛逛我們其他團隊成員的文章啦 ~~
eien_zheng: 前端小嘍嘍的Golang學習旅程_The journey of learning Golang 系列
PollyPO技術: 前端設計轉前端工程師-JS踩坑雜記 30 天 系列
阿電: 忍住不打牌位,只要30天VueJS帶你上A牌 系列
喬依司: 實作經典 JavaScript 30 系列


上一篇
Day 04: 組件自定義發射事件,父子資料傳遞
下一篇
Day 06: 製作動態組件
系列文
Vue CLI + Firebase 雲端資料庫 30天打造簡易部落格及後臺管理30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言