前言
Day04 只有一個主題,那就是components
!他的分量寫起來真的就足夠一篇了!!
用語部分,中國那邊叫做組件;臺灣比較多人叫做元件。我之前比較習慣說是組件,如果文章內有筆誤請見諒QQQ
元件
▲ 元件的組織,源自Vue官網
為什麼要用元件?
初學時都是用HTML+CSS建構網頁,要改東西很可能就要找、改半天,
或是遇到需要重複的東西時要一直寫一樣的,讓整個版面亂糟糟。
這時候Vue就是很好的幫手了!
看看上面那張圖,分成了好幾個元件;把功能細分後再組織起來變成一個網頁。
可以達到將程式碼封裝,更好的重複利用!
全域元件
元件有分成全域
及區域
,因為懶惰篇幅關係這裡就只說全域的。
來註冊一個按鈕元件試試看:
Vue.component('btn-count', {
data: function() {
return {
count: 0
}
},
template: `<button @click="count++" class="btn btn-info">被點擊了{{ count }}下</button>`
})
<div id="app">
<btn-count></btn-count>
</div>
▲ 一個按鈕元件就做出來了!很簡單吧
來分析一下這個元件是怎麼組成的呢?
// 語法
Vue.component(tagName, option)
// 我們上面的範例
Vue.component('btn-count', {
...
(省略)
...
})
tagName
:元件的名字,掛到HTML上就寫這個tag使用此元件。
options
:因為元件是可複用的Vue實例,所以與new Vue接收相同的選項,例如data
、computed
或watch
……等。僅有的例外是el
。
⚠️ 元件的註冊必須在「Vue實例初始化前」完成,不然無法使用!
(↑↑也就是 new Vue({...})
之前)
💡 關於命名,官方的建議...
強烈推薦遵循W3C規範中的自定義組件名(字母全小寫且必須包含一個連字符)。
這會幫助你避免和當前以及未來的HTML元素相衝突。
data
這裡的data和new Vue({ ... })
裡的data,怎麼感覺不一樣?
元件內的data必須是一個函數!
元件內的data必須是一個函數!
元件內的data必須是一個函數!
很重要所以說三次
上面有說到,元件是可複用的Vue實例,可複用就是可以重複使用 (廢話
所以我為什麼一定要寫成function?
- 不寫成function大家就會共用一個資料狀態。
- 使用function return每個元件間就會互相獨立不影響彼此。
不相信嗎?我們實測元件data如果沒寫成function會怎樣呢:
▲ 告訴你data的option要是個function。
Vue很貼心的噴錯給你看,這裡是引入vue.common.dev.js的CDN
我不確定用其他版本會不會也是如此,想看沒寫成函數的效果可以去官方
元件傳遞資料
元件間的溝通方式很重要,來看看下圖的傳遞方式:
Parent是父元件,Child是子元件
元件的父子都是相對的,以剛才上面創的按鈕元件為例:
可以看到btn-count
在vm
(Vue實例)之下,所以我們可以說:
vm
是btn-count
的父元件btn-count
是vm
的子元件
還是不太清楚嗎?我們來用簡單的例子理解
雖然我舉的例子可能不是很好
就以
A
跟A的上司B
舉例:
B
是A
的上司A
是B
的下屬單拿
A
這個人看,你不知道他跟B
有什麼關係;B
也同理。
但他們兩個擺在一起,就能知道他們間的關係了!元件也是一樣,單看
vm
並不能說他是父元件
(底下沒人啊~不能半路亂認爹!)把
btn-count
放進來vm
裡,他就是vm
下的子元件。
以上圖來看,我們可以看到父傳子用prop
,子傳父用$emit
,
這部分實作看看就知道是什麼意思了!
父傳子,prop
用場景描述來寫一個父傳子的prop
,今天有對父子在對話,
爸爸說:「我是你爸,給我去學Vue!」 (不是賣火柴
兒子聽到之後回覆:「好的爸爸」
用父子元件要怎麼寫呢?
<div id="app">
<child :req="msg" res="好的爸爸"></child>
</div>
Vue.component('child', {
props: ['req', 'res'],
template: `
<div>
父元件的爸爸說:{{ req }} <p/>
子元件兒子回覆:{{ res }}
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
msg: '我是你爸,去給我學Vue'
}
})
在child
的tag上有req
和res
,這是要傳入子元件裡面的屬性。
req
有使用v-bind
綁定,將父元件的msg
傳入,這是動態綁法。
res
後面可以隨意輸入字串,這裡是靜態綁法。
屬性名稱請自己更改,這裡是為了示範爸爸的要求(req)和兒子的回覆(res)XD
命名規則
HTML不會區分大小寫,所以命名上請遵照這樣的規格:
props:使用 camelCase(駝峰式大小寫),像fatherRequest、sonResponse。
HTML :使用kebab-case(短橫線隔開式)。以上面例子來說:<child :father-request="msg" son-response="好的爸爸"></child>
prop
是單向綁定的,當父元件資料改變或更新時,只會單向傳遞給子元件。
反過來是不行的(這裡指prop
不行),是為了不讓子元件可以任意去更改父元件的狀態。
子傳父➀,$emit
不能用Prop
該怎麼把子元件更新後傳給父元件呢?
在子層資料發生變化後觸發一個事件方法,告訴父層我更新了;
父層需要監聽這個事件,當捕獲到這個事件運行後,再對其資料進行變更就好~
<div id="app">
父元件msg: {{ msg }} <p/>
<child @child-click="parentClick"></child>
</div>
// 註冊元件Child
Vue.component('child', {
data() {
return {
msg: 'child',
}
},
template: `
<div>
子元件msg: {{ msg }} <p/>
<input type="text" v-model="msg">
<button @click="myClick" class="btn btn-outline-info">button</button>
</div>
`,
methods: {
myClick() {
this.$emit("child-click", this.msg)
}
}
});
new Vue({
el: "#app",
data: {
msg: "parent"
},
methods: {
parentClick(childmsg) {
this.msg = childmsg
}
}
});
用小畫家貼心der畫了一張圖表示$emit
流程,$emit
真的好煩RRR!
子傳父➁,$ref
上面$emit
我們可以清楚了解到:子層更新後觸發事件讓父層也做更動。
如果想要從父元件向子元件要資料,就可以透過$ref
取得~
子元件1點擊了:<child ref="child1"/>次 <p/>
子元件2點擊了:<child ref="child2"/>次 <p/>
<button @click="show" class="btn btn-outline-info">查看</button>
Vue.component('child', {
data() {
return {
count: 20
}
},
methods: {
getCount() {
this.count++
}
},
template: `
<button @click="getCount" class="btn btn-info mt-2">{{ count }}</button>
`
})
// 父元件省略其他,只寫methods
methods: {
show() {
console.log(this.$refs.child1.count);
console.log(this.$refs.child2.count);
}
}
slot
上面我們介紹了元件,知道它有個功能:用來解決需要重複使用的元件。
如果我們做了警告框元件,像底下這樣:
現在要使用於各個場景,要怎麼解決警告文字的問題呢?
這時候好幫手slot
就出現了!slot
是插槽,用於內容分配。
我們實作上面警告框的例子來看:
<div id="app">
<danger></danger>
</div>
const danger = {
template: `
<div class="alert alert-danger">⚠️<slot></slot> </div>
`,
}
new Vue({
el: '#app',
components: {
danger,
},
})
我們要在警告icon後面加上放文字的地方,用slot
插槽
子元件danger中放入文字
<div id="app">
<danger>這樣會不會出現文字呢!</danger>
</div>
子元件 danger 預留了一個空間,讓父元件插入內容並顯示於預留空間。
(重點劃起來:⭐ slot = 預留空間 ⭐)
所以推論出:
- 父元件:負責內容,不負責擺放的位置;
- 子元件:負責擺放位置,不負責內容。
Named Slot 具名插槽
從上面例子可以知道插槽的應用。如果今天有很多slot
,要怎麼知道如何預留空間?
slot
要知道自己要在哪裡預留,必須透過給名字讓他們對號入座。
以例子來說,如果我的元件要顯示吉娃娃的名字、年齡和毛色,要怎麼讓slot對應呢?
在元件中這麼寫:
const card = {
template: `
...
<h4>Name</h4>
<p class="card-text">
<slot name="name"></slot>
</p>
<h4>Age</h4>
<p class="card-text">
<slot name="age"></slot>
</p>
<h4>Color</h4>
<p class="card-text">
<slot name="color"></slot>
</p>
...
`
}
在HTML裡放入元件~
<card>
<template v-slot:name>吉娃娃寶寶</template>
<template v-slot:age>100</template>
<template v-slot:color>彩色</template>
</card>
Compilation Scope 編譯作用域
這裡必須記住以下規則!!
- 父元件裡的所有內容都是在父級作用域中編譯的;
- 子元件裡的所有內容都是在子作用域中編譯的。
WHATTTTTT !?
圖源GIPHY
如果不好理解我們可以拆!
<div id="app">
<card> {{ msg }} </card>
</div>
const card = {
data() {
return {
msg: "我是card元件的msg",
}
},
template: `
<div>
<span>我是card元件</span>
<slot></slot>
</div>
`
}
var vm = new Vue({
el: "#app",
data: {
msg: "我是vm的msg"
},
components: {
card
},
})
看看上面的規則,推論{{msg}}的結果會是什麼呢?
{{ msg }}並不是作用在子元件裡!!他是在父元件中執行的。
如果不太清楚可以往上看元件傳遞資料部分,跟這裡是差不多意思的。
<template v-slot:name>吉娃娃寶寶</template>
<template v-slot:age>100</template>
<template v-slot:color>彩色</template>
這些都是由父元件對子元件提供的內容,所以這裡我們也可以使用父元件的data。
Scoped Slots
接續例子,我要把吉娃娃的年齡換算成人類年齡。
如果age
在子元件的data裡,想在父元件提供插入內容時順便換算,該怎麼做呢?
首先我們到要傳遞資料的slot,把要傳出去的值給綁定。
將子元件data中的ageData
綁定給age
,這會做用在name為age的slot。
const card = {
data() {
return {
ageData: 10,
}
},
template: `
...
<slot name="age" :age="ageData"></slot>
...
`
}
age
要能在父級的插槽內容可用,要將age
作為<slot>
元素的一個attribute綁定上去。
這裡我命名為props
。
<template v-slot:age="props">人類年齡約:{{ props.age * 7 }}</template>
這樣就可以看到資料有傳遞過來了!
slot技術總結
v-slot
只能添加在<template>
上 。- 上面情況的例外:只有
默認slot
(沒有name),元件標籤才可被當作插槽的模板來使用。 - 默認插槽的縮寫語法不能和具名插槽混用,因為它會導致作用域不明確
小小心得
若觀念或內容有誤請不吝指教,謝謝您( ᐛ)パァ
元件部分是我比較不熟的,今天複習完之後感覺好很多了QQ
尤其是資料傳遞的部分 & slot,收穫很多。
但我還有同層的event bus沒說到QQ
→ 參考掘金vue篇之事件总线(EventBus)
專案如果大的話其實不好維護,這部分可以改用vuex!後天會講的:)
還剩3天,加油!(๑•̀ㅂ•́)و✧