Props down, Events up 是指 使用props向子組件傳遞數據,父組件屬性發生變化時,子組件可實時更新視圖;子組件發生變化,可使用$emit發送事件消息,以此向父組件傳遞變化消息。javascript
props 是單向的,當父組件的屬性變化時,將傳遞給子組件,但子組件中的props屬性變化不會影響父組件的屬性變化(props屬性類型是Object除外)。假若使用vue1.0的.sync強制雙向,那麼子組件的更改將會影響到父組件的狀態,隨着業務的增多,很容易讓數據流變得難以理解,最終陷入痛苦的泥潭。所以,vue2.0已經剔除.sync,且不容許在子組件中更改自身的props屬性。若是真的須要更改props,那必定是設計方案出了問題,請使用替代方案,如:在data選項或computed選項中再定義一個不一樣的變量進行數據轉換。這是props down。vue
既然父組件能夠經過props像子組件傳遞信息了,那子組件的數據變化如何通知到父組件呢?java
$emit的出現便解決了這一問題,該方法用於 子組件向父組件發送事件消息,可帶上子組件的數據信息一塊兒發送,父組件接收到消息後,作出自身相應的變動。vue1.0 和vue2.0均支持$emit。這是events up。git
如示例代碼1,父組件經過 age(props) 向子組件傳遞數據信息,子組件拿到後,經過$emit向父組件傳遞最新狀態。若是子組件涉及到可能會對age進行更改,則從新定義一個變量age$進行中轉。github
【示例代碼1】vuex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
<body>
<div id=
"app"
>
<p>parent age: {{age}}</p>
<p><button @click=
"changeAge"
>changeAge to 333</button></p>
<hr>
<child :age=
"age"
@on-age-change=
"onAgeChange"
></child>
</div>
<script>
Vue.component(
'child'
, {
template:
'<div><p>child age (props選項): {{age}}</p> child age$ (data選項): {{age}}</p> <button @click="changeAge(222)">changeAge to 222</button></div>'
,
props: {
age: Number
},
data () {
return
{
age$:
this
.age
}
},
methods: {
changeAge (age) {
this
.age$ = age
this
.$emit(
'on-age-change'
,
this
.age$)
}
}
});
new
Vue({
el:
'#app'
,
data: {
age: 111
},
methods: {
onAgeChange (val) {
this
.age = val
},
changeAge () {
this
.age = 333
}
}
})
</script>
</body>
|
在複雜邏輯組件中,必定不要使用.sync,很容易在N個組件中繞暈。如圖, A 是 BC 的父組件,AB和AC都雙向了一個共同的props屬性(如:model.sync)。B中的Model變化除了影響父組件A,A的變化進而還會影響組件C,這時C就要爆炸了,這Model變化到底來自A,仍是來自B。如此,Model的變化變得很難跟蹤,增大維護成本。若是B或C還watch model的話,啊呵,足以毀掉你一天的心情了。app
父子組件直接雙向綁定是個隱式毒蟲,但對於某些基礎組件來講倒是隻有益的蜜蜂,能夠省掉很多麻煩。一些簡單基礎的組件,或不須要關心數據流的地方 使用.sync 或 v-model就會是代碼顯得簡潔,且一目瞭然。組件化
示例代碼2 和 示例代碼3 效果圖: this
vue1.0修飾符.sync能夠強制props屬性雙向綁定,如示例代碼2,checked爲雙向綁定,能夠輕鬆完成radio單選組件。spa
vue2.0中對prop屬性進行直接賦值更改會拋錯,但若是prop屬性類型爲object時,僅僅添加或更改props屬性內部的屬性不會拋錯。因爲此特性,vue2.0不支持.sync修飾符。
【示例代碼2】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
<body>
<div id=
"app"
>
<radio v-
for
=
"item in data"
:value=
"item.value"
:checked.sync=
"checked"
>{{item.text}}</radio>
</div>
<script>
Vue.component(
'radio'
, {
template:
'<label><input type="radio" :value="value" v-model="checked"><slot></slot></label>'
,
props: {
value: {},
checked: {}
}
});
new
Vue({
el:
'#app'
,
data: {
checked:
''
,
data: [
{text:
'2G'
, value:
'2G'
},
{text:
'3G'
, value:
'3G'
},
{text:
'4G'
, value:
'4G'
}
]
}
})
</script>
</body>
|
vue2.0雖然已經棄用.sync,但有語法糖v-model,其 實質 就是 props down, events up,只是由於v-model隱藏了數據流,所以喚其爲雙向綁定。 父組件向下使用props隱式屬性value將數據傳遞數據給子組件,子組件使用$emit('input')向上將數據返回給父組件。但正如上文所說,對於基礎組件或不關心數據流的組件使用雙向綁定是糟粕中的小蜂蜜,書寫簡潔,清晰明瞭。
【示例代碼3】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<body>
<div id=
"app"
>
<radio v-
for
=
"item in data"
:label=
"item.value"
v-model=
"checked"
>{{item.text}}</radio>
</div>
<script>
Vue.component(
'radio'
, {
template:
'<label><input type="radio" :value="label" v-model="checked"><slot></slot></label>'
,
props: {
label: {},
value: {}
},
computed: {
checked: {
get () {
return
this
.value
},
set (val) {
this
.$emit(
'input'
, val)
}
}
}
});
new
Vue({
el:
'#app'
,
data: {
checked:
''
,
data: [
{text:
'2G'
, value:
'2G'
},
{text:
'3G'
, value:
'3G'
},
{text:
'4G'
, value:
'4G'
}
]
}
})
</script>
</body>
|
拆解語法糖v-model,如示例代碼4。已熟知的朋友能夠略過。
代碼第4行 <child :value="info" @input="dataChange"> </child>更爲 <child v-model="info"></child> 效果同樣。
【示例代碼4】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
<body>
<div id=
"app"
>
<p>parent info: {{info}}</p>
<child :value=
"info"
@input=
"dataChange"
></child>
<p><button @click=
"changeInfo"
>changeInfo from parent</button></p>
</div>
<script>
Vue.component(
'child'
, {
template:
'<div><p>child data: {{data}}</p> <button @click="changeData">changeData from child</button></div>'
,
props: {
value: {}
},
computed: {
data: {
get () {
return
this
.value
},
set (val) {
this
.$emit(
'input'
, val)
}
}
},
methods: {
changeData () {
this
.data =
'This is a child component!'
}
}
});
new
Vue({
el:
'#app'
,
data: {
info:
'This is a original component!'
},
methods: {
dataChange (info) {
this
.info = info
},
changeInfo () {
this
.info =
'This is a parent component!'
}
}
})
</script>
</body>
|
只有vue1.0中才有這兩種方法。
$dispatch首先會觸發自身實例,冒泡向上,直到某個父組件收到$dispatch消息。若是子組件內部使用了$dispatch,那麼該組件的 父組件鏈在寫監聽事件時都必須格外當心,必須得有父組件截獲該消息,不然會一直冒泡。這是一項很是危險的行爲,由於,父組件鏈中的組件 很難關注到全部子組件的dispatch消息,隨着$dispatch在組件中增多,頂層組件或中間組件想知道消息來自哪一個子組件變得異常艱辛,事件流跟蹤困難,痛苦深淵由此開啓。
$broadcast會向每個子樹路徑發送消息,一條路徑某個組件接收消息,則該路徑中止向下發送消息,其它路徑規則同樣。同理$dispatch,隨着通訊的增長,消息的增多,子組件也將很難跟蹤監聽的消息到底來自哪一個父組件。不注意的話,最後上演一場的 尋親記,想來也是持久精彩的。
$dispatch 和 $broadcast 都已在vue2.0中被棄用,若是實在想堅持使用,也可經過$emit進行模擬,經過apply或call改變this,遞歸便利。
適合複雜數據流管理。詳細使用方法見以下站點,
建立一個單獨的空Vue實例(Events = new Vue()),做爲事件收發器,在該實例上進行$emit, $on, $off等操做。適用場景:a. 使用Events.$on的組件不關心事件具體來源; b. 事件處理程序 執行與否 或 重複執行 都沒有反作用(如刷新、查詢等操做)。如示例代碼5,
【示例代碼5】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<body>
<div id=
"app"
>
<h5>A組件</h5>
<a-component></a-component>
<h5>B組件</h5>
<b-component></b-component>
</div>
<script>
var
Events =
new
Vue()
Vue.component(
'AComponent'
, {
template:
'<button @click="changeBName">change B name</button>'
,
methods: {
changeBName () {
Events.$emit(
'on-name-change'
,
'The name is from A component!'
)
}
}
});
Vue.component(
'BComponent'
, {
template:
'<p>B name: {{name}}</p>'
,
data () {
return
{
name:
'sheep'
}
},
created () {
Events.$on(
'on-name-change'
, (name) => {
this
.name = name
})
}
});
new
Vue({
el:
'#app'
})
</script>
</body>
|
爲何說只適用簡單場景呢? vue組件化的開發模式意味着一個組件極可能被屢次實例化。請看下文分析,
假設 A組件使用Events.$emit('event'), 在同一個界面被實例化了兩次,如今的需求是,組件A實例1觸發消息'event'時,組件B根據消息'event'相應更新,組件A實例2觸發消息'event',組件B不能根據消息'event'進行相應的更新。這時,由於組件B使用的是Events.$on('event')就搞不清是由A組件實例1觸發的消息'event', 仍是A組件實例2觸發的。
所以,使用該方法時,最好保證組件在同一界面只會被渲染一次,或這不須要關心Events.$emit由哪一個實例觸發。
適用場景:列表中,須要更改某條數據的某項信息。當一個變量向另外一個變量複製引用類型的值時,複製的值其實是一個指針,指向存儲在堆中的同一個對象。所以,改變其中一個變量就會影響另外一個變量。 如,在一個表格列表中,如何在 N行X列 更改 N行Y列 的數據?
【示例代碼6】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
<body>
<div id=
"app"
>
<table>
<tr>
<th v-
for
=
"title in table.head"
>{{title}}</th>
</tr>
<tr v-
for
=
"item in table.data"
>
<td>{{item.name}}</td>
<td><status :value=
"item.status"
></status></td>
<td><t-
switch
:item=
"item"
>切換</t-
switch
></td>
</tr>
</table>
</div>
<script>
Vue.component(
'status'
, {
template:
'<span>{{value}}</span>'
,
props: {
value: {}
}
});
Vue.component(
't-switch'
, {
template:
'<button @click="switchStatus">切換狀態</button>'
,
props: {
item: {}
},
methods: {
switchStatus () {
this
.item.status =
this
.item.status ===
'有效'
?
'無效'
:
'有效'
}
}
});
new
Vue({
el:
'#app'
,
data: {
table: {
head: [
'廣告名稱'
,
'狀態'
,
'操做'
],
data: []
}
},
ready () {
var
timer = setTimeout(() => {
this
.table.data = [
{name:
'adName1'
, status:
'無效'
},
{name:
'adName2'
, status:
'有效'
},
{name:
'adName3'
, status:
'無效'
}
]
clearTimeout(timer)
}, 1000)
}
})
</script>
</body>
|
https://github.com/vuejs/vue-rx
https://github.com/Reactive-Extensions/RxJS
簡而言之,不管是vue1.0,仍是vue2.0,爲保證清晰的數據流 和 事件流,父子組件通訊遵循「props down, events up」的原則。非父子組件根據不一樣場景選擇不一樣的方案,大部分狀況依然建議使用vuex狀態管理方案。特別複雜的場景,建議使用vue-rx。