组件组织
通常一个应用会以一棵嵌套的组件树的形式来组织。
日常开发里,我们可以按照功能将一个网页拆分成很多个部分,每个部分就是一个组件。
例如,可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的子组件。
在实际开发中我们经常会遇到在一个自定义组件中要使用其他自定义组件,这就需要一个父子组件关系,进而引出互相通信的问题。
父子组件传值通信
假设第二层组件想和第一层组件进行通信,分析其传值过程
1、(第一层向第二层传值):父组件通过props传值到子组件,如此便实现父子组件向下通信
2、(第二层向第一层传值):子组件通过触发自定义事件$emit向父组件传值,如此便实现了父子组件向上通信
跨级组件传值通信
假设第三层组件想和第一层组件进行通信
1、(第一层组件向第三层组件传值)—禁止第一层直接传值到第三层,需要逐层传递,即1→2→3,此时便可以实现跨级组件向下通信
2、(第三层组件向第一层组件传值)—通过触发自定义事件将数据传递至第二层,然后第二层通过事件触发传递至第一层,如此逐层传递,便可以实现跨级组件向上通信
兄弟组件传值通信
假设第三层组件想和同层的另一个组件进行通信。
如果还是逐层传递至第二层,再由第二层传递至第三层,此时代码量十分累赘复杂,这也不符合Vue的定义(Vue是一个轻量级的视图层框架)。
引用官方文档:
因为基于组件树结构的事件流方式让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。这种事件方式确实不太好,我们也不希望在以后让开发者们太痛苦。
官方推荐的状态管理方案是 Vuex。不过如果项目不是很大,状态管理也没有很复杂的话,使用 Vuex 有种杀鸡用牛刀的感觉,当然,这也是要根据自己的需求来的。
本节介绍下另一种方法,即bus/总线/发布订阅模式/观察者模式来解决
文档术语:
通过使用事件中心,允许组件自由交流,这个集中式的事件中间件就是 Bus总线。
语法:
在组件中使用$emit, $on, $off 分别来分发、监听、取消监听事件
首先定义两个兄弟组件,如下所示
要求:点击一个组件,另一个兄弟组件也发生改动,两者数值相等
兄弟组件通信
接下来结合案例,介绍下如何通过bus/总线/发布订阅模式/观察者模式来解决非父子组件通信。
第一步(注册全局bus总线):
创建一个Vue实例,将其赋值给Vue.prototype.bus
在Vue的prototype类上挂载了一个bus属性,该属性指向Vue实例。只要在其后通过Vue实例创建组件,则每个组件都会有bus属性。
步骤
第一步(注册全局bus):
写法2:Vue bus的使用(兄弟|非父子组件传值)–>可以使用一个空的Vue实例作为中央事件总线new Vue(),此外也可以写成$bus
$ 是在 Vue 所有实例中都可用的属性的一个简单约定。这样做会避免和已被定义的数据、方法、计算属性产生冲突,即避免命名冲突。
二、在data数据里声明个同名的变量
因为 this.appName 在实例被创建之后被 data 覆写了。我们通过 $ 为实例属性设置作用域来避免这种事情发生。
作用:
另外还可以根据你的喜好使用自己的约定,诸如 $_appName 或 ΩappName,来避免和插件或未来的插件命名相冲突。
案例二
需求,当点击某个组件改变时,希望同时触发另外一个非父级组件改动,所以接下来需要在子组件绑定事件。
第二步:通过中央事件线程
b
u
s
,
分
发
事
件
属
性
bus,分发事件属性
bus,分发事件属性emit
this.
b
u
s
为
中
央
事
件
总
线
程
n
e
w
V
u
e
实
例
,
通
过
bus为中央事件总线程new Vue实例,通过
bus为中央事件总线程newVue实例,通过emit来分发事件,分发事件时可以携带参数option。
子组件分发完毕后,其他组件需要进行监听接收分发的事件,此时可以借助生命周期钩子来实现。
第三步:
结合生命周期钩子监听和接收分发的事件,监听事件属性$on。
接下来修改数据
此时点击按钮,可以实现修改另一个,但控制台报错。
原因:Vue的单向数据流,禁止子组件直接修改父组件传值
解决方案之前说过,利用子组件data,拷贝份父组件传递数据,后续即可维护自身数据,而不改动父组件数据
第四步:取消|清除监听事件,取消监听属性$off
日常开发里,为了避免不要的BUG,建议在使用bus时,加上清除监听操作,代码如下
如果需要监听多个组件,只需要更改 bus 的 eventName
步骤小结:
1、注册全局
b
u
s
使
用
空
V
u
e
实
例
作
为
中
央
事
件
总
线
n
e
w
V
u
e
(
)
,
注
册
全
局
bus 使用空Vue实例作为中央事件总线new Vue(),注册全局
bus使用空Vue实例作为中央事件总线newVue(),注册全局bus总线
2、分发
e
m
i
t
:
通
过
中
央
事
件
线
程
emit: 通过中央事件线程
emit:通过中央事件线程bus,A组件,分发事件属性
e
m
i
t
3
、
监
听
emit 3、监听
emit3、监听on:
结合生命周期钩子,B组件监听和接收分发的事件,监听事件属性
o
n
。
4
、
取
消
on。 4、取消
on。4、取消off:
取消|清除监听事件,取消监听属性$off
父子组件的关系可以总结为 prop 向下传递,事件向上传递
父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息,如下图所示:
跨级组件通信自上而下
祖父级:用于动态数据的绑定与事件的定义
父级:通过设置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,而自下而上的通信,都是通过事件触发,然后层层上传。
(1)bus.
o
n
监
听
多
次
绑
定
,
造
成
事
件
多
次
触
发
问
题
描
述
:
只
要
页
面
没
有
强
制
刷
新
,
存
在
组
件
切
换
,
b
u
s
.
on监听多次绑定,造成事件多次触发 问题描述:只要页面没有强制刷新,存在组件切换,bus.
on监听多次绑定,造成事件多次触发问题描述:只要页面没有强制刷新,存在组件切换,bus.on方法会被多次绑定,造成事件多次触发
方案:注册的总线事件(Bus)要在组件销毁时(beforeDestroy/destroyed)卸载,否则会多次挂载,造成触发一次但多个响应的情况
(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相当于父作用域的事件监听器,那我们就可以用这两个属性实现祖孙之间的数据通信
插槽概念在Vue中十分重要
在很多第三方的Vue模块或插件中都会大量使用插槽
作用:通过插槽slot可以更加方便的向子组件传递DOM元素,同时子组件使用插槽内容也十分简单,大大简化了代码,增加了代码可读性。
首先创建个基础组件,然后在页面调用显示
需求升级:
要求子组件中除了展示p标签的文章主体外,还要展示文章标题h2
这里注意:要求文章标题并不是子组件决定,而是由父组件传递过来的
父组件传递写法
父组件通过属性形式向子组件传值,即父传子借助props属性向下传值
分析:
解析后发现标题标签h2发生转义,即被转义为普通字符,没有被浏览器识别
如果不想被转义,接下来需要借助v-html即原始HTML指令编译代码
再次验证发现,此时便可以正常编译解析。
父组件传递写法缺点分析:
上述代码虽然可以解决问题,但缺点也很明显
缺点一:多余嵌套标签
文章标题h2的外部,多余了div嵌套标签
缺点二:模板代码多时不简洁,过于累赘,不方便阅读
目前为止,如果想通过父组件传值方式,直接在组件里显示标题标签h2还无法实现,必须在外面包裹嵌套div标签,且传递内容较多时,代码难以阅读。
综上所述,当子组件部分内容是通过父组件传递DOM进行显示时,可以不用上述这种父组件props传值的比较挫的语法,Vue中提供了一种新型语法:插槽slot。
默认插槽案例:
接下来以插槽形式介绍下如何在父组件向子组件优雅传递DOM结构
修改上述案例,将代码修改如下所示
命名由来:
这种语法看起来像是用子组件直接往里直接插DOM内容,所以称之为插槽。
小结:
1、插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性;
2、插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制
插槽分类有很多种,本节将一一介绍
(1)单个插槽/默认插槽
(2)具名插槽
(3)作用域插槽
(4)解构插槽
插槽分类一:单个插槽(备胎插槽)
出现缘由:
最初在 标签中的任何内容都被视为备用内容。
备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。
插槽分类一:单个插槽(备胎)
(1)宿主元素不为空时,显示宿主元素里内容,不显示备用内容
(2)宿主元素为空时,显示备用内容
插槽分类一:单个插槽(备胎)也叫—→默认插槽
插槽的作用就是组件外部取代码片段放到组件内部来
1、定义默认插槽通过slot组件定义,定义好了之后,就相当于一个坑
2、组件innerHTML位置以后不管有任何代码,都会被放进插槽那个坑里面去
官方文档描述:
Vue 实现了一套内容分发的 API,将 元素作为承载分发内容的出口
插槽内可以包含任何模板代码,包括 HTML模板代码,甚至可以是其它的组件。
通俗理解:
没有插槽的情况下在组件标签内写一些内容是不起任何作用的,当在组件中声明了插槽元素后,在组件元素内写的内容就会跑到它这里了,即插槽此时充当承载分发内容的出口!
首先看个案例,结合案例了解下具名插槽的概念
(1)在子组件中定义了三个slot标签,其中有两个分别添加了name属性header和footer。即通过给slot添加name属性,来指定当前slot的名字
具名插槽案例:
(2)第二步,多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot 特性的元素。
具名插槽小结
(1)具名插槽其实就是在父组件中添加一个 slot=‘自定义名字’ 的属性,然后在子组件中的 里面添加 name=‘自定义名字’ 即可
(2)如果父组件中有一部分没有添加 slot 属性,则此处就是默认的插槽,在子组件中的 直接就是使用的父组件的默认插槽部分
(3)如果没有默认插槽,这些找不到匹配的内容片段将被抛弃。
简介:
作用域插槽为Vue2.1.0版本新增,是一种特殊类型的插槽,用作一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
不过,我们可以在父组件中使用 slot-scope 特性从子组件获取数据。前提是需要在子组件中使用 :data=data 先传递 data 的数据。
作用域插槽案例
(1)传递数据:在子组件中使用 :data=data 先传递 data 的数据
(2)获取数据:在父组件中使用 slot-scope 特性从子组件获取数据
注意:
(1)在父级中,具有特殊特性 slot-scope 的 元素必须存在,表示它是作用域插槽的模板(在 2.5.0+,slot-scope 能被用在任意元素或组件中而不再局限于 )。
(2)slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 prop 对象
(3)在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样,接下来父组件中使用 slot-scope 特性从子组件获取数据
应用场景:
作用域插槽更典型的用例是在列表组件中,允许使用者自定义如何渲染列表的每一项,接下啦结合案例来介绍下
(1)定义基本组件并引用
(2)子组件要实现一个功能“循环显示一个列表”,所以首先需要引入数据,进行循环展示
(3)需求升级
blog子组件可能在很多地方调用,希望在不同地方调用blog组件时
但是:注意这里要求列表的循环和样式不是由子组件决定,而是外部决定的,修改代码如下
此时,blog组件循环的每一项怎么显示,由外部告知决定
即父组件调用子组件时给子组件传递对应模板,完整代码如下所示
(4)父组件修改模板
正如之前所说,blog组件循环的每一项怎么显示,由外部告知决定
即父组件调用子组件时给子组件传递对应模板,例如这里我想将模板改为h6,如下所示
此时,模板便会呈现出父组件传递的样式去显示
(5)条件判断渲染
到目前为止,便可以在元素上随便操作了
例如:当书本名字长度等于3的时候,在前面加个“你好”标志
解构插槽:
v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JS表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop
这样可以使模板更简洁,尤其是在该插槽提供了多个 prop 的时候
解构插槽prop 重命名
解构插槽同样开启了 prop 重命名等其它可能
例如将 data 重命名为 book:
作用域插槽slot-scrop新语法
版本:自 2.6.0 起有所更新,已废弃使用 slot-scope 语法,开始使用v-slot进行替代
因为Vue版本向前兼容,所以不管使用旧语法还是新语法,都会正常支持。
作用域插槽slot-scrop新语法
你甚至可以定义后备内容,用于插槽 prop 是 undefined 的情形
编写可复用组件注意事项:
在编写组件时,最好考虑好以后是否要进行复用。一次性组件间有紧密的耦合没关系,但是可复用组件应当定义一个清晰的公开接口,同时也不要对其使用的外层数据作出任何假设。
Vue 组件的 API 来自三部分——prop、事件和插槽:
1、Prop 允许外部环境传递数据给组件;
2、事件允许从组件内触发外部环境的副作用;
3、插槽允许外部环境将额外的内容组合在组件中。