初识组件

组件(Component)绝对是 Vue 最强大的功能之一。它可以扩展HTML元素,封装可复用代码。从较高层面讲,可以理解组件为自定义的HTML元素,Vue 的编译器为它添加了特殊强大的功能。所有的 Vue 组件同时也都是 Vue 的实例,因此可以接受相同的选项对象(除了一些特有的选项)并提供相同的生命周期函数。

再来回顾下 你也许不知道的Vuejs - 花式渲染目标元素 中的代码:

1
2
3
<div id="app1">
<helloworld/>
</div>
1
2
3
4
5
6
7
8
9
10
11
Vue.component("helloworld", {
template: "<h1>{{ msg }}</h1>",
data () {
return {
msg: "Hello Vue.js!"
}
}
})
var app1 = new Vue({
el: "#app1"
})

上面通过 Vue.component 注册了一个全局组件,然后在 div#app 元素内通过 <helloworld/> 标签直接使用。可以看出,这里就是相当于自定义了一个 HTML 元素 helloworld,它的功能就是输出一个内容为 msg 的 h1 标签。这就是一个基本全局组件的定义方式,当然你也可以注册为局部组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
var app2 = new Vue({
el: "#app2",
components: {
'helloworld': {
template: "<h1>{{ msg }}</h1>",
data () {
return {
msg: "Hello Vue.js!"
}
}
}
}
})

无论是全局或者局部注册组件,它跟上一篇中的指令注册是非常相似的。局部注册组件就是在创建 Vue 实例的时候添加一个 components 对象属性,它的键值对就是一个自定义组件,键是组件名,值是创建组建的配置对象参数。当然也可以将组件定义放到单独的文件,然后通过引入的方式,然后添加到components属性中,这个在单文件组件中会具体讲到。

组件间通信

既然说到组件,就不得不说组件间通信了,实际开发中,我们经常需要在不同组件间传递/共享数据,所以实现组件间通信是非常重要的。

组件间关系可以总结为 父子组件 和 非父子组件,自然通信方式也就是这两种了。

父子组件间通信

如上图,在 Vue 中,父子组件的关系可以总结为 props向下传递,事件向上传递。也就是父组件通过 prop 给子组件下发数据,子组件通过 $emit 事件, 给父组件发送数据。先来看个例子:

1
2
3
4
<div id="app3">
当前输入内容:{{ text }}<br>
<com-input :text="text" v-on:change="handleChange"/>
</div>
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
Vue.component('com-input', {
props: {
text: {
type: String,
default: "请输入"
}
},
template: '<input v-on:change="handleChange" v-model="msg"/>',
data () {
return {
// 这里定义为 input 的 v-model绑定值
msg: this.text
}
},
methods: {
// 当input值变化时,执行函数,通过 $emit change 事件,
// 父级组件通过 v-on:change 来监听此事件,执行相关操作
handleChange (e) {
this.$emit('change', this.msg)
}
}
})
var app3 = new Vue({
el: "#app3",
data () {
return {
text: 'Hello Vue.js'
}
},
methods: {
handleChange (val) {
this.text = val
}
}
})

原理解析:上面的代码中,先通过 Vue.component 定义了 com-input 组件,给它添加了 props 属性,用来接收父级通过属性传递的属性数据 text,这里 text 是个对象,含有 type - 属性值类型 和 default - 默认值 两个属性。当然 props 也可以为所有从父级接受的属性数组,有关 props 基础知识请直接阅读 官方文档。然后将初始值赋值给了 data 中的 msg,该子组件的模板是个 input,通过 v-model 实现 input的值 和 msg 的双向绑定,当input值变化时,通过 this.$emit('change', this.msg),发出 change 事件,同时将当前值作为监听器回调参数,这样父级组件就可以通过 v-on:change 来监听此事件,获取修改后的值,执行相关操作了。

虽然这段代码同时实现了上述图片中的 父 -> 子 和 子 -> 父 通信流程,但是代码还是比较繁琐的,单纯实现单个数据的循环传递,就需要父子组件同时监听改变事件,执行监听回调函数,是不是太麻烦了。要是能直接修改 props 中的 text 值就好了,实践证明,这是不行的,因为直接修改,会报下面错误(注意只有引入 vue.js 文件才会出现,因为 vue.min.js 文件移除了 [Vue warn] 错误提示功能):

[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop’s value. Prop being mutated: “text”

这个问题,Vue 作者早就想到了,那就是使用 .sync 修饰符。早在 1.x 版本中此功能是一直存在的,但是作者认为它破坏了 单向数据流 的原则,所以 2.0 发布后,就移除了该修饰符,但是后来发现在实际开发中,有很多相关需求, 于是在 2.3.0+ 版本后,又重新引入了 .sync 修饰符,不过内部实现是跟 1.x 版本有区别的,它并没有破坏 单向数据流 原则,实际上内部就是帮我们实现了父级组件监听和修改相关属性值的操作。

使用 .sync 修改后的代码如下:

1
2
3
4
<div id="app4">
当前输入内容:{{ text }}<br>
<com-input2 v-bind:text.sync="text"/>
</div>
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
Vue.component('com-input2', {
props: {
text: {
type: String,
default: "请输入"
}
},
template: '<input v-on:change="handleChange" v-model="msg"/>',
data () {
return {
msg: this.text
}
},
methods: {
handleChange (e) {
this.$emit('update:text', this.msg)
}
}
})
var app4 = new Vue({
el: "#app4",
data () {
return {
text: 'Hello Vue.js'
}
}
})

这次我们只是将子组件的 $emit 事件名修改为 update:text,并删除了父级组件 v-on:change 监听和相关监听回调,并在模板中 v-bind:text 后面添加了 .sync 修饰符,这样就是实现了相同的功能,代码确实精简了很多。实际上 Vue 在编译含有 .sync 修饰符的 v-bind 指令时,会自动实现监听 update 事件的相关代码,也就是:

1
<com-input2 v-bind:text.sync="text"/>

会被扩展为:

1
<com-input2 v-bind:text="text" v-on:update="val => text = val"/>

注意:val => text = val 是箭头函数,关于箭头函数的介绍可以看这里:箭头函数

这样一解析就很好理解了,全部是我们上一节讲到的内容。

非父子组件间通信

如果是两个非父子组件,并且有共同的父级组件,那么它拆解为 子 -> 父 -> 子 的过程,这个就完全可以使用 父子组件间通信 方法实现。如果是多个组件或者不同父组件的组件间通信,这时我们可以借助创建空的 Vue 实例作为事件总线,通过 发布订阅模式 进行数据传递。 代码如下:

1
2
3
4
<div id="app5">
组件A: <com-a></com-a><br>
组件B: <com-b></com-b>
</div>
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
var bus = new Vue()
Vue.component('com-a', {
template: '<input v-on:change="handleChange" v-model="msg"/>',
data () {
return {
msg: 'Hello Vue.js'
}
},
methods: {
handleChange() {
bus.$emit('a-change', this.msg)
}
},
created () {
var me = this
bus.$on('b-change', function (msg) {
me.msg = msg
})
}
})
Vue.component('com-b', {
template: '<input v-on:change="handleChange" v-model="msg"/>',
data () {
return {
msg: 'Hello Vue.js'
}
},
methods: {
handleChange() {
bus.$emit('b-change', this.msg)
}
},
created () {
var me = this
bus.$on('a-change', function (msg) {
me.msg = msg
})
}
})
var app5 = new Vue({
el: '#app5'
})

熟悉 发布订阅模式 的同学,应该很容易理解上面这段代码,创建的全局空 Vue 实例 bus 就是用来充当中央事件总线,所有的事件都经过它来触发和传播。

思路解析:在组件 com-a 中,当 input 值发生改变时,通过 bus.$emit('a-change', this.msg) 来触发修改事件,并将其更新后的值做为参数传递,组件 com-b 通过 bus.$on('a-change', xxx) 来监听,进行值更新操作,组件 com-b 也是相同原理。

当然在复杂情况下,我们应该考虑使用专门的 状态管理模式,比如 vuex,这个将在后续的文章中讲到。

动态组件

Vue 中还提供了 component 元素,允许我们在实际开发中,通过修改其 is 属性值,来动态切换组件。这个在某些应用场景非常实用,笔者曾经有个需求就是,需要根据参数 type来绘制不同类型的图表,而我的所有图表类型都已经装成了一个独立的组件,所以我只需要依据此特性,通过参数 type 来动态修改元素 component 的属性 is 为对应的组件名称即可。

下面来看示例代码:

1
2
3
4
<div id="app6">
<button v-on:click="changeType">改变组件</button><br>
<component v-bind:is="currentComp"></component>
</div>
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
var app6 = new Vue({
el: '#app6',
data () {
return {
type: 'a'
}
},
computed: {
currentComp () {
return this.type === 'a' ? 'com-a' : 'com-b';
}
},
components: {
'com-a': {
template: '<h1>我是组件a</h1>'
},
'com-b': {
template: '<h1>我是组件b</h1>'
}
},
methods: {
changeType () {
this.type = this.type === 'a' ? 'b' : 'a';
}
}
})

运行上面代码,点击改变组件按钮,就可以轻松的实现组件 com-a 和 com-b 的动态切换了,是不是很酷?赶紧动手尝试下吧。

Vuejs - 组件式开发的更多相关文章

  1. Agile.Net 组件式开发平台 - 组件开发示例

    所谓组件式开发平台,它所有的功能模块都是以组件的形式扩展的,下面我来演示一个简单的组件开发例程. Agile.Net开发管理平台项目,已经托管在开源中国码云平台(http://git.oschina. ...

  2. Agile.Net 组件式开发平台 - 平台系统介绍

    平台介绍 Agile.Net 组件式开发平台是一款针对企业级产品的开发框架,平台架构基于SOA服务体系,多层组件式架构打造.平台提供企业应用开发所需的诸如ORM.IOC.WCF.EBS.SOA等分布式 ...

  3. 基于TypeScript的FineUIMvc组件式开发(开头篇)

    了解FineUIMvc的都知道,FineUIMvc中采用了大量的IFrame框架,对于IFrame的优缺点网上也有很多的讨论,这里我要说它的一个优点“有助于隔离代码逻辑”,这也是FineUIMvc官网 ...

  4. 组件式开发(Vue)

    什么是组件式开发: 组件式开发就是将单个组件组合起来,形成一个大的组件进行页面的开发完成 什么是复合型组件: 复合型组件就是将相同的功能写成一个公用的组件(单元组件),供其他组件使用,就类似于后台开发 ...

  5. PIE SDK组件式开发综合运用示例

    1. 功能概述 关于PIE SDK的功能开发,在我们的博客上已经分门别类的进行了展示,点击PIESat博客就可以访问,为了初学者入门,本章节将对从PIE SDK组件式二次开发如何搭建界面.如何综合开发 ...

  6. Agile.Net 组件式开发平台 - 驱动开发示例

    首先讲一下概念,此驱动非彼驱动.在Agle.Net中我们将组件规划成两种类型,一种是基于业务的窗体组件,一种是提供扩展功能的驱动组件. 打个比方例如一般系统中需要提供身份证读卡功能,然而市面上有很多种 ...

  7. Agile.Net 组件式开发平台 - 服务开发示例

    在上一篇文章中已经讲解了组件的开发,这篇文章讲解平台服务开发. Agile.Net开发管理平台项目,已经托管在开源中国码云平台(http://git.oschina.net) 登陆码云平台进入项目主页 ...

  8. 基于TypeScript的FineUIMvc组件式开发(概述)

    WebForm与Mvc 我简单说一下WebForm与Mvc,WebForm是微软很早就推出的一种WEB开发架构,微软对其进行了大量的封装,使开发人员可以像开发桌面程序一样去开发WEB程序,虽然开发效率 ...

  9. Agile.Net 组件式开发平台 - 内核管理组件

    敏捷开发体系   软件构件技术:所谓软件构件化,就是要让软件开发像机械制造工业一样,可以用各种标准和非标准的零件来进行组装.软件的构件化和集成技术的目标是:软件系统可以由不同厂商提供的,用不同语言开发 ...

随机推荐

  1. Java常用类之Math类

    Java 的常用类Math类: java.lang.Math 提供了系列的静态方法用于科学计算,其方法的参数和返回值类型一般为 double 类型. 如: 1. public static final ...

  2. 分页查询es时,返回的数据不是自己所期望的问题

    在进行es分页查询时,一般都是用sql语句转成es查询字符串,在项目中遇到过不少次返回的数据不是自己所期望的那样时,多半原因是自己的sql拼接的有问题. 解决办法:务必要保证自己的sql语句拼接正确.

  3. <Effective C++>读书摘要--Inheritance and Object-Oriented Design<一>

    1.Furthermore, I explain what the different features in C++ really mean — what you are really expres ...

  4. ejabberd学习2

    1.ejabberd监听多个端口 每个网络连接进来,ejabberd都会使用一个进程来负责这个连接的数据处理.原理跟Joe Armstrong的<Erlang程序设计>中的并行服务器一样, ...

  5. Expected Conditions的常用函数

    Expected Conditions的使用场景有两种  1.直接在断言中使用  2.与WebDriverWait配合使用,动态等待页面上元素出现或者消失 1. title_is: 判断当前页面的ti ...

  6. pyHeatMap生成热力图

    库链接:https://pypi.org/project/pyheatmap/ 现在的linux系统默认都是安装好的py环境,直接用pip进行热力库安装 pip install pyheatmap 或 ...

  7. DELPHI BOOKMARK使用

    关于书签(BookMark)操作:       书签操作主要用于在表中快速地定位记录指针,在应用程序中常常要保存记录指针所在的位置,在进行其他处理之后,希望能快速地返回到先前指针所在的位置,此时,使用 ...

  8. DELPHI Showmodal 模式窗体

    Showmodal 是个函数, Show 是个过程 1.     Showmodal: 概念 : 当你调用一个窗口用 SHOWMODAL 时 , 当这个窗口显示出来后 , 程序不会继续自己执行 , 而 ...

  9. FTP-成型版本

    1. 旧知识回顾-反射 hasattr(object, name) 说明:判断对象object是否包含名为name的属性(方法) 测试代码如下: class tt(object): def __ini ...

  10. hihoCoder#1838 : 鎕鎕鎕 贪心

    ---题面--- 题解: 神奇的贪心题,,,感觉每次做贪心题都无从下手... 我们首先按照a对所有卡片从小到大排序,然后从1开始,从连续的两张牌中取b最大的,最后一张单出来的也取了. 可以证明,这样的 ...