Vue躬行记(4)——组件
组件是可复用的Vue实例,拥有属于自己的数据、模板、脚本和样式,可避免繁重的重复性开发。由于组件都是独立的,因此其内部代码不会影响其它组件,但可以包含其它组件,并且相互之间还能通信。
一、注册
在使用组件之前,需要先将其注册,Vue提供了两种注册方式:全局注册和局部注册。
1)全局注册
通过Vue.component()方法可注册全局的组件,它能接收两个参数,第一个是组件名称,第二个既可以是扩展过的构造器(即Vue.extend()的返回值),也可以是选项对象(会自动调用Vue.extend()),如下所示。
Vue.component("btn-custom", Vue.extend({ })); //扩展过的构造器
Vue.component("btn-custom", { }); //选项对象
组件的选项对象也会包含data、methods、计算属性和生命周期钩子等成员,但不包含挂载目标el选项,并且data选项也不再是一个对象而是一个函数,因为只有这样才能让每个实例维护各自的数据对象,互不影响。注意,只有在组件注册之后才能将其应用于其它Vue根实例的模板中,如下所示。
<div id="container">
<btn-custom></btn-custom>
</div>
<script>
Vue.component("btn-custom", {
data: function() {
return {
txt: "提交"
};
},
template: '<button>{{txt}}</button>'
});
var vm = new Vue({
el: "#container"
});
</script>
渲染出的DOM结构如下所示。
<div id="container">
<button>提交</button>
</div>
组件的命名方式除了上面的连字符分隔式之外,还有另一种大驼峰式(例如BtnCustom)。当把组件引用至字符串模板中时,两种命名方式都是有效的;而当把组件直接应用到DOM模板中时(如下所示),就不能用大驼峰命名,因为标签会被自动转换成小写(即<btncustom>),于是就找不到这个组件的定义,进而抛出错误。
<div id="container">
<BtnCustom></BtnCustom>
</div>
2)局部注册
Vue的局部注册需要分两步,首先通过创建选项对象的方式来定义组件,如下所示。
var BtnCustom = {
data: function() {
return {
txt: "提交"
};
},
template: "<button>{{txt}}</button>"
};
然后在Vue根实例的components选项中注册要使用的组件(如下所示),其中属性名就是模板中要使用的自定义元素名,属性值就是组件。
var vm = new Vue({
el: "#container",
components: {
"btn-custom": BtnCustom
}
});
注意,当构建一个组件时,其模板中必须包含一个根元素,之前的示例都只有一个元素。如果有多个元素,那么就得像下面这样用一个元素(<div>)包裹其它元素(<span>和<button>)。
var BtnCustom = {
template: `<div>
<span>按钮</span>
<button>提交</button>
</div>`
};
二、数据传递
组件的props选项能接收从外部(可以是父组件)传递进来的数据,其值是一个由HTML特性组成的数组或对象,如下所示。
<btn-custom in-html="提交"></btn-custom>
<script>
Vue.component("btn-custom", {
props: ["inHtml"],
template: '<button>{{inHtml}}</button>'
});
</script>
由于HTML特性的名称大小写不敏感,因此浏览器会将所有大写字母自动转换成小写。这意味着如果在组件内为props选项添加驼峰式的特性(例如inHtml),那么在DOM模板中需要声明成等价的连字符分隔式的特性(例如in-html),否则在组件内将读取不到该特性。有一点要注意,在字符串模板中使用特性,两种命名方式都是有效的。
1)动态传值
特性值的类型除了上文的字符串之外,还可以通过v-bind指令动态的将任意类型传递给组件的props选项,例如传入一个数字,如下所示。
<btn-custom :digit="1"></btn-custom>
<script>
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
typeof this.digit; //"number"
}
});
</script>
在created钩子中调用typeof运算符计算this.digit,得到的值为“number”,说明数字传递成功。
如果要传递对象的所有属性,那么不必一个一个声明,只需要不定义v-bind的参数即可,如下所示,两个btn-custom组件是等价的。
<div id="container">
<btn-custom v-bind="obj"></btn-custom>
<!-- 相当于 -->
<btn-custom :id="obj.id" :name="obj.name"></btn-custom>
</div>
<script>
Vue.component("btn-custom", {
props: ["id"],
template: '<button>{{id}}</button>'
});
var vm = new Vue({
el: "#container",
data: {
obj: { id: 1, name: "strick" }
}
});
</script>
注意,在props选项中声明的是id或name,而不是obj。
2)数据流
在Vue中,组件之间的数据是自顶向下单向流动的(即单向数据流),父组件通过props将数据传递给子组件。一旦父组件的数据有所更新,那么子组件也会自动更新,如果在子组件中修改接收的props(例如下面的digit特性),那么Vue会抛出错误警告,避免改变父组件的状态。
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
this.digit = 2;
}
});
很多需要改变props的情况,其实都能以另一种更合理的方式解决,例如将其保存到组件的data属性中或定义成一个计算属性等。
3)校验特性
组件的props能以对象的形式指定值类型,其键是接收的特性名称,值是类型构造函数。这样既有助于阅读,也可以避免传递无效的值。在下面的示例中,指定了digit必须是数字,而number既可以是数字也可以是字符串。
Vue.component("btn-custom", {
props: {
digit: Number,
number: [Number, String]
}
});
除了Number和String之外,内置的构造函数还有Boolean、Array、Object、Date、Function和Symbol。不仅如此,还可以自定义构造函数,通过instanceof运算符来检查。在下面的示例中,验证man特性是否是通过new Person()创建的。
Vue.component("btn-custom", {
props: {
man: People
}
});
function People(name) {
this.name = name;
}
除了基础的类型检查之外,组件还允许自定义验证函数、添加必填标记和附带默认值,如下所示。
Vue.component("btn-custom", {
props: {
digit: {
type: Number,
required: true //必填
},
number: {
type: Number,
default: 100 //数字默认值
},
people: {
type: Object,
default: function() { //对象默认值
return { name: "strick" };
}
},
name: {
validator: function(value) { //验证函数
return value.length > 5;
}
}
}
});
在使用这些校验规则时,有两点需要注意:
(1)当默认值是对象或数组时,需要从函数中获取。
(2)由于props会在组件实例创建之前进行验证,因此在default()和validator()函数中不能使用组件的属性,例如data、computed、methods等。
4)未在props中的特性
组件可以声明任意多个特性,而那些没有在props中定义的特性不但会被保存到实例属性$attrs中,还会被添加到根元素上。注意,class和style两个特性未包含在$attrs属性中,并且它们会与原特性进行合并,而不是替换。以下面的btn-custom组件为例,根元素<button>会接收type和class两个特性。
<btn-custom type="submit" class="size"></btn-custom>
<script>
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
console.log(this.$attrs); //{type: "submit"}
},
template: '<button type="button" class="warning">{{digit}}</button>'
});
</script>
渲染出的<button>元素如下所示,其中type的值被替换成了“submit”,而class的值变成了“warning size”。
<button type="submit" class="warning size"></button>
如果不想让根元素继承特性,那么可以将组件的inheritAttrs选项设为false,但要注意,inheritAttrs不会影响class和style的传递。还是以btn-custom组件为例,props和template两个选项与之前相同。
<btn-custom type="submit" class="size"></btn-custom>
<script>
Vue.component("btn-custom", {
inheritAttrs: false
});
</script>
渲染出的<button>元素如下所示,其中type的值未被替换,而class的值仍然是“warning size”。
<button type="button" class="warning size"></button>
三、混入
混入(mixin)是一种代码复用技术,一个混入对象可包含任意组件选项,并能将其与普通组件混合在一起。
1)选项合并策略
当组件和混入对象包含同名选项时,这些选项将会通过2种策略进行合并。
(1)当数据对象或值为对象的选项(例如methods、components等)发生冲突时,同名的属性将以组件的为准。如下代码所示,虽然混入对象Mixin的数据对象也包含name属性,但是依然会被btn-custom组件中的name属性所覆盖,并且它的getName()也会被替换。
var Mixin = {
data: function() {
return { name: "strick" };
},
methods: {
getName: function() {
console.log("mixin");
}
}
};
Vue.component("btn-custom", {
mixins: [Mixin],
data: function() {
return { name: "freedom" };
},
methods: {
getName: function() {
console.log("component");
}
}
});
(2)当生命周期钩子发生冲突时,同名的钩子将合并成一个数组,混入对象的钩子在前,组件的钩子在后,如下所示,先输出“mixin”,再输出“component”。
var Mixin = {
created: function() {
console.log("mixin");
}
};
Vue.component("btn-custom", {
mixins: [Mixin],
created: function() {
console.log("component");
}
});
2)全局混入
通过Vue.mixin()方法可注册全局的混入对象,如下所示。
Vue.mixin({
created: function () {
console.log("global");
}
});
全局混入会影响所有的Vue实例,包括自定义的组件或第三方组件,因此要谨慎使用。大部分情况下它只适合自定义的选项,在官方的代码风格指南中,为混入中的这些选项制订了专门的命名规范,即以“$_”和自定义的命名空间为前缀(例如$_namespace_),从而避免与其它实例中的选项相冲突,下面是一个简单的示例。
Vue.mixin({
$_namespace_getAge: function () {
return 28;
}
});
3)自定义选项合并策略
除了预定义的合并策略之外,Vue还允许自定义合并策略,只需在Vue.config.optionMergeStrategies中添加一个包含合并逻辑的函数即可。
下面是一个示例,首先在混入对象和组件中都声明了一个自定义的age选项;然后在Vue.config.optionMergeStrategies中添加一个同名的age()函数,并且需要在组件之前声明合并函数;最后在created钩子中调用实例属性$options,读取到的age值为28,符合age()函数中的合并规则。
var Mixin = {
age: 28
};
Vue.config.optionMergeStrategies.age = function(toVal, fromVal) {
return fromVal > toVal ? toVal : fromVal;
};
Vue.component("btn-custom", {
mixins: [Mixin],
created: function() {
this.$options.age; //
},
age: 30
});
四、动态组件
Vue内置的<component>元素可渲染一个元组件为动态组件,通过它的is特性来决定使用哪个组件。下面用一个例子来演示<component>元素的用法,首先全局注册两个组件tab1和tab2;然后将它们合并成数组赋给vm实例的tabs属性,而另一个current属性记录了当前要渲染的组件,默认值为tab1;最后将该属性值传递给is特性,并在DOM模板中创建两个按钮,每个按钮都注册了点击事件,可更改要渲染的组件。
<div id="container">
<button v-for="tab in tabs" @click="current = tab">{{ tab }}</button>
<component :is="current"></component>
</div>
<script>
Vue.component("tab1", {
template: '<input type="text"/>'
});
Vue.component("tab2", {
template: '<input type="text"/>'
});
var vm = new Vue({
el: "#container",
data: {
current: "tab1",
tabs: ["tab1", "tab2"]
}
});
</script>
1)<keep-alive>
虽然可以动态切换组件,但是组件的状态无法保持,例如在tab1组件的文本框中输入字符,来回切换后,这些字符就消失了。如果要缓存组件的状态,那么可以用Vue提供的另一个内置的<keep-alive>元素,如下所示,用它来包裹<component>元素,就不会销毁失活的组件,从而提升渲染性能。
<keep-alive>
<component :is="current"></component>
</keep-alive>
注意,<keep-alive>元素自身不会渲染成一个DOM元素,并且其可与任意元素配合,但子元素只能渲染一个。由此可知,<keep-alive>元素内可包含条件指令(如下所示),但不能包含v-for指令。
<keep-alive>
<tab1 v-if="current == 'tab1'"></tab1>
<tab2 v-else></tab2>
<keep-alive>
有两个与<keep-alive>元素相关的生命周期钩子:activated和deactivated。以之前的tab1组件为例,为其添加这两个钩子(如下代码所示),它被包裹在<keep-alive>元素中。当激活tab1组件时,会触发activated钩子;而当停用tab1组件时,会触发deactivated钩子。
Vue.component("tab1", {
template: '<input type="text"/>',
activated: function() {
console.log("activated");
},
deactivated: function() {
console.log("deactivated");
}
});
Vue躬行记(4)——组件的更多相关文章
- Vue躬行记(5)——组件通信
组件之间除了保持独立之外,还需要相互通信,本章将介绍几种通信的方式. 一.直接访问 Vue提供了三个实例属性,可直接访问父组件.子组件和根实例,如下所列. (1)$parent:父组件. (2)$ro ...
- Vue躬行记(1)——数据绑定
Vue.js的核心是通过基于HTML的模板语法声明式地将数据绑定到DOM结构中,即通过模板将数据显示在页面上,如下所示. <div id="container">{{c ...
- Vue躬行记(2)——指令
Vue不仅内置了各类指令,包括条件渲染.事件处理等,还能注册自定义指令. 一.条件渲染 条件渲染的指令包括v-if.v-else.v-else-if和v-show. 1)v-if 该指令的功能和条件语 ...
- Vue躬行记(3)——样式和表单
Vue对DOM元素的class和style两个特性做了专门的增强,即对CSS类和内联样式做了一层封装,通过v-bind指令来处理它们,而接收的表达式既可以是简单的字符串.对象或数组,也可以是复杂的计算 ...
- Vue躬行记(6)——内容分发
Vue提供了一种内容分发技术,可将父组件中的内容传递给子组件的模板,实现方式参照了Web组件规范草案. 一.插槽 Vue内置了一个<slot>元素,能作为插槽(slot)存在,而插槽内可包 ...
- Vue躬行记(7)——渲染函数和JSX
除了可通过模板创建HTML之外,Vue还提供了渲染函数和JSX,前者的编码自由度很高,后者对于开发过React的人来说会很熟悉.注意,Vue的模板最终都会被编译成渲染函数. 一.渲染函数 虽然在大部分 ...
- Vue躬行记(8)——Vue Router
虽然Vue.js未提供路由功能,但是官方推出了Vue Router(即vue-router库),以插件的形式支持.它与Vue.js深度集成,可快速的创建单页应用(Single Page Applica ...
- Vue躬行记(9)——Vuex
Vuex是一个专为Vue.js设计的状态管理库,适用于多组件共享状态的场景.Vuex能集中式的存储和维护所有组件的状态,并提供相关规则保证状态的独立性.正确性和可预测性,这不仅让调试变得可追踪,还让代 ...
- ES6躬行记(1)——let和const
古语云:“纸上得来终觉浅,绝知此事要躬行”.的确,不管看了多少本书,如果自己不实践,那么就很难领会其中的精髓.自己研读过许多ES6相关的书籍和资料,平时工作中也会用到,但在用到时经常需要上搜索引擎中查 ...
随机推荐
- 安装MariaDB
1.安装MariaDB安装命令yum -y install mariadb mariadb-server安装完成MariaDB,首先启动MariaDBsystemctl start mariadb设置 ...
- 苹果系统IOS第三方管理工具——imazing 优秀
iMazing 是一款 Windows.macOS 平台的 iPhone.iPad 管理工具,可以进行文件.音乐.视频传输,备份与还原数据,并且可以管理已安装应用,比如重新安装那些已下架的应用,是「史 ...
- java数据结构——递归(Recursion)例题持续更新中
继续学习数据结构递归,什么是递归呢?字面理解就是先递出去,然后回归,递归核心思想就是直接或间接调用本身,好比从前有座山,山里有位老和尚,在给小和尚讲故事,讲的是从前有座山,山里有位老和尚,在给小和尚讲 ...
- 4款黑科技级别的宝藏APP,能够轻松满足你的多种需求,请低调收藏
有没有这样几款软件,在你每次一换新手机的时候就会立刻重新安装下来,感觉自己已经完全离不开它们?今天就来给大家分享几个非常好用的APP. 一.小羊搜搜 在生活中人人都有自己的爱好,无论你是喜欢影视.小说 ...
- 23种设计模式之单例(Singleton Pattern)
单例 在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例(eg:应对一些特殊情况,比如数据库连接池(内置了资源) 全局唯一号码生成器),才能确保它们的逻辑正确性.以及良好的效率 ...
- springboot 集成swagger2
使用Swagger 可以动态生成Api接口文档,在项目开发过程中可以帮助前端开发同事减少和后端同事的沟通成本,而是直接参照生成的API接口文档进行开发,提高了开发效率.这里以springboot(版本 ...
- 使用apache的poi来实现数据导出到excel的功能——方式二
此次,介绍利用poi与layui table结合导出excel.这次不需要从数据库中查询出来的数据进行每一行的拼接那么麻烦,我们这次将标题定义一个id值,对应从数据库中查找出来的字段名即可. 1.po ...
- 【IT技术概念】什么是webservice?
WebService是一个SOA(面向服务的编程)的架构,它是不依赖于语言,不依赖于平台,可以实现不同的语言间的相互调用,通过Internet进行基于Http协议的网络应用间的交互. WebServi ...
- jQuery常用方法(六)-jQuery 工具
JQuery Utilities 方法说明 jQuery.browser .msie 表示ie jQuery.browser.version 读取用户浏览器的版本信息 jQuery.boxModel ...
- SOFAJRaft—初次使用
SOFAJRaft-初次使用 SOFAJRaft 是基于 Raft 算法的生产级高性能 Java 实现,支持 MULTI-RAFT-GROUP.应用场景有 Leader 选举.分布式锁服务.高可靠的元 ...