🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
&emsp;&emsp;组件是可复用的Vue实例,拥有属于自己的数据、模板、脚本和样式,可避免繁重的重复性开发。由于组件都是独立的,因此其内部代码不会影响其它组件,但可以包含其它组件,并且相互之间还能通信。 ## 一、注册 &emsp;&emsp;在使用组件之前,需要先将其注册,Vue提供了两种注册方式:全局注册和局部注册。 **1)全局注册** &emsp;&emsp;通过Vue.component()方法可注册全局的组件,它能接收两个参数,第一个是组件名称,第二个既可以是扩展过的构造器(即Vue.extend()的返回值),也可以是选项对象(会自动调用Vue.extend()),如下所示。 ~~~js Vue.component("btn-custom", Vue.extend({ })); //扩展过的构造器 Vue.component("btn-custom", { }); //选项对象 ~~~ &emsp;&emsp;组件的选项对象也会包含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> ~~~ &emsp;&emsp;渲染出的DOM结构如下所示。 ~~~html <div id="container"> <button>提交</button> </div> ~~~ &emsp;&emsp;组件的命名方式除了上面的连字符分隔式之外,还有另一种大驼峰式(例如BtnCustom)。当把组件引用至字符串模板中时,两种命名方式都是有效的;而当把组件直接应用到DOM模板中时(如下所示),就不能用大驼峰命名,因为标签会被自动转换成小写(即\<btncustom>),于是就找不到这个组件的定义,进而抛出错误。 ~~~html <div id="container"> <BtnCustom></BtnCustom> </div> ~~~ **2)局部注册** &emsp;&emsp;Vue的局部注册需要分两步,首先通过创建选项对象的方式来定义组件,如下所示。 ~~~js var BtnCustom = { data: function() { return { txt: "提交" }; }, template: "<button>{{txt}}</button>" }; ~~~ &emsp;&emsp;然后在Vue根实例的components选项中注册要使用的组件(如下所示),其中属性名就是模板中要使用的自定义元素名,属性值就是组件。 ~~~js var vm = new Vue({ el: "#container", components: { "btn-custom": BtnCustom } }); ~~~ &emsp;&emsp;注意,当构建一个组件时,其模板中必须包含一个根元素,之前的示例都只有一个元素。如果有多个元素,那么就得像下面这样用一个元素(\<div>)包裹其它元素(\<span>和\<button>)。 ~~~js var BtnCustom = { template: `<div> <span>按钮</span> <button>提交</button> </div>` }; ~~~ ## 二、数据传递 &emsp;&emsp;组件的props选项能接收从外部(可以是父组件)传递进来的数据,其值是一个由HTML特性组成的数组或对象,如下所示。 ~~~html <btn-custom in-html="提交"></btn-custom> <script> Vue.component("btn-custom", { props: ["inHtml"], template: '<button>{{inHtml}}</button>' }); </script> ~~~ &emsp;&emsp;由于HTML特性的名称大小写不敏感,因此浏览器会将所有大写字母自动转换成小写。这意味着如果在组件内为props选项添加驼峰式的特性(例如inHtml),那么在DOM模板中需要声明成等价的连字符分隔式的特性(例如in-html),否则在组件内将读取不到该特性。有一点要注意,在字符串模板中使用特性,两种命名方式都是有效的。 **1)动态传值** &emsp;&emsp;特性值的类型除了上文的字符串之外,还可以通过v-bind指令动态的将任意类型传递给组件的props选项,例如传入一个数字,如下所示。 ~~~html <btn-custom :digit="1"></btn-custom> <script> Vue.component("btn-custom", { props: ["digit"], created: function() { typeof this.digit; //"number" } }); </script> ~~~ &emsp;&emsp;在created钩子中调用typeof运算符计算this.digit,得到的值为“number”,说明数字传递成功。 &emsp;&emsp;如果要传递对象的所有属性,那么不必一个一个声明,只需要不定义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> ~~~ &emsp;&emsp;注意,在props选项中声明的是id或name,而不是obj。 **2)数据流** &emsp;&emsp;在Vue中,组件之间的数据是自顶向下单向流动的(即单向数据流),父组件通过props将数据传递给子组件。一旦父组件的数据有所更新,那么子组件也会自动更新,如果在子组件中修改接收的props(例如下面的digit特性),那么Vue会抛出错误警告,避免改变父组件的状态。 ~~~js Vue.component("btn-custom", { props: ["digit"], created: function() { this.digit = 2; } }); ~~~ &emsp;&emsp;很多需要改变props的情况,其实都能以另一种更合理的方式解决,例如将其保存到组件的data属性中或定义成一个计算属性等。 **3)校验特性** &emsp;&emsp;组件的props能以对象的形式指定值类型,其键是接收的特性名称,值是类型构造函数。这样既有助于阅读,也可以避免传递无效的值。在下面的示例中,指定了digit必须是数字,而number既可以是数字也可以是字符串。 ~~~js Vue.component("btn-custom", { props: { digit: Number, number: [Number, String] } }); ~~~ &emsp;&emsp;除了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; } ~~~ &emsp;&emsp;除了基础的类型检查之外,组件还允许自定义验证函数、添加必填标记和附带默认值,如下所示。 ~~~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; } } } }); ~~~ &emsp;&emsp;在使用这些校验规则时,有两点需要注意: &emsp;&emsp;(1)当默认值是对象或数组时,需要从函数中获取。 &emsp;&emsp;(2)由于props会在组件实例创建之前进行验证,因此在default()和validator()函数中不能使用组件的属性,例如data、computed、methods等。 **4)未在props中的特性** &emsp;&emsp;组件可以声明任意多个特性,而那些没有在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> ~~~ &emsp;&emsp;渲染出的\<button>元素如下所示,其中type的值被替换成了“submit”,而class的值变成了“warning size”。 ~~~html <button type="submit" class="warning size"></button> ~~~ &emsp;&emsp;如果不想让根元素继承特性,那么可以将组件的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> ~~~ &emsp;&emsp;渲染出的\<button>元素如下所示,其中type的值未被替换,而class的值仍然是“warning size”。 ~~~html <button type="button" class="warning size"></button> ~~~ ## 三、混入 &emsp;&emsp;混入(mixin)是一种代码复用技术,一个混入对象可包含任意组件选项,并能将其与普通组件混合在一起。 **1)选项合并策略** &emsp;&emsp;当组件和混入对象包含同名选项时,这些选项将会通过2种策略进行合并。 &emsp;&emsp;(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"); } } }); ~~~ &emsp;&emsp;(2)当生命周期钩子发生冲突时,同名的钩子将合并成一个数组,混入对象的钩子在前,组件的钩子在后,如下所示,先输出“mixin”,再输出“component”。 ~~~js var Mixin = { created: function() { console.log("mixin"); } }; Vue.component("btn-custom", { mixins: [Mixin], created: function() { console.log("component"); } }); ~~~ **2)全局混入** &emsp;&emsp;通过Vue.mixin()方法可注册全局的混入对象,如下所示。 ~~~js Vue.mixin({ created: function () { console.log("global"); } }); ~~~ &emsp;&emsp;全局混入会影响所有的Vue实例,包括自定义的组件或第三方组件,因此要谨慎使用。大部分情况下它只适合自定义的选项,在官方的代码风格指南中,为混入中的这些选项制订了专门的命名规范,即以“$\_”和自定义的命名空间为前缀(例如$\_namespace\_),从而避免与其它实例中的选项相冲突,下面是一个简单的示例。 ~~~js Vue.mixin({ $_namespace_getAge: function () { return 28; } }); ~~~ **3)自定义选项合并策略** &emsp;&emsp;除了预定义的合并策略之外,Vue还允许自定义合并策略,只需在Vue.config.optionMergeStrategies中添加一个包含合并逻辑的函数即可。 &emsp;&emsp;下面是一个示例,首先在混入对象和组件中都声明了一个自定义的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 }); ~~~ ## **四、动态组件** &emsp;&emsp;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>** &emsp;&emsp;虽然可以动态切换组件,但是组件的状态无法保持,例如在tab1组件的文本框中输入字符,来回切换后,这些字符就消失了。如果要缓存组件的状态,那么可以用Vue提供的另一个内置的\<keep-alive>元素,如下所示,用它来包裹\<component>元素,就不会销毁失活的组件,从而提升渲染性能。 ~~~html <keep-alive> <component :is="current"></component> </keep-alive> ~~~ &emsp;&emsp;注意,\<keep-alive>元素自身不会渲染成一个DOM元素,并且其可与任意元素配合,但子元素只能渲染一个。由此可知,\<keep-alive>元素内可包含条件指令(如下所示),但不能包含v-for指令。 ~~~html <keep-alive> <tab1 v-if="current == 'tab1'"></tab1> <tab2 v-else></tab2> <keep-alive> ~~~ &emsp;&emsp;有两个与\<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 等。