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。