本文深入解析Vue组件的核心机制,涵盖组件定义、渲染原理及作用域管理。重点讲解Props传值中的命名规范陷阱与插槽应用,并通过封装BootstrapTable和Select组件的完整案例,展示如何利用Vue Component特性提升前端开发效率与代码复用性。
引言与组件重要性
正文
前言:距离上一篇《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组件基础与原理
二、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实例内部使用,在实例外部调用组件无效。通过以下示例可清晰区分二者差异。
<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
插槽(Slot)的应用
(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>的模式来定义标签模板,使用更加灵活和方便。
实战:封装BootstrapTable与Select组件
三、封装自己的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的基础,组件的封装还得结合很多其他的东西,要读懂那些框架的源码还需要学习一些其他知识,但至少通过本文希望能够让你了解这些东西的由来。
以上所述是小编给大家介绍的JS组件系列之MVVM组件构建自己的Vue组件,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持!