el-icon
el-icon是图标组件,看文档描述,直接设置类名就可以了
我们来看下组件中是如何实现的。打开packages/icon/src/icon.vue,里面很简单,只给了一个name属性去拼接成带el-icon-的类名
<template>
<i :class="'el-icon-' + name"></i>
</template>
<script>
export default {
name: 'ElIcon',
props: {
name: String
}
};
</script>
再看下对应的icon.scss文件,里面有一系列类名对应的样式,给每个类名添加了一个伪元素,并写入内容。这些内容是在icon.scss文件里面一开头就定义了的字体样式,字体文件会解析对应的字体图标,从而实现通过类名就可以显示出不同的图标。
\
我们看上面的文档教我们的使用方法是<i class="el-icon-delete-solod"></i>,在项目开发中我们也照着文档所示的方式去使用icon,
并不是通过像el-button那样,通过组件的方式来使用<el-icon name="xxx"></el-icon>,那我们可以以组件的形式来使用吗?
我们像下面那样试一下,页面是可以渲染出来的,我们可以以组件的方式来使用el-icon图标。
<el-icon name="delete-solid" />
el-button
provide和inject 依赖注入
在阅读el-button的源码前,我们需要先来学习一个vue的知识点组合式API:依赖注入provide和inject
provide提供一个值,可以被后代组件注入。接受两个参数,第一个是要注入的key,可以是字符串或者Symbol,第二个参数是要注入的值,可以像下面这样使用
// 方式一
export default {
provide: {
message: 'hello!'
}
}
// 方式二
export default {
data() {
return {
message: 'hello!'
}
},
provide() {
// 使用函数的形式,可以访问到 `this`
return {
message: this.message
}
}
}
inject注入上层传过来的值,注入的数据会在组件自身的状态之前被解析,因此可以在data()中访问注入的属性,可以像下面这样使用,更多使用方式见文档
export default {
// 方式一
inject: ['message'],
// 方式二,注入别名
inject: {
localMessage: { // localMessage是别名
from: 'message' // from是来源
}
},
// 方式三,注入有默认值,此时必须使用对象形式
inject: {
message: {
from: 'message', // 当与原注入名同名时,可以省略
deafult: 'default value'
}
}
}
逻辑
我们接着来看el-button的源码,和文档上写的一样,传入了一些属性,注入了elForm、elFormItem两个属性,计算了三个属性,当点击时会传出一个click方法,默认参数为event
<script>
export default {
name: 'ElButton',
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
props: {
type: {
type: String,
default: 'default'
},
size: String,
icon: {
type: String,
default: ''
},
nativeType: {
type: String,
default: 'button'
},
loading: Boolean,
disabled: Boolean,
plain: Boolean,
autofocus: Boolean,
round: Boolean,
circle: Boolean
},
methods: {
handleClick(evt) {
this.$emit('click', evt);
}
}
};
</script>
上面的elForm、elFormItem从哪里注入的呢?我们可以找到packages/form/src,打开form-item.vue和form.vue,可以看到提供了注入,这里的this就是当前的form-item或form组件
// form.vue
export default {
provide() {
return {
elForm: this
};
},
}
// form-item.vue
export default {
provide() {
return {
elFormItem: this
};
},
}
因此在button组件的计算属性中,this.elFormItem和this.elForm就相当于访问form-item和form组件的elFormItemSize和disabled属性
export default {
computed: {
// 当button嵌入在form-item组件中时,没有传递button的size属性
// buttonSize要判断一下外层的父组件form-item是否有传递size属性,有则用外层的
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
buttonSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
// 当button嵌入在form组件中时,没有传递disabled属性或者为false
// 要判断下外层的父组件form是否有传递disabled属性,有则用外层的
buttonDisabled() {
return this.disabled || (this.elForm || {}).disabled;
}
},
}
目前我们阅读的源码版本是v2.15.6,但是这样写的buttonDisabled会有个问题:当我们给按钮明确传入false的时候,按钮应该不会被禁用,可是当他发现this.disabled为空或false时就会去找elForm身上的disabled,也就是说如果elForm设置disabled:true,离el-Form最近的那个按钮就会导致失效。
这个问题在v2.15.7版本中被修复了,会先去判断下button组件本身是否有传递disabled属性,没传递再用elForm组件中的。
我们在平时的开发中也会遇到没有考虑完全的时候,这就需要我们在迭代过程中不断测试、完善逻辑。
computed: {
buttonDisabled() {
return this.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled;
}
}
再来看下template部分,给button元素绑定了一系列属性,预留了一个插槽
<template>
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle
}
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
scss部分
在上篇文章我们已经分析过了b、e、m的作用,现在可以直接来分析下button的scss文件。
下面的代码中有一个&+&,scss中的&表示父级元素,即.el-button,所以&+&即为.el-button + .el-button,表示相邻的.el-button元素需要设置左外边距10px
/* button.scss */
@include b(button) {
display: inline-block;
...
@include utils-user-select(none);
& + & {
margin-left: 10px;
}
}
/* util.scss */
/* 禁止用户选中文字 */
@mixin utils-user-select($value) {
-moz-user-select: $value;
-webkit-user-select: $value;
-ms-user-select: $value;
}
button.scss文件中还有一个when混合。
$state-prefix在config.scss中定义了:is-。$state为传进混合的plain,结合起来就是is-plain,这里的&仍是.el-button,用一个@at-root来跳出嵌套
/* button.scss */
@include b(button) {
...
@include when(plain) {
&:hover,&:focus {
...
}
&:active {
...
}
}
}
/* mixins.scss */
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
/* 第一段代码会被编译为 */
.el-button {
...
}
.el-button.is-plain {
&:hover,&:focus {
...
}
}
button.scss中还有一个utils-clearfix混合,这是用于清除浮动的混合
@mixin utils-clearfix {
$selector: &;
@at-root {
#{$selector}::before,
#{$selector}::after {
display: table;
content: "";
}
#{$selector}::after {
clear: both
}
}
}
小结
至此,el-icon和el-button部分的源码我们就分析完毕了。从el-icon中我们学习到图标是通过字体文件库来实现的,从el-button中学习到了provide和inject的用法。