JS组件系列之MVVM组件构建自己的Vue组件
正文
前言:转眼距离上篇 JS组件系列――又一款MVVM组件:Vue(一:30分钟搞定前端增删改查) 已有好几个月了,今天打算将它捡起来,发现好久不用,Vue相关技术点都生疏不少。经过这几个月的时间,Vue的发展也是异常迅猛,不过这好像和博主都没什么太大的关系,博主还是老老实实研究自己的技术吧。技术之路还很长,且行且研究吧。
一、为什么组件很重要
前两天,看到一篇关于 汇总vue开源项目 的文章,资源非常丰富,不得不感叹开源社区的强大。随便点进去看了几个UI组件,基本都不是原生的html用法,如果你不懂Vue的组件相关概念,看到一些“稀奇古怪”的标签写法,可能会使用,但肯定无法理解为什么可以这么写。比如我们随便找了一个名叫IView的来看看:
<i-input type="text" :value.sync="formInline.user" placeholder="Username"> <Icon type="ios-person-outline" slot="prepend"></Icon> </i-input>
这样一段代码就能得到如下效果:
博主好奇心重,打算一探究竟,今天就和大家一起来看一看这些“古怪”写法的出处。希望通过本文,让你有一种“哦,原来是这样,不过如此嘛!”的感觉!
二、Vue里面的组件基础知识
1、组件的概念
官方定义:组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
博主理解:Vue里面的组件可以理解为通过对普通html标签的封装,得到一套独立而且可以通用的html标签,我们在页面里面使用这些标签传入相应的参数即可调用封装好的组件。通过下面这张图相信可以一目了然。
由普通的html标签form、input、button、label组成了一个新的元素集合,我们命名为i-form,这个i-form就是vue里面组件的概念。我们在页面里面使用<i-form></i-form>时,通过vue的组件渲染机制,在浏览器里面最终就可以显示成为普通的html标签form、input、button、label。
2、组件原理
通过上图我们知道,vue里面的组件实际上就是一些普通html元素的集合。那么,它是如何将这些自定义标签转换为普通html标签的呢?在介绍组件原理之前,还是先来看一个最简单的组件实例。
<div style="text-align:center;margin-top:200px;" id="app"> <!-- 3. 在Vue实例里面使用组件--> <b-component></b-component> </div> <script src="Content/vue/dist/vue.js"></script> <script type="text/javascript"> // 1.创建组件构造器 var myComponent = Vue.extend({ template: '<div id="bComponent">我是自定义组件的内容</div>' }); //2.注册组件到vue里面 Vue.component('b-component', myComponent) new Vue({ el: '#app', }); </script>
得到效果:
整个过程不难理解,主要分为三个大的步骤:
- 定义一个组件构造器,声明组件要渲染的html内容
- 将组件构造器注册到Vue的组件系统里面,使其成为Vue的一个组件,给组件取一个名称,比如b-component
- 在Vue的实例里面使用组件。因为上面两步定义了Vue的组件,既然是Vue的组件,那么要使用组件,首先得有一个Vue的实例,组件必须要在Vue的实例里面使用。
在网上找到一张图可以清晰地解释组件的整个渲染过程。
其实有时为了简便,我们常将1、2步合并,代码如下:
<div style="text-align:center;margin-top:200px;" id="app"> <!-- 2. 在Vue实例里面使用组件--> <b-component></b-component> </div> <script src="Content/vue/dist/vue.js"></script> <script type="text/javascript"> //1.创建组件构造器,注册组件到vue里面 Vue.component('b-component', { template: '<div id="bComponent">我是自定义组件的内容</div>' }) new Vue({ el: '#app', }); </script>
得到的结果和上述相同。
3、组件使用
上述解释了下组件的定义和原理,关于组件的简单实用,我们主要介绍以下几个方面。
(1)组件的作用域
这个应该不难理解,组件分为全局组件和局部组件,也就是说,你可以在页面上面定义一个全局组件,页面上面的任何Vue实例都可使用;而对于局部组件,是和具体的Vue实例相关的,只能在当前Vue实例里面使用组件。还有一点需要说明:组件必须在Vue的实例里面使用,在Vue实例之外使用组件无效。通过下面一个例子即可清晰说明它们的区别。
<body> <div style="text-align:center;margin-top:50px;" id="app"> <b-component></b-component> <b-component2></b-component2> </div> <div style="text-align:center;margin-top:50px;" id="app2"> <b-component></b-component> <b-component2></b-component2> </div> <b-component></b-component> <b-component2></b-component2> <script src="Content/vue/dist/vue.js"></script> <script type="text/javascript"> //定义组件 Vue.component('b-component', { template: '<div id="bComponent">我是全局组件,任何Vue实例都可使用</div>' }) new Vue({ el: '#app', components: { 'b-component2': { template: '<div id="bComponent">我是局部组件,只能在app这个div里面使用</div>' } } }); new Vue({ el: '#app2', }); </script> </body>
得到结果:
(2)组件的传值
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。这段话怎么理解呢?我们先来看几个例子。
静态Prop
我们先来看看下面的一段简单的代码
<body> <div style="text-align:center;margin-top:50px;" id="app"> <b-component componentmessage="你好"></b-component> </div> <script src="Content/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('b-component', { template: '<div>{{componentmessage}}</div>', props: ['componentmessage'], }) new Vue({ el: '#app' }); </script> </body>
通过在组件里面使用props属性,将外部的值传入组件模板。最终渲染到页面上面就得到“<div>你好</div>”这么一段html
动态Prop
在多数情况下,我们在使用Vue实例的时候,一般通过data属性传入模型,比如
new Vue({ el: '#app', data: { name: 'Jim', Age: '28' } });
这个时候,我们的name和age如何传到组件实例里面呢?
<body> <div style="text-align:center;margin-top:50px;" id="app"> <b-component v-bind:my-name="name" v-bind:my-age="Age"></b-component> </div> <script src="Content/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('b-component', { template: '<div>姓名:{{myName}},年龄:{{myAge}}</div>', props: ['myName', 'myAge'], }) new Vue({ el: '#app', data: { name: 'Jim', Age: '28' } }); </script> </body>
得到结果
需要说明几点:
在使用标签<b-component>的时候,通过v-bind命令,将Vue实例里面的name、Age属性以别名my-name、my-age的形式传入组件实例。
为什么my-name、my-age传到组件里面就变成了['myName', 'myAge']呢?这是因为在子组件中定义prop时,使用了camelCase命名法。由于HTML特性不区分大小写,camelCase的prop用于特性时,需要转为 kebab-case(短横线隔开)。
很多情况下,v-bind可以简写为冒号(:),所以上述代码也可以这么写: <b-component :my-name="name" :my-age="Age"></b-component>
。效果也是一样。
这里很恶心的还有一点,在Props里面定义的必须要使用所谓“驼峰式”的方式来定义变量,否则会因为一个变量名大小写搞死你。比如props:["myName"]
这样可以正确,但是如果props:["myName"]
这样的话就错误,使用myname取值会是undefined。博主第一次玩这个玩意找了好半天,新手一定注意,大坑,大坑,大坑!慎入!
在封装组件里面,props属性使用非常多,更多props用法可参见文档https://vuefe.cn/v2/guide/components.html#Prop
(3)组件的插槽
在使用组件的时候,我们经常需要在组件实例向组件模板传入html元素,这个时候我们就需要在组件的模板标签里面留一些占位符(俗称“坑”),然后在具体的组件实例里面传入标签来填“坑”,在Vue里面这些“坑”也叫插槽,使用<slot>来解决。对于开发人员来说,这个其实不陌生,从原来的母版页到现在的layout页面,基本都是使用的这种原理。
<body> <div style="text-align:center;margin-top:50px;" id="app"> <b-component> <h1 slot="header">这里可能是一个页面标题</h1> <h2 slot="content">姓名:{{name}},年龄:{{Age}}</h2> <h1 slot="footer">尾部</h1> </b-component> </div> <template id="slottest"> <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot name="content"></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </template> <script src="Content/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component('b-component', { template: '#slottest', }) new Vue({ el: '#app', data: { name: 'Jim', Age: '28' } }); </script> </body>
得到结果
上述代码应该不难理解,就是一个“挖坑”和“填坑”的过程。顺便要提一笔的是,Vue的组件支持使用<templete>的模式来定义标签模板,使用更加灵活和方便。
三、封装自己的Component
以上讲了这么多,都是关于Vue里面Component组件的一部分主要知识点,其他还有很多都没有展开说,因为这方面的文档也是相当丰富,园子里面keepfool的博文关于Vue组件的部分就介绍得非常详细,再者,Vue中文文档也是有很详细的用法说明。接下来,博主打算通过几个实例来说明使用组件给我们前端开发带来的好处。
1、使用Component封装bootstrapTable
对于项目里面的表格展示,可以基于Vue可以自己开发一套,但是说实话,这个工程量还是蛮大的,并且如果要做好,要兼容很多表格的功能,从零开始去重复造轮子实在是有点太耗时。博主项目里面大部分的表格用的bootstrapTable组件,于是博主一直在想能不能封装一套基于Vue的bootstrapTable的用法。网上也找不到类似的封装示例,大部分使用vue的框架都会自己去实现一套自己的表格样式。于是打算自己动手试试,正好也可以熟悉下component的用法。
首先新建一个js文件命名为vue.bootstrapTable.js。博主直接将代码贴出来,如果有不完善的地方,希望大家斧正。
(function ($) { //表格初始化的默认参数 var defaults = { method: 'get', toolbar: '#toolbar', striped: true, cache: false, pagination: true, }; //注册bootstrapTable组件 Vue.component('bootstrap-table', { template: '<table></table>', props: { 'tableParam': { type: Object } }, //组件渲染之前 created: function () { //debugger; }, //组件渲染之后 mounted: function () { debugger; var params = $.extend({}, defaults, this.tableParam || {}); this.bootstraptable = $(this.$el).bootstrapTable(params); } }); })(jQuery);
然后再界面上面
<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <link href="Content/bootstrap/css/bootstrap.css" rel="external nofollow" rel="external nofollow" rel="stylesheet" /> <link href="Content/bootstrap-table/bootstrap-table.css" rel="external nofollow" rel="external nofollow" rel="stylesheet" /> </head> <body> <div id="app"> <bootstrap-table :table-param="tableParam"></bootstrap-table> </div> <script src="Content/jquery-1.9.1.min.js"></script> <script src="Content/bootstrap/js/bootstrap.js"></script> <script src="Content/bootstrap-table/bootstrap-table.js"></script> <script src="Content/vue/dist/vue.js"></script> <script src="Content/vue-component/vue.bootstrapTable.js"></script> <script type="text/javascript"> var testData = [ { Name: 'Jim', Age: 30, Remark: '鸡母格林' }, { Name: 'Kate', Age: 28, Remark: '凯特' }, { Name: 'Lucy', Age: 20, Remark: '露西' }, { Name: 'Uncle Wang', Age: 45, Remark: '严厉的王老师' } ]; new Vue({ el: '#app', data: { tableParam: { data: testData, columns: [ { field: 'Name', title:'姓名' }, { field: 'Age', title: '年龄' }, { field: 'Remark', title: '备注' }] }, } }); </script> </body>
最后测试结果:
纵观这数十行代码,基本原来其实很简单,通过组件的props功能将<bootstrap-table>实例中的初始化参数传到组件模板里面,然后再组件加载完成之后初始化bootstrapTable,最后将bootstrapTable的实例给到组件,这样在就可以通过Vue的实例通过子组件调用到当前初始化的bootstrapTable对象。
2、封装select
关于select的封装,还是打算基于第三方组件来做。同样的,我们新建一个js文件,命名为vue.bootstrapSelect.js,其代码如下:
(function ($) { $("body").append('<template id="bootstrapSelect">' + '<select class="selectpicker" v-if="myMultiple" v-bind:data-live-search="mySearch" multiple>' + '<option v-for="item in myDatasource" v-bind:value="item.value">{{item.text}}</option>' +'</select>' + '<select class="selectpicker" v-else v-bind:data-live-search="mySearch">' + '<option v-for="item in myDatasource" v-bind:value="item.value">{{item.text}}</option>' +'</select>' + '</template>'); Vue.component('bootstrap-select', { template: '#bootstrapSelect', props: ['myDatasource', 'myMultiple', 'mySearch'], //组件渲染之前 created: function () { }, //组件渲染之后 mounted: function () { } }); })(jQuery);
页面使用
<html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> <link href="Content/bootstrap/css/bootstrap.css" rel="external nofollow" rel="external nofollow" rel="stylesheet" /> <link href="Content/bootstrap-table/bootstrap-table.css" rel="external nofollow" rel="external nofollow" rel="stylesheet" /> <link href="Content/bootstrap-select/css/bootstrap-select.css" rel="external nofollow" rel="stylesheet" /> </head> <body> <div id="app"> <bootstrap-select :my-datasource="selectOptions.data" :my-multiple="selectOptions.multiple" :my-search="selectOptions.search"> </bootstrap-select> </div> <script src="Content/jquery-1.9.1.min.js"></script> <script src="Content/bootstrap/js/bootstrap.js"></script> <script src="Content/bootstrap-table/bootstrap-table.js"></script> <script src="Content/bootstrap-select/js/bootstrap-select.js"></script> <script src="Content/bootstrap-select/js/i18n/defaults-zh_CN.js"></script> <script src="Content/vue/dist/vue.js"></script> <script src="Content/vue-component/vue.bootstrapSelect.js"></script> <script type="text/javascript"> $(function () { var vm = new Vue({ el: '#app', data: { selectOptions:{ multiple: false,//多选 search: true,//搜索 data: [ { text: "北京市", value: 1 }, { text: "上海市", value: 2 }, { text: "重庆市", value: 3 }, ] } }, }); }); </script> </body> </html>
得到效果:
然后可配置多选,将初始化参数multiple设置为true即可。
为什么模板里面会有两个select标签?原因就在于那个multiple,因为只要标签里面出现了multiple,select就自动多选,把multiple的值设置为任何属性都不好使,这不做了一个if判断,如果哪位有更好的方法,欢迎指出,不胜感激!
3、查看其他Vue框架源码
现在再来看文章的开头那段html
<i-input type="text" :value.sync="formInline.user" placeholder="Username"> <Icon type="ios-person-outline" slot="prepend"></Icon> </i-input>
结合Vue组件的文档,其实上述就是一个对input标签做的封装。
当然,以上只是component的基础,组件的封装还得结合很多其他的东西,要读懂那些框架的源码还需要学习一些其他知识,但至少通过本文希望能够让你了解这些东西的由来。