雖然每個元件是獨立運作,但Vue設計元件的目的是為了讓每個元件都有各自的用途,然後再互相配合使用,如此一來,系統開發上也比較結構化。
比較常見的組合元件為父子元件,當一個A元件包含另一個B元件,則A、B元件就形成了父子關係(parent-child relationship),那要如何讓他們之間做溝通呢?以下我們來詳細介紹。
Props down, Events up
)從上圖來看,我們可以大概看出一個溝通模式:Props down, Events up
,父元件透過props
向下傳遞資料給子元件,而子元件則是透過events
將結果向上傳回給父元件。
Props down
因為元件的作用範圍是獨立的,所以當子元件想接收父元件的資料時,我們是不能直接去引用父元件的資料的,因此父元件想要傳遞資料給子元件時,可以使用props
屬性,將資料傳遞給子元件。
範例:
<div id="app">
<child :name='data_name' message='I am a child.'></child>
</div>
Vue.component('child', {
props: ['name', 'message'],
template: `<p>
<font color=red>{{ name }}</font> says
<font color=red>{{ message }}</font>
</p>`
})
var vm = new Vue ({
el: '#app',
data: {
data_name: 'Mary'
}
})
從上述的範例來看,我們建立一個全域元件
child
,並在模板使用此元件時定義了兩個屬性name
、message
,其中name
有使用v-bind
指令:
- 自製元件
child
,裡面有一個自訂屬性props
,它會去接收寫在模板的屬性name
、message
下的資料。- 使用
v-bind
指令是為了帶入vue instancedata
中的data_name
。
我們整理一下props
的功能:
在HTML中,屬性是不區分大小寫的,也就是說如果你在HTML中寫myattribute
跟寫myAttribute
,HTML會看成是一樣的東西,所以如果我們在元件中使用props
去接收屬性的資料時,寫的是camelCase(駝峰式命名法)的屬性名稱,在HTML中就要自動轉換為kebab-case(用dash間隔的命名法)。
註:如果你使用的是字串模板,則不會有這個命名的問題存在。
範例:
<div id="app">
<child my-message="HelloWorld"></child>
</div>
Vue.component('child', {
props: ['myMessage'],
template: '<p>{{ myMessage }}</p>'
})
var vm = new Vue ({
el: '#app'
})
將props
接收資料的方法結合屬性綁定的指令v-bind
和資料雙向綁定的指令v-model
,當父元件的資料改變時,子元件資料也會跟著改變,來達到動態資料變化的效果。
範例:試著改變input裡面的字,可以發現原本顯示的字也會跟著改變。
<div id="app">
<input type="text" v-model="message">
<child :my-message="message"></child>
</div>
Vue.component('child', {
props: ['myMessage'],
template: '<p>{{ myMessage }}</p>'
})
var vm = new Vue ({
el: '#app',
data: {
message: 'HelloWorld'
}
})
props
的小細節:傳入的資料是字串還是數值?如果我們只是透過props
來接收模板屬性資料時,因為模板不會做任何處理,所以我們收到的資料型態為string
,但是我們假設我們想收到的資料型態為number
,則我們必須使用v-bind
指令,在模板編譯的時候會被當成JavaScript表達式來做計算。
範例:如果屬性沒有使用v-bind
指令綁定,我們收到的”15“就是string
,“+1”後會變成"151"。
<div id="app">
<p>沒有使用v-bind:<child price="15"></child></p>
<p>有使用v-bind:<child :price="15"></child></p>
</div>
Vue.component('child', {
props: ['price'],
template: '<span>{{ price + 1 }} ({{ typeof price }})</span>'
})
var vm = new Vue ({
el: '#app'
})
Prop是單向綁定的,也就是當父元件屬性資料改變時,只能單向傳遞資料給子元件,反過來是不行的,目的是為了不讓子元件可以任意去更改父元件的狀態。
注意,當父元件更新時,子元件的所有prop也會跟著更新為最新的資料。
但是如果我們還是想要從子元件中更動父元件的資料狀態時,我們可以使用區域變數(local variable)或computed
的方式來做。
範例:如果我們直接在元件的data
中,將this.name
指派別的值,Vue會在編譯時發出警告。
<div id="app">
<child name="Mary"></child>
</div>
Vue.component('child', {
props: ['name'],
data: function() {
this.name = this.name + "is a girl.";
return {
myName: this.name;
}
},
template: '<p>{{ myName }}</p>'
})
var vm = new Vue ({
el: '#app'
})
改寫1:使用區域變數
Vue.component('child', {
props: ['name'],
data: function() {
var myName = '';
myName = this.name + "is a girl.";
return {
myName: myName;
}
},
template: '<p>{{ myName }}</p>'
})
改寫2:使用computed
Vue.component('child', {
props: ['name'],
computed: {
myName: function() {
return this.name + "is a girl.";
}
}
template: '<p>{{ myName }}</p>'
})
我們可以在元件定義Prop的資料型態,當傳入的資料不符合該型態時,Vue就會提出警告。
通常會使用物件的方式來定義資料型態,但是如果有多個資料型態,可以使用陣列。
Vue.component('child', {
props: {
// 直接定義屬性
id: Number,
// 使用陣列來定義多個型態
name: [String, Number],
// 使用物件來寫一些規則
age: {
type: String,
default: '',
validator: function(value) {
return value > 0;
},
required: true
}
}
})
物件中的規則說明如下:
type
:資料型態default
:預設值validator
:檢驗屬性資料是否有效required
:該屬性是否為必要type
可以有的屬性如下:
String
Number
Boolean
Function
Object
Array
Symbol
type
可以是自訂的建構式函式,使用instanceof
來檢測。
注意,因為元件會在vue instance創建之前建立好,所以在default
或validator
函數裡,不能使用data
、computed
、methods
等屬性無法使用。
Events up
我們學會父元件傳遞資料給子元件,那子元件要怎麼回應給父元件呢?
子元件回應的方式是Events up
,就是Vue的事件處理,使用v-on
接收事件觸發並且去呼叫對應函式,對v-on
的詳細介紹可以去看Day10的內容,下面我們就來介紹子元件要怎麼使用events
去回應父元件。
事件介面
(Event Interface)實作方式包含以下兩種:$on(eventName)
監聽事件$emit(eventName)
觸發事件在HTML模板中,子元件即使用v-on
綁定事件。
如果我們想在某個元件上監聽一個原生事件,可以使用v-on
的修飾符.native
來寫:
<my-component @click.native="doSomething"></my-component>
如果是兩個子元件要互相溝通,因為元件都是獨立運作在自己作用域的,因此我們可以透過創建一個空的Vue Instance作為事件的總管理線。
// 創建空的Vue Instance "bus"
var bus = new Vue()
// 觸發A元件中的事件 "id-selected"
bus.$emit('id-selected', 1)
// 在B元件的 "created" 鉤子中監聽 "id-selected" 這個事件
bus.$on('id-selected', function(id) {
// content
})
但這只能適用於小型網站應用開發,試想如果有很多子元件都透過這樣的方式溝通,會讓這個bus
管理線變得很繁忙複雜,因此中大型網站官方推薦使用Vuex,它是一個狀態管理模式,後面Day20我們會提到關於Vuex的概念與應用。
不好意思,這篇文章一直在說父元件要傳遞資料給子元件時,可利用props來傳遞。還有父元件資料改變時,子元件資料也會跟著改變。
請問,父元件是什麼?是Vue的實體嗎? 子元件又是什麼?是Vue.component('child'裡的child嗎?
google了很多component props的教學,都沒人提到父元件和子元件是什麼,搞得我一頭霧水的...= =
恩恩,對的,父元件指的是Vue實體,我在js中創建的Vue實體vm,有掛載(el)這個實體的元件(#app),也就是html裡面的div tag。
而子元件也就是你說的,child tag,而我在宣告Vue實體時有給定data屬性,因此透過props方法,可以自定義child元件的屬性(需使用v-bind,“:name”),也可以接收父元件的資料,因此最後印出結果即可看見我宣告在Vue實體的data_name有成功顯示出來。
謝謝你的回覆,可能當時我的文章內容沒有寫得很清楚,希望我的回覆有真的幫助到你,加油:)
謝謝!
再請教一個很白痴的問題,你說當一個A元件包含另一個B元件,則A、B元件就形成了父子關係(parent-child relationship)
但要怎看A元件有沒有包含B元件??
以vue實體vm為例,我看不出來vm有包含child..
要怎看A元件有沒有包含B元件?? 我知道這問題一定一堆人覺得很白痴!但我真的不懂啊,方便的話可否請你指點一二,謝謝!
抱歉,現在才看到回應
父子元件的區分,其實就從HTML去看就可以了,div元素下包了一個child的元素,而div的id為app,也就是vue instance掛載的元件,因此div為父元件(通常為vue instance居多),而child包在div裡面,所以child為子元件。
希望有為你解惑~