组件组织
通常一个应用会以一棵嵌套的组件树的形式来组织。
日常开发里,我们可以按照功能将一个网页拆分成很多个部分,每个部分就是一个组件。
例如,可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的子组件。
父子组件通信
父子组件传值通信
假设第二层组件想和第一层组件进行通信,分析其传值过程
1、(第一层向第二层传值):父组件通过props传值到子组件,如此便实现父子组件向下通信
2、(第二层向第一层传值):子组件通过触发自定义事件向父组件传值,如此便实现了父子组件向上通信
跨级组件通信
跨级组件传值通信
假设第三层组件想和第一层组件进行通信
1、(第一层组件向第三层组件传值)—禁止第一层直接传值到第三层,需要逐层传递,即1→2→3,此时便可以实现跨级组件向下通信
2、(第三层组件向第一层组件传值)—通过触发自定义事件将数据传递至第二层,然后第二层通过事件触发传递至第一层,如此逐层传递,便可以实现跨级组件向上通信
兄弟组件通信
兄弟组件传值通信
假设第三层组件想和同层的另一个组件进行通信。
如果还是逐层传递至第二层,再由第二层传递至第三层,此时代码量十分累赘复杂,这也不符合Vue的定义(Vue是一个轻量级的视图层框架)。
兄弟|非父子组件通信
引用官方文档:
因为基于组件树结构的事件流方式让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。这种事件方式确实不太好,我们也不希望在以后让开发者们太痛苦。
官方推荐的状态管理方案是 Vuex。不过如果项目不是很大,状态管理也没有很复杂的话,使用 Vuex 有种杀鸡用牛刀的感觉,当然,这也是要根据自己的需求来的。
本节介绍下另一种方法,即bus/总线/发布订阅模式/观察者模式来解决
文档术语:
通过使用事件中心,允许组件自由交流,这个集中式的事件中间件就是 Bus总线。
语法:
在组件中使用$emit, $on, $off 分别来分发、监听、取消监听事件
接下来依次做下介绍
首先定义两个兄弟组件,如下所示
<div id="demo">
<my-btn :count="6"></my-btn>
<my-btn :count="2"></my-btn>
</div>
<script type="text/javascript">
Vue.component('my-btn',{
props:{
count:Number
},
template:`<button>按钮{{count}}</button>`
})
new Vue({
el:'#demo'
})
</script>
要求:点击一个组件,另一个兄弟组件也发生改动,两者数值相等
兄弟|非父子组件通信
兄弟组件通信
接下来结合案例,介绍下如何通过bus/总线/发布订阅模式/观察者模式来解决非父子组件通信。
第一步(注册全局bus总线):
创建一个Vue实例,将其赋值给Vue.prototype.bus
解析:在Vue的prototype类上挂载了一个bus属性,该属性指向Vue实例。只要在其后通过Vue实例创建组件,则每个组件都会有bus属性
第一步(注册全局bus):
写法2:Vue bus的使用(兄弟|非父子组件传值)–>可以使用一个空的Vue实例作为中央事件总线new Vue(),此外也可以写成$bus
有人可能会问“为什么加$,作用是什么”?
接下来简单介绍下$作用
$命名法则
简介:
$ 是在 Vue 所有实例中都可用的属性的一个简单约定。这样做会避免和已被定义的数据、方法、计算属性产生冲突,即避免命名冲突。接下来结合案例测试下
<div id="demo"></div>
<script type="text/javascript">
Vue.prototype.action='吃'
new Vue({
el:'#demo',
mounted(){
document.write(this)
document.write(this.action)
}
})
</script>
![
接下来,在data数据里声明个同名的变量,如下所示
<div id="demo"></div>
<script type="text/javascript">
Vue.prototype.action = '吃' ;
new Vue({
el:'#demo',
data:{
action:'睡'
},
mounted(){
document.write(this)
document.write(this.action)
}
})
</script>
修改代码如下所示
将Vue挂载实例命名前,加上前缀$,用于避免和已被定义的数据、方法、计算属性产生冲突,即避免命名冲突。
<div id="demo"></div>
<script type="text/javascript">
Vue.prototype.$action = '吃' ;
new Vue({
el:'#demo',
data:{
action:'睡'
},
created(){
document.write(this)
document.write(this.$action)
document.write(this.action)
}
})
</script>
兄弟|非父子组件通信
接下来继续介绍兄弟组件通信
再来梳理下需求,即当点击某个组件改变时,希望同时触发另外一个非父级组件改动,所以接下来需要在子组件绑定事件
<div id="app">
<my-btn :count="6"></my-btn>
<my-btn :count="2"></my-btn>
</div>
<script type="text/javascript">
Vue.prototype.$bus=new Vue();
Vue.component('my-btn',{
props:{
count:Number
},
template:`<button @click="handelClick">按钮{{count}}</button>`,
methods:{
handelClick(){
alert(this.count)
}
}
})
new Vue({
el:'#app'
})
</script>
兄弟|非父子组件通信
第二步:通过中央事件线程 b u s , 分 发 事 件 属 性 bus,分发事件属性 bus,分发事件属性emit
解析:
this.
b
u
s
为
中
央
事
件
总
线
程
n
e
w
V
u
e
实
例
,
通
过
bus为中央事件总线程new Vue实例,通过
bus为中央事件总线程newVue实例,通过emit来分发事件,分发事件时可以携带参数option。
子组件分发完毕后,其他组件需要进行监听接收分发的事件,此时可以借助生命周期钩子来实现。
第三步:
结合生命周期钩子监听和接收分发的事件,监听事件属性
o
n
。
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
20191030165453801.
p
n
g
)
!
[
在
这
里
插
入
图
片
描
述
]
(
h
t
t
p
s
:
/
/
i
m
g
−
b
l
o
g
.
c
s
d
n
i
m
g
.
c
n
/
2019103016551267.
p
n
g
?
x
−
o
s
s
−
p
r
o
c
e
s
s
=
i
m
a
g
e
/
w
a
t
e
r
m
a
r
k
,
t
y
p
e
Z
m
F
u
Z
3
p
o
Z
W
5
n
a
G
V
p
d
G
k
,
s
h
a
d
o
w
1
0
,
t
e
x
t
a
H
R
0
c
H
M
6
L
y
9
i
b
G
9
n
L
m
N
z
Z
G
4
u
b
m
V
0
L
3
d
l
a
X
h
p
b
l
80
N
T
Y
y
N
j
E
2
N
g
=
=
,
s
i
z
e
1
6
,
c
o
l
o
r
F
F
F
F
F
F
,
t
7
0
)
第
三
步
:
结
合
生
命
周
期
钩
子
监
听
和
接
收
分
发
的
事
件
,
监
听
事
件
属
性
on。   第三步: 结合生命周期钩子监听和接收分发的事件,监听事件属性
on。第三步:结合生命周期钩子监听和接收分发的事件,监听事件属性on。
接下来修改数据
第三步:
结合生命周期钩子监听和接收分发的事件,监听事件属性$on。
解决方案之前说过,利用子组件data,拷贝份父组件传递数据,后续即可维护自身数据,而不改动父组件数据
Vue.component('my-btn',{
data(){
return {
childCount:this.count
}
},
props:{
count:Number
},
template:`<button @click="handelClick">按钮{{childCount}}</button>`,
methods:{
handelClick(){
this.$bus.$emit('change',this.childCount+1);
}
},
mounted() {
var _this = this;
_this.$bus.$on('change',function(Option){
document.write(Option);
_this.childCount = Option
})
}
})
第四步:取消|清除监听事件,取消监听属性$off
日常开发里,为了避免不要的BUG,建议在使用bus时,加上清除监听操作,代码如下
如果需要监听多个组件,只需要更改 bus 的 eventName
步骤小结:
1、注册全局$bus
使用空Vue实例作为中央事件总线new Vue(),注册全局$bus
总线
2、分发$emit
:
通过中央事件线程$bus
,A组件,分发事件属性$emit
3、监听$on
:
结合生命周期钩子,B组件监听和接收分发的事件,监听事件属性$on
。
4、取消$off
:
取消|清除监听事件,取消监听属性$off
父子组件通信小结
引用官网的一句话:
父子组件的关系可以总结为 prop 向下传递,事件向上传递
父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息,如下图所示:
非父子组件通信小结
如果需要子对子传参,可以先从传到父组件,再传到子组件,但这样写过于累赘。
方案:
为了便于开发,vue 推出了一个状态管理工具 vuex,可以很方便实现组件之间的参数传递,后期结合工程化介绍。
官方推荐的状态管理方案是 Vuex。不过如果项目不是很大,状态管理也没有很复杂的话,使用 Vuex 有种杀鸡用牛刀的感觉。所以本节介绍下另一种方法,即bus/总线/发布订阅模式/观察者模式来解决
跨级组件通信
跨级通信方案
使用
a
t
t
r
s
和
attrs和
attrs和listeners实现祖孙组件之间的数据传递,也就是多重嵌套组件之间的数据传递
版本注意:
本方法针对vue2.4版本及以上,使用
a
t
t
r
s
和
attrs和
attrs和listeners来实现的
首先看下跨级组件通信自上而下步骤,案例如下
祖父级
父级
孙子级
祖父级调用
跨级组件通信
跨级组件通信自上而下
祖父级:用于动态数据的绑定与事件的定义
父级:通过设置v-bind="
a
t
t
r
s
"
和
v
−
o
n
=
"
attrs" 和v-on="
attrs"和v−on="listeners"来充当中间人
孙子级:通过
a
t
t
r
s
和
attrs和
attrs和listeners来获取数据和事件
一般来说,自上而下的通信会涉及到props与$attrs,而自下而上的通信,都是通过事件触发,然后层层上传。
跨级组件通信自下而上
方案一:
$emit逐层上传
孙子级
父级
祖父级
跨级组件通信自下而上
方案二:通过$listeners接收事件
孙子级
父级
祖父级
常见组件通信BUG
(1)bus.
o
n
监
听
多
次
绑
定
,
造
成
事
件
多
次
触
发
问
题
描
述
:
只
要
页
面
没
有
强
制
刷
新
,
存
在
组
件
切
换
,
b
u
s
.
on监听多次绑定,造成事件多次触发 问题描述:只要页面没有强制刷新,存在组件切换,bus.
on监听多次绑定,造成事件多次触发问题描述:只要页面没有强制刷新,存在组件切换,bus.on方法会被多次绑定,造成事件多次触发
方案:注册的总线事件(Bus)要在组件销毁时(beforeDestroy/destroyed)卸载,否则会多次挂载,造成触发一次但多个响应的情况
方案2:在每次调用方法前先解绑事件bus. o f f , 然 后 在 重 新 绑 定 b u s . off ,然后在重新绑定bus. off,然后在重新绑定bus.on ,该做法不常用,简单了解即可。
课堂总结
(1)数值传递大概可以分为两种,即父子传值与非父子传值
(2)非父子传值包含:跨级传值、兄弟传值
(3)父向子传递:props
(4)子向父传递:
e
m
i
t
(
5
)
兄
弟
之
间
传
递
:
借
助
中
间
代
理
,
即
中
央
事
件
总
线
emit (5)兄弟之间传递:借助中间代理,即中央事件总线
emit(5)兄弟之间传递:借助中间代理,即中央事件总线bus
使用$emit, $on, $off 分别来分发、监听、取消监听事件
(6)跨级通信:
a
t
t
r
s
可
以
获
取
父
作
用
域
传
入
的
值
(
不
包
括
p
r
o
p
s
中
的
)
,
attrs可以获取父作用域传入的值(不包括props中的),
attrs可以获取父作用域传入的值(不包括props中的),listeners相当于父作用域的事件监听器,那我们就可以用这两个属性实现祖孙之间的数据通信