组件是可复用的Vue实例,拥有属于自己的数据、模板、脚本和样式,可避免繁重的重复性开发。由于组件都是独立的,因此其内部代码不会影响其它组件,但可以包含其它组件,并且相互之间还能通信。
## 一、注册
  在使用组件之前,需要先将其注册,Vue提供了两种注册方式:全局注册和局部注册。
**1)全局注册**
  通过Vue.component()方法可注册全局的组件,它能接收两个参数,第一个是组件名称,第二个既可以是扩展过的构造器(即Vue.extend()的返回值),也可以是选项对象(会自动调用Vue.extend()),如下所示。
~~~js
Vue.component("btn-custom", Vue.extend({ })); //扩展过的构造器
Vue.component("btn-custom", { }); //选项对象
~~~
  组件的选项对象也会包含data、methods、计算属性和生命周期钩子等成员,但不包含挂载目标el选项,并且data选项也不再是一个对象而是一个函数,因为只有这样才能让每个实例维护各自的数据对象,互不影响。注意,只有在组件注册之后才能将其应用于其它Vue根实例的模板中,如下所示。
~~~html
<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结构如下所示。
~~~html
<div id="container">
<button>提交</button>
</div>
~~~
  组件的命名方式除了上面的连字符分隔式之外,还有另一种大驼峰式(例如BtnCustom)。当把组件引用至字符串模板中时,两种命名方式都是有效的;而当把组件直接应用到DOM模板中时(如下所示),就不能用大驼峰命名,因为标签会被自动转换成小写(即\<btncustom>),于是就找不到这个组件的定义,进而抛出错误。
~~~html
<div id="container">
<BtnCustom></BtnCustom>
</div>
~~~
**2)局部注册**
  Vue的局部注册需要分两步,首先通过创建选项对象的方式来定义组件,如下所示。
~~~js
var BtnCustom = {
data: function() {
return {
txt: "提交"
};
},
template: "<button>{{txt}}</button>"
};
~~~
  然后在Vue根实例的components选项中注册要使用的组件(如下所示),其中属性名就是模板中要使用的自定义元素名,属性值就是组件。
~~~js
var vm = new Vue({
el: "#container",
components: {
"btn-custom": BtnCustom
}
});
~~~
  注意,当构建一个组件时,其模板中必须包含一个根元素,之前的示例都只有一个元素。如果有多个元素,那么就得像下面这样用一个元素(\<div>)包裹其它元素(\<span>和\<button>)。
~~~js
var BtnCustom = {
template: `<div>
<span>按钮</span>
<button>提交</button>
</div>`
};
~~~
## 二、数据传递
  组件的props选项能接收从外部(可以是父组件)传递进来的数据,其值是一个由HTML特性组成的数组或对象,如下所示。
~~~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选项,例如传入一个数字,如下所示。
~~~html
<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组件是等价的。
~~~html
<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会抛出错误警告,避免改变父组件的状态。
~~~js
Vue.component("btn-custom", {
props: ["digit"],
created: function() {
this.digit = 2;
}
});
~~~
  很多需要改变props的情况,其实都能以另一种更合理的方式解决,例如将其保存到组件的data属性中或定义成一个计算属性等。
**3)校验特性**
  组件的props能以对象的形式指定值类型,其键是接收的特性名称,值是类型构造函数。这样既有助于阅读,也可以避免传递无效的值。在下面的示例中,指定了digit必须是数字,而number既可以是数字也可以是字符串。
~~~js
Vue.component("btn-custom", {
props: {
digit: Number,
number: [Number, String]
}
});
~~~
  除了Number和String之外,内置的构造函数还有Boolean、Array、Object、Date、Function和Symbol。不仅如此,还可以自定义构造函数,通过instanceof运算符来检查。在下面的示例中,验证man特性是否是通过new Person()创建的。
~~~js
Vue.component("btn-custom", {
props: {
man: People
}
});
function People(name) {
this.name = name;
}
~~~
  除了基础的类型检查之外,组件还允许自定义验证函数、添加必填标记和附带默认值,如下所示。
~~~js
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两个特性。
~~~html
<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”。
~~~html
<button type="submit" class="warning size"></button>
~~~
  如果不想让根元素继承特性,那么可以将组件的inheritAttrs选项设为false,但要注意,inheritAttrs不会影响class和style的传递。还是以btn-custom组件为例,props和template两个选项与之前相同。
~~~html
<btn-custom type="submit" class="size"></btn-custom>
<script>
Vue.component("btn-custom", {
inheritAttrs: false
});
</script>
~~~
  渲染出的\<button>元素如下所示,其中type的值未被替换,而class的值仍然是“warning size”。
~~~html
<button type="button" class="warning size"></button>
~~~
## 三、混入
  混入(mixin)是一种代码复用技术,一个混入对象可包含任意组件选项,并能将其与普通组件混合在一起。
**1)选项合并策略**
  当组件和混入对象包含同名选项时,这些选项将会通过2种策略进行合并。
  (1)当数据对象或值为对象的选项(例如methods、components等)发生冲突时,同名的属性将以组件的为准。如下代码所示,虽然混入对象Mixin的数据对象也包含name属性,但是依然会被btn-custom组件中的name属性所覆盖,并且它的getName()也会被替换。
~~~js
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”。
~~~js
var Mixin = {
created: function() {
console.log("mixin");
}
};
Vue.component("btn-custom", {
mixins: [Mixin],
created: function() {
console.log("component");
}
});
~~~
**2)全局混入**
  通过Vue.mixin()方法可注册全局的混入对象,如下所示。
~~~js
Vue.mixin({
created: function () {
console.log("global");
}
});
~~~
  全局混入会影响所有的Vue实例,包括自定义的组件或第三方组件,因此要谨慎使用。大部分情况下它只适合自定义的选项,在官方的代码风格指南中,为混入中的这些选项制订了专门的命名规范,即以“$\_”和自定义的命名空间为前缀(例如$\_namespace\_),从而避免与其它实例中的选项相冲突,下面是一个简单的示例。
~~~js
Vue.mixin({
$_namespace_getAge: function () {
return 28;
}
});
~~~
**3)自定义选项合并策略**
  除了预定义的合并策略之外,Vue还允许自定义合并策略,只需在Vue.config.optionMergeStrategies中添加一个包含合并逻辑的函数即可。
  下面是一个示例,首先在混入对象和组件中都声明了一个自定义的age选项;然后在Vue.config.optionMergeStrategies中添加一个同名的age()函数,并且需要在组件之前声明合并函数;最后在created钩子中调用实例属性$options,读取到的age值为28,符合age()函数中的合并规则。
~~~js
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; //28
},
age: 30
});
~~~
## **四、动态组件**
  Vue内置的\<component>元素可渲染一个元组件为动态组件,通过它的is特性来决定使用哪个组件。下面用一个例子来演示\<component>元素的用法,首先全局注册两个组件tab1和tab2;然后将它们合并成数组赋给vm实例的tabs属性,而另一个current属性记录了当前要渲染的组件,默认值为tab1;最后将该属性值传递给is特性,并在DOM模板中创建两个按钮,每个按钮都注册了点击事件,可更改要渲染的组件。
~~~html
<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>元素,就不会销毁失活的组件,从而提升渲染性能。
~~~html
<keep-alive>
<component :is="current"></component>
</keep-alive>
~~~
  注意,\<keep-alive>元素自身不会渲染成一个DOM元素,并且其可与任意元素配合,但子元素只能渲染一个。由此可知,\<keep-alive>元素内可包含条件指令(如下所示),但不能包含v-for指令。
~~~html
<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钩子。
~~~js
Vue.component("tab1", {
template: '<input type="text"/>',
activated: function() {
console.log("activated");
},
deactivated: function() {
console.log("deactivated");
}
});
~~~
*****
> 原文出处:
[博客园-Vue躬行记](https://www.cnblogs.com/strick/category/1512864.html)
[知乎专栏-Vue躬行记](https://zhuanlan.zhihu.com/pwvue)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020