一、前言
从这篇开始分享一下我在学习组件的时候总结的一些经验和组件实现的原理(以element-ui上面的一些组件为例),然后内容可能慢慢的多了起来,在某一模块出现更多的文字,如果你有哪里不明白的地方或者看起来不习惯的地方,我将您的建议使用,做以改正,为后面写出来的文章不再出现这样的问题做铺垫
二、概念
简单理解: 组件是一种以vue语法来自定义的HTML的标签,既然是HTML标签,所以它肯定具有复用性并且自带一个名字,在创建层面上看组件是一个可复用的Vue的实例,在使用层面组件是一个自定义的HTML标签,然而Vue实例化的结果是由配置项而决定,导致定义组件变成定义配置选项
组件类型 | 特点说明 |
---|---|
页面组件 | 也叫做单页应用(只有一个HTML文档的程序),既然一个程序只有一个HTML页面,那这个文件文件很大应该没毛病吧,它一般不会存在props选项以及自定义的事件,导致它一般作为路由的渲染,没有复用性,不会对外提供接口 |
基础组件 | 比如说element-ui上面的一个按钮,这个按钮就属于一个基础组件,很多的基础组件就可以变成一个组件库(这类组件可以按照文档给定的方式使用),所以这类组件开发会难一点,最起码我要适配多种使用场景,开发难度自然而然也就提高 |
业务组件 | 与基础组件类似,只不过业务组件只会在当前的项目中复用,不具有通用性 |
注意: 组件的命名不能使用已经存在的HTML标签,不然会存在冲突,导致不必要的麻烦!
优化: 既然组件有自己的模板、样式、配置项这种结构,所以也可以将组件各部分进行抽离方便管理
三、使用
全局注册: 使用静态方法Vue.component(组件的名称,组件的配置)
注册,(但是全局注册需要在Vue实例化之前),上面说过一个组件也是一个HTML页面(只不过具有很高的复用性而已),所以template模板(html结构)、css样式、就是文件他都会具备,
import Vue from vue
import Home from './views/home'
// 组件的配置对象
const opt = {
data() {
}
template: `<button></button>`
}
// 这里就会定义一个<button-counter></button-counter>
// 这样的标签
Vue.component('button-counter', opt)
const vm = new Vue(Home)
<!-- 就可以直接在Home页面使用这个按钮 -->
<button-counter></button-counter>
局部注册: 在需要注册的组件的页面的配置项中使用component配置项注册,在配置项中以间(组件名)值(组件的配置)对的形式进行注册
export default {
// 在这个配置对象中,一组间值对就是一个组件
components: {
// 定义了一个名为counter的组件
counter: {
// 组件的核心是一个按钮
template: '<button></button>'
}
}
}
<!-- 在页面上使用 -->
<counter></counter>
<counter />
结论: 静态方法Vue.component
和配置项components
都可以注册组件,因为单页面应用可以称为做页面组件,当做组件使用,而页面组件是可以通过实例化vue将其构建出来,但是这种方式只能通过new关键字实例化,并且这种方式在一个项目中只能使用一次(因为Vue实例一个项目只能存在一个),所以想要实例化Vue既可以通过new关键字实例化,也可以通过静态方法Vue.component
全局去实例化和components
局部实例化
问题:为什么组件不需要el配置项
答:
因为根实例的el配置项是提供挂载的位置,在生命周期中,挂载的过程就是替换的过程,所以在这个过程中el节点的这个位置会被占住,等这个el节点被处理好之后就将其替换掉,而组件使用的时候会直接写在模板上面的具体位置,如果我再去写个ID将这个位置占住,再去替换,有点多此一举吧!
问题:为什么组件的data配置项必须是一个函数然后返回一个对象
答:
如果使用函数返回一个对象,在实例化的过程中会执行这个函数,每次执行完函数之后会返回一个对象,这个对象会被计算机分配一个内存地址,导致每次返回的对象的内存地址不一样,也就是每个组件中的会变成独立的状态,在复用的时候不会受影响,而根实例没有复用性,所以其data可以不是一个函数类型
四、prop属性
定义: prop是可以在组件上面注册一些自定义的属性,当一个值传递给prop属性的时候,它会变成那个组件实例的一个property,这个property可以直接在模板上面使用,类似data,定义过的prop属性会被收集到实例的_props属性上面(实例上面_开头的属性为不想让外界直接调用的属性)
使用: 我们自定义的组件是一个标签,标签可以定义属性
特点:
- 自定义标签的属性会默认继承给模板的根标签
// 如果我的模板里面没有嵌套的话里面input标签会有
// type为checkbox的属性(例如:template: `<input />`),
// 因为标签的属性会继承给跟标签
// 如果出现嵌套,就不会有type的这个属性了
// (例如:template: `<div><input /></div>`)
// 它会继承到div上
// 如果想要定义标签的porp属性,那么需要在其组件的配置
// 项里面去定义一个props的选项,通过数组的方式声明该
// 属性的名称
// 如果属性值是一个不变的值:静态绑定(值不会变)
<my-input type = "checkbox"/>
// 如果属性值是一个变化的值:动态绑定(值会变)
<my-input :type = "newValue"/>
export default {
components: {
// 上面的举例我在my-input的标签上定义了一个type的属性,
// 我如果需要拿到这个属性需要定义一个props的配置项,其次
// 以数组的方式来定义,上面说到过使用方法跟data类似,所以
// 可以在mounted声明周期函数中打印这个prop属性
props: ["type"]
// 自定义了一个<my-input />的标签,这个标签的本质是一个input框
'my-input': {
// 给指定标签绑定属性就不会受默认绑定给跟标签默认行为的影响了
template: `<input :type="type"/>
}
mounted(){
// 这里会拿到这个checkbox的值,拿到值之后就
// 可以将其绑定给指定的标签,这样我自定义的标
// 签就能够使用自己定义的属性了
console.log(this.type)
}
}
}
理解: prop就是外界的数据想要传递到组件内部的一个入口,它可以将组件外部环境 和内部环境相 联系起来,在定义了一个prpos属性的时候,组件外部就可以在标签上定义这些属性并传值,组件内部就 可以通过this.属性名拿到这个值
五、prop的类型验证
说明: 组件的prop可以指定验证要求,如果开发人员知道这些类型,当需求没有被满足的时候,Vue会在控制台发出警告
类型: 无类型约束、自带类型约束、自定义类型约束
注意: 所有的prop属性自带只读不可写的特性
// 无约束类型
export default {
// 这里的type没有任何的限制,所以它可以传任意类型
// 的值当数据没有约束的时候,数组语法可行,但是数
// 组语法是最简陋的语法,当数据存在约束的时候,
// 约束一般以间值对的形式产生,所以就要只用对象语法
props: ['type']
}
// 约束类型
props: {
// 自带类型约束
type: {
required: true // 设置为必传属性,不穿会报错
default: 'text'// 设置为可传,如果不传值那么会使用默认值
type: String // 设置类型为字符串,这里需要写构造函数
}
// 自定义类型约束(使用自定义检查函数)
type: {
// 自定义验证函数,只需要返回true或者是false就可以
// 这个value值为传递过来的值,假设我书写了
// (<my-input type = "checkbox"/>),那么此时这个
// value就是checkbox
validator(value) {
const result = ['medium', 'small', 'mini'].includes(value);
if (!result) {
console.error('prop属性size的取值范围是:medium / small / mini');
}
// 我这样写的好处是如果输入的值有错误,
// 那么我就提示这个值得取值范围
return result;
}
}
}
标签中属性的书写:
一般语法:
如果有多个class类名就将其写在一个之中
数组语法:
将所有类名用数组包裹起来,里面每一个元素为一个类名,这个元素用引号包裹,使用时注意单双引号的交替
对象语法:
在数组语法中再将每个类名用一个对象将其包裹起来,在对象语法中,可以使用变量,表达式,函数,但是他们的主要目的是返回一个true或者是false的值,来让这个class属性生效或者不生效,使用同样需要绑定,因为使用了变量
// 一般语法
class="active" class="active display"
// 数组语法
class="['active', 'display']"
:class="['font' + size]" 在数组语法中可以使用变量和拼接,但变量需定义绑定
// 对象语法
:class="[{'font':false}]" ==> font的这个class不生效
:class="[{'font':true}]" ==> font的这个class生效
:class="[{'font':a > b ? false : true}]" ==> 使用表达式判断
:class="[{'font':fn}]" ==> 使用函数判断
六、attr属性
定义: 组件上定义的所有属性默认会被收集到vm.$attr(class除外)中,如果在props选项中去指定一些属性,指定的这些属性会被收集到vm._props里面,从而完成了attr属性转换为prop属性
attr与prop: attr和prop都可以接受外界传递到组件内部的数据,prop属性可以直接被代理到vm实例上去,使用方法跟data类似,除此之外,prop还可以配置默认值,类型检查,可选,使用起来比较简洁,但是attr不行,attr属性没有被代理到实例上面,所以只能使用this.$attrs.属性名去取值,但是,attr属性会默认继承在模板的跟标签上面,如果不需要这个继承,在当前组件的配置项里面加上一个配置选项 inheritAttrs: false,
七、单文件组件
说明: 使用官方定义的一个.vue
文件,文件将前面所写的.css .js .template
三个文件用语法块的形式集成在一起,与之对应的官方的语法块分别为style、script、template,然后将对应文件中的内容放在对应标签的语法块中。使用就将这个文件导入就可以,一般文件名的首字母大写
<template>
<div >
</div>
</template>
<script>
export default {
};
</script>
<style lang="scss">
</style>
解析
.vue
的文件需要去下载一个vue-loader,然后在webpack.config.JS中去配置解析文件的规则
// 在配置项module的rules规则里面加入以下代码
{
test: /\.vue$/,
use: ['vue-loader']
}
// 在vue-loader里面获取一个VueLoaderPlugin
const { VueLoaderPlugin } = require('vue-loader');
// 再将获取到的这个VueLoaderPlugin放在配置项
// plugins里面然后用new关键字去创建一个插件
new VueLoaderPlugin()