前言
本節內容包含下述子章節:
- 元件概念介紹
- 使用 x-template 建立元件
- 使用 function return 建構資料格式
- props 基本觀念
- props 使用上的注意事項
- props 型別及預設值
- emit 向外層傳遞事件
- Slot 元件插槽
- 使用 is 動態切換元件
- 元件章節作業
- Vue 元件測驗
元件概念介紹
元件可獨立運作且可重複使用。
元件中的資料皆為獨立的。
父元件傳遞資料給子元件,使用
props傳遞。父元件更新資料時,props會即時將資料更新到內層元件。子元件若要傳遞給父元件,是使用
emit,必須透過e觸發事件mit才會能將資料傳到父元件,
相對 props 來說它並非即時的。
SPA(single page application)也是透過元件製作。
使用 x-template 建立元件
在 Vue 裡面,我們可以使用元件來動態掛載 HTML ,而元件到底是什麼?
如果以 HTML 來說的話,就會類似像是 Header 、 Content 這樣一個區塊一個區塊,區塊裡面當然還可以包覆區塊,
所以元件內也可以包覆元件。
接下來來個基本的 HTML 表格:
<div id="app">
<table class="table">
<thead>
</thead>
<tbody>
<tr v-for="(item, key) in data" :key="key">
<td>{{ item.name }}</td>
<td>{{ item.cash }}</td>
<td>{{ item.icash }}</td>
</tr>
</tbody>
</table>
</div>
然後基本的 Vue 架構和資料:
<script>
var app = new Vue({
el: '#app',
data: {
data: [
{
name: '小明',
cash: 100,
icash: 500,
},
{
name: '杰倫',
cash: 10000,
icash: 5000,
},
{
name: '漂亮阿姨',
cash: 500,
icash: 500,
},
{
name: '老媽',
cash: 10000,
icash: 100,
},
]
}
});
</script>
接下來 <tbody> 下的 <tr> 是可以重複使用的,所以我們選擇這部分來改寫成元件,
在 Vue 裡面要新增元件時,要在外面寫
上 Vue.component('',{}); 裡面兩個參數,分別是自訂義的元件名稱,和元件的物件內容:
<script>
Vue.component('row-component',{
template: '#rowComponentTemplate'
});
var app = new Vue({
....
});
</script>
我們在 row-component 元件內有多一個 template 的屬性,這是用來等一下新增 x-template 與其 id 綁定的名字(可自訂義),
至於 x-template 是什麼? 簡單來說就是一個封裝我們的 HTML 模板的 <script> 。所以這時候,我們再新增一個 <script> 而
type 屬性需改成 text/x-template 且記得給予 id ,而 id 就是剛剛 template 屬性自訂義的值,且把需要做成元件的
HTML 部分放在裡面:
<script type="text/x-template" id="rowComponentTemplate">
</script>
<script>
Vue.component('row-component',{
template: '#rowComponentTemplate'
});
var app = new Vue({
....
});
</script>
這時候我們就可以把我們的元件用標籤的方式寫在 HTML 表格內:
<div id="app">
<table class="table">
<thead>
</thead>
<tbody>
<row-component v-for="(item, key) in data" :key="key"></row-component>
</tbody>
</table>
</div>
會發現頁面上還是沒有資料顯示出來,這是因為每個元件的資料都是獨立的,所以row-component 內的 item 是空的,這時候
如果我們想要把資料傳遞給 row-component 接收,就要在剛剛新增元件的地方,新增一個 props 屬性:
<script>
Vue.component('row-component',{
props: ['person'],
template: '#rowComponentTemplate'
});
var app = new Vue({
....
});
</script>
props 屬性放的是一個陣列,這裡我們先自定義為 person ,並動態把它綁在 row-component 上,即 :person="item" :
<div id="app">
<table class="table">
<thead>
</thead>
<tbody>
<row-component v-for="(item, key) in data" :person="item" :key="key"></row-component>
</tbody>
</table>
</div>
並且 x-template 的地方, item 的部分也要改成 person :
<script type="text/x-template" id="rowComponentTemplate">
</script>
會發現資料呈現上去了,可是會有跑版的情況,這是 <table> 的特性,在 <tbody> 後面都是接 <tr> 而我們卻接了我們新建的
元件,如果這時候檢查元件(F12),會發現 HTML 結構會如下:
<div id="app">
<tr></tr>
<tr></tr>
<tr></tr>
<tr></tr>
<table>
</table>
</div>
這跟我們原先預期的情況不一樣,因為我們要把 <tr> 掛在 <table> 內的 <tbody> 下,這時候我們可以把 HTML 的部分
改成如下:
<div id="app">
<table class="table">
<thead>
</thead>
<tbody>
<tr is="row-component" v-for="(item, key) in data" :person="item" :key="key"></tr>
</tbody>
</table>
</div>
會發現新增了一個 is 屬性,值就是我們新增的元件,這時候跑版問題就會解決了。
在前面講了很長篇幅的元件,那元件可以分為「局部註冊」跟「全域註冊」,前面的方式我們都是採用全域註冊,而所謂的全域
就是指,當我在新增一個 Vue 的應用程式時,它也可以使用這個元件,那局部的意思就是指,我們只想給當前這個應用程式做
使用,如果要這樣使用,我們就要改寫如下:
<script>
var child = {
props: ['person'],
template: '#rowComponentTemplate'
}
var app = new Vue({
data: {},
components: {
'row-component': child
}
});
</script>
不同於前面的是,我們新增了一個 child 變數為物件,裡面放置的就是我們本來 Vue.component() 內的內容,而我們在當前的
Vue 應用程式內在 data 後面,新增了一個 components 屬性,左邊放置新增的元件名字,右邊放置我們在上層新增的 child
變數,這時後我們的元件就會變成局部的了。
使用 function return 建構資料格式
在元件的使用上,如果有使用到 data 這個屬性,一定要搭配 function return 的用法才能正常運作,這是為了確保
每個 instance 實例可以維護一份獨立的拷貝物件,避免修改一個 instance 的數值後影響到其他所有的 instance。
<script>
var child = {
data: function(){
return {
counter: 0
}
},
props: ['person'],
template: '#rowComponentTemplate'
}
var app = new Vue({
data: {},
components: {
'row-component': child
}
});
</script>
props 基本觀念
這邊要介紹由外到內的資料傳遞,什麼時候會使用到呢?
先前有提到每個元件的資料狀態都是獨立的,當我們外部的資料要傳遞到內部時,就必須使用到元件裡的 props 屬性。
props 為父元件向子元件傳遞資料的方式,在子元件中設定所要傳入的變數名稱,在 HTML 中使用該元件時使用該變數
名稱即可將資料傳入。
傳遞資料的方式有兩種
- 靜態傳遞的寫法:
將 Vue.component 裡 props 定義的變數名稱 imgUrl ,放入 HTML 中,放入之後他就會將資料透過 props 傳進去。
注意:
在Vue中props的變數名稱imgUrl是使用小駝峰的寫法,但是如果要放入HTML中仍使用小駝峰是不符合規範的,必須改為img-url。
<div id="app">
<h2>靜態傳遞</h2>
<photo img-url="https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80"></photo>
<h2>動態傳遞</h2>
<photo></photo>
</div>
<script type="text/x-template" id="photo">
<img :src="imgUrl" class="img-fluid" alt="" />
</script>
<script>
Vue.component('photo', {
props: ['imgUrl'],
template: '#photo'
})
var app = new Vue({
el: '#app',
data: {
url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80'
}
});
</script>
- 動態傳遞的寫法:
我們一樣使用變數名稱 img-url ,但是前面須加上冒號 : ,這個跟 v-bind 的概念是一樣的,當我們要用動態資料的時候,
就會使用這種方式,就是在屬性前方加上冒號 :img-url ,接著我們再把 new Vue 中 data 裡的變數 url 放在等號後面,
即 :img-url="url" 。
<div id="app">
<h2>靜態傳遞</h2>
<photo></photo>
<h2>動態傳遞</h2>
<photo :img-url="url"></photo>
</div>
<script type="text/x-template" id="photo">
<img :src="imgUrl" class="img-fluid" alt="" />
</script>
<script>
Vue.component('photo', {
props: ['imgUrl'],
template: '#photo'
})
var app = new Vue({
el: '#app',
data: {
url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80'
}
});
</script>
props 使用上的注意事項
- 單向數據流
看到下圖有一個 url ,這個是由 Vue 的應用程式由外層所帶進來的,沒有經過任何處理。
這時候如果在畫面上直接修改 url , console 就會直接跳錯告訴你不要直接修改 props 的內容,它會希望你在處理資料
的時候是單向,而不要反向的寫回去。
這時候我們要在 Vue.component 裡新建一個 data 來接這個外部傳進來的資料內容,一樣 data 後面是 function return ,
裡面我們新建一個變數 newUrl 來接外部過來的 props ,而 newUrl: this.imgUrl 的 imgUrl 就是 props 所傳進來的。
接著把 newUrl 放入 input 的 v-model 裡,回到畫面重新整理後再去調整 url 就不會在跳錯了!
<div id="app">
<h2>單向數據流</h2>
<photo :img-url="url"></photo>
<p>修正單向數據流所造成的錯誤</p>
</div>
<script type="text/x-template" id="photo">
<img :src="imgUrl" class="img-fluid" alt="" />
<input type="text" class="form-control" v-model="newUrl">
</script>
<script>
Vue.component('photo', {
props: ['imgUrl'],
template: '#photo',
data: function () {
return {
newUrl: this.imgUrl
}
}
})
var app = new Vue({
el: '#app',
data: {
user: {},
url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80',
isShow: true
},
created: function() {
var vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function(data) {
vm.user = data.results[0];
}
});
}
});
</script>
- 物件傳參考特性 及 尚未宣告的變數
可以看到 new Vue 應用程式內,主要需傳入的資料內容有 user 跟 url ,裡面的 user 目前是空的,我們希望傳入的時候
是經過 ajax 這個非同步的行為處理完之後,才把 user 的資料傳進去,所以在沒有 user 資料的時候,畫面可能就會出錯,
來看我們該如何解決這個問題。
看到 Vue.component 的 card ,它會接收外部的 userData ,並且將卡片這個元件繪製出來。
那這個 userData 是怎麼來的呢?它是透過 new Vue 應用程式,先宣告一個 user 的物件,再透過 ajax 行為,
把遠端的資料抓進來之後,才把它存進 user 裡面。
<div id="app">
<h2 class="mt-3">物件傳參考特性 及 尚未宣告的變數</h2>
<div class="row">
<div class="col-sm-4">
<card :user-data="user"></card>
</div>
</div>
</div>
<script type="text/x-template" id="card">
<div class="card">
<img class="card-img-top" :src="user.picture.large" alt="Card image cap">
<div class="card-body">
<div class="card-footer">
<input type="email" class="form-control" v-model="user.email">
</script>
<script>
Vue.component('card', {
props: ['userData'],
template: '#card',
data: function () {
return {
user: this.userData
}
}
});
var app = new Vue({
el: '#app',
data: {
user: {},
url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80',
isShow: true
},
created: function() {
var vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function(data) {
vm.user = data.results[0];
}
});
}
});
</script>
那這個 user 在傳入的時候會有時間差,所以就會跳出錯誤,因為它根本找不到裡面其中一些變數。
這時候該怎麼做呢?
我們可以在 html 的 card 標籤裡,新增 v-if 。
假設 user 的哪一個特性並沒有完全讀進來之前,就不要將這個卡片執行出來。我們可以打開 Vue 開發者工具,像是 ajax
的時候它就會讀進來一些資料,我們就選取一個它一定會得到的資料內容,假設它有電話號碼才能把卡片繪製出來,那 v-if 後
面就加入 user.phone ,這時候卡片就能正確繪製並不會跳錯了。
這邊的重點是:
如果你的資料載入有時間差,就可以使用v-if讓元件的產生時間往後移,可以判斷資料中的某個特性尚未讀入就不要顯示該元件之類的,避免error / warn產生。
<div id="app">
<h2 class="mt-3">物件傳參考特性 及 尚未宣告的變數</h2>
<div class="row">
<div class="col-sm-4">
<card :user-data="user" v-if="user.phone"></card>
</div>
</div>
</div>
<script type="text/x-template" id="card">
<div class="card">
<img class="card-img-top" :src="user.picture.large" alt="Card image cap">
<div class="card-body">
<div class="card-footer">
<input type="email" class="form-control" v-model="user.email">
</script>
<script>
Vue.component('card', {
props: ['userData'],
template: '#card',
data: function () {
return {
user: this.userData
}
}
});
var app = new Vue({
el: '#app',
data: {
user: {},
url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80',
isShow: true
},
created: function() {
var vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function(data) {
vm.user = data.results[0];
}
});
}
});
</script>
另外這邊要提到物件傳參考的特性,先打開 Vue 開發者工具,下方有一個 email ,我們可以看到 Card → user → email ,
這整個物件是從外層應用程式傳進來的,所以我們可以看到 Root → user 這裡有一個一模一樣的 email 。
這時候我們在畫面中修改這個 email ,會發生什麼事呢?
這時候可以透過 Vue 開發者工具看到 Card 跟 Root 裡面的 email 都被修改過去了,這個是 javascript 的特性,
物件在傳遞的時候它是傳參考,透過下面例子你就會了解了!
JavaScript 在傳遞物件時 call by reference,所以在子元件內部的物件資料變動會影響外部物件。
- 維持狀態與生命週期
這邊的卡片在每次啟動的時候都會執行一段 ajax ,我們在畫面的 Check me out 前方的欄位打勾跟取消,你會發現照片顯示
的不一樣,也就是說它在每次元件銷毀再生成的時候,它是重新再執行一次這段 ajax ,當然如果這是符合預期的狀態這麼做
是沒有問題的,但是有時候我們不希望每次元件在重新生成的時候,還要完全重新執行一次裡面的程式碼,這時候該怎麼做呢?
這時候就可以使用 <keep-alive> 來維持元件的生命週期,在 html 裡面新增 <keep-alive> 標籤,將 <keep-card> 元件放入
<keep-alive> 裡面,接著把 v-if="isShow" 放入 <keep-card> 標籤。好了之後回到畫面重新整理,將 Check me out 前方的
欄位打勾跟取消,你會發現照片顯示的是同一個人。
<div id="app">
<h2 class="mt-3">維持狀態與生命週期</h2>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="isShow" v-model="isShow">
<label class="form-check-label" for="isShow">Check me out</label>
</div>
<div class="row">
<div class="col-sm-4">
<keep-alive>
<keep-card v-if="isShow">
</keep-card>
</keep-alive>
</div>
</div>
</div>
<script type="text/x-template" id="card">
<div class="card">
<img class="card-img-top" :src="user.picture.large" alt="Card image cap">
<div class="card-body">
<div class="card-footer">
<input type="email" class="form-control" v-model="user.email">
</script>
<script>
Vue.component('keepCard', {
template: '#card',
data: function() {
return {
user: {}
}
},
created: function() {
var vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function(data) {
vm.user = data.results[0];
}
});
}
});
var app = new Vue({
el: '#app',
data: {
user: {},
url: 'https://images.unsplash.com/photo-1522204538344-922f76ecc041?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=50e38600a12d623a878983fc5524423f&auto=format&fit=crop&w=1351&q=80',
isShow: true
},
created: function() {
var vm = this;
$.ajax({
url: 'https://randomuser.me/api/',
dataType: 'json',
success: function(data) {
vm.user = data.results[0];
}
});
}
});
</script>
我們看一下 Vue 的開發者工具,將畫面中 Check me out 取消勾選時, <keep-card> 旁邊會顯示 inactive ,這只是暫時將它
隱藏。當我們下次啟動的時候,它不會重新執行整個元件的生命週期,它會重新將它呼叫出來而已,所以這就是維持生命周期的
方法。
不過在元件中有一個問題,它無法讀到特定的屬性。例如:在 template 裡面的 img 中 :src="user.picture.large" 這張照片
無法讀取到,原因是因為這邊這段跟上方有點相同,在讀取物件的時候 ajax 還沒有執行完成。
所以我們可以在 :src 後方加上 v-if="user.picture" ,另外一個在 <h5> 標籤後方也加上 v-if="user.name",這樣就不會再
跳錯了。而這些內容都會確保在資料載入之後,才將物件的內容讀出來。
<script type="text/x-template" id="card">
<div class="card">
<img class="card-img-top" :src="user.picture.large" v-if="user.picture" alt="Card image cap">
<div class="card-body">
<div class="card-footer">
<input type="email" class="form-control" v-model="user.email">
</script>
props 型別及預設值
在前面我們已經了解了元件,現在我們要來多加說明 props 這個屬性, props 是由外而內傳遞資料所使用的屬性,
有時候我們傳遞的資料也許是需要數值型態來計算的,這時候我們該如何來定義型別?
下面我們有一個 prop-type 的元件,動態綁著 props 傳遞過來的資料 cash
<div id="app">
<h2>Props 的型別</h2>
<prop-type :cash="cash"></prop-type>
</div>
<script type="text/x-template" id="propType">
<input type="number" class="form-control" v-model="newCash">
</script>
<script>
Vue.component('prop-type', {
props: ['cash'],
template: '#propType',
data: function() {
return {
newCash: this.cash
}
}
});
var app = new Vue({
el: '#app',
data: {
cash: '300'
}
});
</script>
這個時候會發現,我們的型別會是字串,那如果我們想要在傳遞時就給它定義數值型別呢?
這個時候我們就可以改寫一下 props 的部分:
<script>
Vue.component('prop-type', {
props: {
cash: {
type: Number,
}
},
template: '#propType',
data: function() {
return {
newCash: this.cash
}
}
});
var app = new Vue({
el: '#app',
data: {
cash: '300'
}
});
</script>
我們把 props 改成物件型態,並把要接受過來的 cash 也寫成物件,並給予 type 屬性為 Number 。
這時候數值型別就定義好了,可是會發現,畫面上還是顯示字串,這個時候我們就可以檢查 Vue 應用程式內的
data 的 cash 就會發現竟然是字串!!! 這時候把它改寫成 cash: 300 寫成數值就可以了。
如果我們在元件並沒有動態綁定要傳遞的資料,如下:
<div id="app">
<h2>Props 的型別</h2>
<prop-type></prop-type>
</div>
這時候我們可以在 props 內的 cash 物件,多加一個 default 屬性,並給它一個數值,這個就是當元件沒有綁定要傳遞
的資料,預設要給予多少數值:
<script>
Vue.component('prop-type', {
props: {
cash: {
type: Number,
default: 300,
}
},
template: '#propType',
data: function() {
return {
newCash: this.cash
}
}
});
var app = new Vue({
...
});
</script>
另外一種情況是靜態與動態傳入數值的差異性,如下:
<div id="app">
<h2 class="mt-3">靜態與動態傳入數值差異</h2>
<prop-type cash="300"></prop-type>
</div>
如果我們是採用靜態的方式直接傳入數值,會發現型態仍然是字串,但如果我們把它改成動態的:
<div id="app">
<h2 class="mt-3">靜態與動態傳入數值差異</h2>
<prop-type :cash="300"></prop-type>
</div>
即在 cash 加上 : ,也就是 (v-bind) 縮寫,
就會發現會自動轉型成數值傳遞了,這是比較值得注意的一點
emit 向外層傳遞事件
前面講到 props ,它是由外而內傳遞資料的一種屬性
不同於 props, emit 是一種事件,而且特性是由內至外
有一種情境是,當我們在 Vue 的 methods 寫好了一個 function ,而元件也想使用時,這時候我們難道還要在元件底下
重新寫一次,而不能直接取用外面的 methods 的 function 嗎 ?
所以 emit 就是這樣的功用,
首先我們先把基本的情境架構寫出來:
<div id="app">
<h2>透過 emit 向外傳遞資訊</h2>
我透過元件儲值了 {{ cash }} 元
<button>按我</button>
<button-counter></button-counter>
<hr>
<button-counter></button-counter>
</div>
<script>
Vue.component('buttonCounter', {
<input type="number" class="form-control mt-2" v-model="counter">
data: function() {
return {
counter: 1
}
}
});
var app = new Vue({
el: '#app',
data: {
cash: 300
}
});
</script>
第一件事情是點擊「按我」的按鈕會觸發一個事件,然後讓 data 內的 cash +1
這時候我們在 Vue 的應用程式底下
新增一個 methods 屬性,並新增一個讓 cash +1 的 function ,也就是 incrementTotal
<script>
Vue.component()
var app = new Vue({
el: '#app',
data:{
cash: 300
},
methods: {
incrementTotal: function(){
this.cash ++;
}
}
});
</script>
然後在「按我」的按鈕新增一個 click 事件,觸發 function ,即 @click="incrementTotal"
<div id="app">
<h2>透過 emit 向外傳遞資訊</h2>
我透過元件儲值了 {{ cash }} 元
<button @click="incrementTotal">按我</button>
<button-counter></button-counter>
<hr>
<button-counter></button-counter>
</div>
這時候 button-counter 元件也想使用 incrementTotal 這個 function 的話,該如何使用 emit 來寫呢?
首先先在 button-counter 元件新增 v-on:自訂義的名字="incrementTotal"
<div id="app">
<h2>透過 emit 向外傳遞資訊</h2>
我透過元件儲值了 {{ cash }} 元
<button @click="incrementTotal">按我</button>
<button-counter v-on:increment="incrementTotal"></button-counter>
<hr>
<button-counter></button-counter>
</div>
再來,在 button-counter 元件的程式碼內新增一個 methods 屬性,並再自訂義一個 function 使用 emit :
<script>
Vue.component('buttonCounter', {
<input type="number" class="form-control mt-2" v-model="counter">
data: function() {
return {
counter: 1
}
},
methods: {
incrementCounter: function(){
this.$emit('increment');
}
}
});
var app = new Vue({
el: '#app',
data: {
cash: 300
},
methods: {
incrementTotal: function(){
this.cash ++;
}
}
});
</script>
到這裡就會發現,我們點擊 button-counter 元件產生的 button 時,也能讓 cash +1 了。
注意:
emit事件的命名需為小寫
另外, emit 其實可以傳遞參數,我們在 this.$emit('increment'); 內的 'increment' 後方加上 this.counter ,這個 counter 其實是 input 裡面的數值。但是為了避免傳出去的不是數值,我們應該要寫這樣 Number(this.counter) 。
<script>
Vue.component('buttonCounter', {
<input type="number" class="form-control mt-2" v-model="counter">
data: function() {
return {
counter: 1
}
},
methods: {
incrementCounter: function(){
this.$emit('increment', Number(this.counter));
}
}
});
var app = new Vue({
el: '#app',
data: {
cash: 300
},
methods: {
incrementTotal: function(){
this.cash ++;
}
}
});
</script>
那麼傳入的數值要從外層的 incrementTotal 方法做接收,所以在 function 內要增加一個參數 newNumber ,而 function 內程式碼要改成 this.cash = this.cash + newNumber; 。
<script>
Vue.component('buttonCounter', {
<input type="number" class="form-control mt-2" v-model="counter">
data: function() {
return {
counter: 1
}
},
methods: {
incrementCounter: function(){
this.$emit('increment', Number(this.counter));
}
}
});
var app = new Vue({
el: '#app',
data: {
cash: 300
},
methods: {
incrementTotal: function (newNumber) {
this.cash = this.cash + newNumber;
}
}
});
</script>
Slot 元件插槽
前面幾篇講到了元件,並介紹元件之間由外而內資料傳遞的屬性 props ,還有由內而外傳遞的事件 emit 。
元件也不只可以重複的讓我們來做使用,如果今天一個元件我們只想要替換部分內容也是可行的,這時候就要來介紹 slot 了,
也就是替換部分內容。
首先先照前面的方式建立一個元件:
<div id="app">
<h2>沒有插槽可替換的狀態</h2>
<no-slot-component></no-slot-component>
</div>
<script type="text/x-template" id="noSlotComponent">
<div class="alert alert-warning">
這沒有插槽。
</script>
<script>
Vue.component('no-slot-component', {
template: '#noSlotComponent',
});
var app = new Vue({
el: '#app',
data: {}
});
</script>
這時候如果在 no-slot-component 元件內,加上一些內容:
<div id="app">
<h2>沒有插槽可替換的狀態</h2>
<no-slot-component>
<p>這是一段置換的內容</p>
</no-slot-component>
</div>
會發現內容根本沒有被呈現出來
如果今天換成另外一種情況:
<div id="app">
<h2>Slot 基礎範例</h2>
<single-slot-component>
<p>使用這段取代原本的 Slot。</p>
</single-slot-component>
</div>
<script type="text/x-template" id="singleSlotComponent">
<div class="alert alert-warning">
如果沒有內容,則會顯示此段落。
</script>
<script>
Vue.component('single-slot-component', {
template: '#singleSlotComponent',
})
var app = new Vue({
el: '#app',
data: {}
});
</script>
我們在 single-slot-component 元件內新增了一個 <p>,然後在 x-template 的地方新增一個 <slot> 標籤:
<script type="text/x-template" id="singleSlotComponent">
<div class="alert alert-warning">
如果沒有內容,則會顯示此段落。
</script>
會發現內容就被呈現出來了
有趣的是當我們替換成下面的樣子時:
<script type="text/x-template" id="singleSlotComponent">
<div class="alert alert-warning">
如果沒有內容,則會顯示此段落。
</script>
如果 single-slot-component 元件沒有任何內容時,就會顯示 <slot> 標籤內的內容,如果有的話,就是呈現插入的內容
如果我們想要多個部分內容置換時:
<div id="app">
<h2>具名插槽</h2>
<named-slot-component>
<header>替換的 Header</header>
<template>替換的 Footer</template>
<template>按鈕內容</template>
</named-slot-component>
</div>
<script type="text/x-template" id="namedSlotComponent">
<div class="card my-3">
<div class="card-header">
<div class="card-body">
<h5 class="card-title">Special title treatment</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="#" class="btn btn-primary">
<div class="card-footer">
</script>
<script>
Vue.component('named-slot-component', {
template: '#namedSlotComponent',
});
var app = new Vue({
el: '#app',
data: {}
});
</script>
我們已經把要置換的內容放在 named-slot-component 元件裡面了,這時候我們只要在 x-template 的地方,把要置換的部分對應起來,這時候除了用 slot 標籤之外,還要賦予 name 這個屬性,值可以自定義,如下:
<script type="text/x-template" id="namedSlotComponent">
<div class="card my-3">
<div class="card-header">
<div class="card-body">
<h5 class="card-title">Special title treatment</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="#" class="btn btn-primary">
<div class="card-footer">
</script>
然後在 named-slot-component 元件置換內容的部分,新增 slot 屬性,值就是我們剛剛自定義 name 屬性的值
<div id="app">
<h2>具名插槽</h2>
<named-slot-component>
<header slot="header">替換的 Header</header>
<template>替換的 Footer</template>
<template>按鈕內容</template>
</named-slot-component>
</div>
這時候內容就會替換上去了。
其他地方也是一樣的。
但是有發現我元件內的置換內容有用到 <header> 跟 <template> 標籤,它們有甚麼差異嗎?
我們打開開發者工具,看到 <div class="card-header"> 下方的 <slot> 標籤並不會顯示出來,而 <header> 標籤是從外層替換
過來的,所以外層是用什麼標籤內層就會替換成什麼標籤。
但有些時候我們不希望標籤被輸出的時候可以怎麼做呢?
我們就可以使用 <template> 標籤,可以看到這裡的 <a> 標籤,裡面對應的是 <slot> ,而 <slot> 本身是不會被輸出的。
而 <template> 一樣的道理,它不會被輸出,有些時候我們在使用 HTML ,我們不喜歡產生額外的標籤,就可以使用
<template>
使用 is 動態切換元件
這邊要來介紹用 is 來動態切換組件,先來看一下程式碼:
下方有兩個組件一個是 id="primaryComponent" ,另一個是 id="dangerComponent" ,
兩者只差在顏色的不同而已。
可以看到 Vue 初始化內容的 current ,等一下我們會將 'primary-component' 透過 is 切換成 'dangerComponent' ,
<script type="text/x-template" id="primaryComponent">
<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
<div class="card-body">
</script>
<script type="text/x-template" id="dangerComponent">
<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
<div class="card-body">
</script>
<script>
Vue.component('primary-component', {
props: ['data'],
template: '#primaryComponent',
});
Vue.component('danger-component', {
props: ['data'],
template: '#dangerComponent',
});
// Vue 初始化的內容
var app = new Vue({
el: '#app',
data: {
item: {
header: '這裡是 header',
title: '這裡是 title',
text: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Enim perferendis illo reprehenderit ex natus earum explicabo modi voluptas cupiditate aperiam, quasi quisquam mollitia velit ut odio vitae atque incidunt minus?'
},
current: 'primary-component'
}
});
</script>
我們先來看一下這邊現在是使用 <primary-component :data="item"></primary-component> ,所以畫面上看到的會是
primary-component 的原件。
<div id="app">
<h2>使用 is 顯示單一組件</h2>
<primary-component :data="item"></primary-component>
</div>
那我們可以用另外一種寫法,我們使用 <div> 標籤,裡面寫入 is="primary-component" ,再將 :data="item" 資料內容
也放入 <div> 標籤。
<div id="app">
<h2>使用 is 顯示單一組件</h2>
<div is="primary-component" :data="item"></div>
<!-- <primary-component :data="item"></primary-component> -->
</div>
可以看到兩個元件顯示的一模一樣。
但是它有其他額外的使用方式,可以看到 <ul> 裡面包著兩個 <li> 的頁籤內容,我們會透過 @click 來切換 current 的值,
那 current 的值一個是 primary-component ,另一個是 danger-component 。
<div id="app">
<h2 class="mt-3">使用 is 動態切換組件</h2>
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" :class="{'active': current == 'primary-component'}" href="#" @click.prevent="current = 'primary-component'">藍綠色元件</a>
</li>
<li class="nav-item">
<a class="nav-link" :class="{'active': current == 'danger-component'}" href="#" @click.prevent="current = 'danger-component'">紅色元件</a>
</li>
</ul>
<div class="mt-3">
<primary-component :data="item" v-if="current === 'primary-component'"></primary-component>
<danger-component :data="item" v-if="current === 'danger-component'"></danger-component>
</div>
</div>
我們打開 Vue 開發者工具,可以看到當我們切換的時候它就會切換兩者。
這邊目前是使用 v-if 做切換,不過因為數量不多,所以程式碼看起來不會很亂。有些時候我們在切換頁籤內容,
它的數量可能會很多,這時候我們就可以使用 <div> 標籤,使用 is 來切換元件的內容,不過因為我們是動態切換,
所以 is 前面要加上冒號 : ,那我們要切換的是 current 這個變數,所以我們在動態切換 current 的時候它也會動態載入,
接下來再把 :data="item" 放進 <div> 標籤,這樣就完成了。
<div id="app">
<h2 class="mt-3">使用 is 動態切換組件</h2>
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" :class="{'active': current == 'primary-component'}" href="#" @click.prevent="current = 'primary-component'">藍綠色元件</a>
</li>
<li class="nav-item">
<a class="nav-link" :class="{'active': current == 'danger-component'}" href="#" @click.prevent="current = 'danger-component'">紅色元件</a>
</li>
</ul>
<div class="mt-3">
<div :is="current" :data="item"></div>
<!-- <primary-component :data="item" v-if="current === 'primary-component'"></primary-component>
<danger-component :data="item" v-if="current === 'danger-component'"></danger-component> -->
</div>
</div>
元件章節作業
Vue 元件測驗
第一題
Vue 的元件中,外部資料要傳入到內層,要使用何種方式?
- props
- emit
- on
點我看答案
答案: 1
第二題
Vue 的元件中,外部資料要傳入到內層的字串、數值,內層可以再次修改它?
- 對,它都傳進來了,怎麼做都可以。
- 不對,這樣會有錯誤提示。
點我看答案
答案: 2
第三題
Vue 的元件中,資料建構需額外使用 function return 的方式?
data: function(){
return {}
}
- 對。
- 錯,沒有那個必要。
點我看答案
答案: 1
第四題
Vue 的元件,如果外層有使用 keep-alive 包起來,在 created 生命週期加上 Ajax 則?
- 每次切換元件時,資料都會重新載入。
- 只有第一次會載入資料,重新切換則會維持就有狀態。
- 元件將無法被 v-if 切換。
點我看答案
答案: 2
第五題
內層的元件,如何傳遞數值到外層?
- 內層元件使用 emit 向外做事件傳遞。
- 外層使用 props 就能雙向綁定。
- 沒有辦法,元件資料是單向數據流。
點我看答案
答案: 1