0

一天一个 Element 组件 - Tag

 2 years ago
source link: https://shiningdan.github.io/2020/01/30/%E4%B8%80%E5%A4%A9%E4%B8%80%E4%B8%AA-Element-%E7%BB%84%E4%BB%B6-Tag/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文是 Element 的组件源码学习系列。

项目源码:ElemeFE/element | GitHub,Tag:v2.13.0

Tag 组件使用文档:Tag 标签

.vue 文件:/packages/tag

.scss 文件:/packages/theme-chalk/tag.scss

.d.ts 文件:/types/tag.d.ts

el-tag 是基于 span 封装的标签组件。因为 Tag 的使用形式,大多为一排多个 Tag 进行选择,所以并没有基于 div 进行封装

Props

首先我们来看一下 el-tag 的 Props,了解使用这个组建的入参:

props: {
text: String, // 并没有使用
closable: Boolean,
type: String,
hit: Boolean,
disableTransitions: Boolean,
color: String,
size: String,
effect: {
type: String,
default: 'light',
validator(val) {
return ['dark', 'light', 'plain'].indexOf(val) !== -1;
}
}
}

closable

设置 closable 属性可以定义一个标签是否可移除。在显示效果上来说,设置了 closableel-tag,标签内部靠后,有一个小 x,点击即可触发 close 事件。

我们开看一下相关的实现方案:

render(h) {
const tagEl = (
<span
{ this.$slots.default }
{
this.closable && <i class="el-tag__close el-icon-close" on-click={ this.handleClose }></i>
}
</span>
);

return this.disableTransitions ? tagEl : <transition name="el-zoom-in-center">{ tagEl }</transition>;
}

由于 el-tag 标签的内部,会接收文字或者一些 HTML 子元素,所以 span 标签的第一个子元素,就是先补充 this.$slots.default

当设置了 this.cloable 的时候,会在 span 内部追加一个 i 标签,用来显示小小的删除按钮,同时点击删除按钮的时候,会调用 this.handleClose 方法:

handleClose(event) {
event.stopPropagation();
this.$emit('close', event);
}

这里有一个细节大家需要注意一下,我们点击 i 标签的时候,触发的事件是 click 事件,但是 el-tag 对外提供的是 close 事件,所以我们要把 click 事件转换成 close 事件。并且为了防止子元素的 click 事件冒泡出去,还需要调用 event.stopPropagation();

同样,el-tag 也提供了 click 事件,直接使用的是 span 标签的 click 事件。逻辑非常简单,感觉这样的设计挺冗余的:

handleClick(event) {
this.$emit('click', event);
}

由上,我们可以总结出一些写高级组件的事件处理的一些套路:

  1. 高级组件的事件,都是需要主动 emit 出来的,所以我们对于每一个要提供的事件,都需要有对应的 handler 函数,虽然这个函数的逻辑可能非常简单
  2. 所有对外提供的事件,因为事件的冒泡特性,都要考虑一下是不是会互相影响。

type & hit

将这两个属性放在一起介绍,主要是因为这两个属性,都是直接操作 CSS class 来影响样式的,没有太多的逻辑耦合。

const classes = [
'el-tag',
type ? `el-tag--${type}` : '',
tagSize ? `el-tag--${tagSize}` : '',
effect ? `el-tag--${effect}` : '',
hit && 'is-hit'
];

相关的 CSS 代码为:

@mixin genTheme($backgroundColorWeight, $borderColorWeight, $fontColorWeight, $hoverColorWeight) {
&.el-tag--info {
background-color: mix($--tag-info-color, $--color-white, $backgroundColorWeight);
border-color: mix($--tag-info-color, $--color-white, $borderColorWeight);
color: mix($--tag-info-color, $--color-white, $fontColorWeight);

@include when(hit) {
border-color: $--tag-info-color;
}

.el-tag__close {
color: mix($--tag-info-color, $--color-white, $fontColorWeight);
&:hover {
color: $--color-white;
background-color: mix($--tag-info-color, $--color-white, $hoverColorWeight);
}
}
}
}

通过 CSS 相关的代码,我们可以知道:

  1. 设置了 type 之后,改变了对应的 background-colorborder-colorcolor
  2. hit 属性决定是否有边框描边,当然改变的是 border-color
  3. 当设置为 Tag 可以删除时,会在 span 中新增一个 i 标签来显示删除按钮,对应的 CSS 类名都为 el-tag__close,相关的样式也可以在这里找到。

size & effect

computed: {
tagSize() {
return this.size || (this.$ELEMENT || {}).size;
}
},

------------

const classes = [
'el-tag',
type ? `el-tag--${type}` : '',
tagSize ? `el-tag--${tagSize}` : '',
effect ? `el-tag--${effect}` : '',
hit && 'is-hit'
];

sizetype 的变化,同样也反应到 span 标签的 CSS 类上,来触发大小的改变,以及主题的变换。

我们来看一下相关的 SCSS 实现:

@include b(tag) {
@include genTheme(10%, 20%, 100%, 100%);
// 基础 CSS 样式
display: inline-block;
height: 32px;
padding: $--tag-padding;
......

// 关闭按钮的基础样式
.el-icon-close {
border-radius: 50%;
text-align: center;
position: relative;
......
}

// 主题的变换
@include m(dark) {
@include genTheme(100%, 100%, 0, 80%);
}

// 主题的变换
@include m(plain) {
@include genTheme(0, 40%, 100%, 100%);
}

// 大小的变化
@include m(medium) {
height: 28px;
line-height: 26px;

.el-icon-close {
transform: scale(.8);
}
}

@include m(small) {
height: 24px;
padding: 0 8px;
line-height: 22px;

.el-icon-close {
transform: scale(.8);
}
}

}

在这段 SCSS 里面,基础的 CSS 语法,比如设置 border,设置 height 之类的,我们都能够看懂。问题是,这段 SCSS 是如何确认当前的主题是 light 还是 dark,当前的大小是 medium 还是 small 呢?

答案都在 m 这个函数上。

Element 在对 CSS 类进行命名的时候,使用的是 BEM 命名法。具体什么是 BEM 命名法,大家可以去自行搜一下,我们这里主要看 BEM 命名法如何在 el-tag 中应用的:

当设置主题 effectdark,设置大小 sizesmall 的时候

const classes = [
'el-tag',
type ? `el-tag--${type}` : '',
tagSize ? `el-tag--${tagSize}` : '',
effect ? `el-tag--${effect}` : '',
hit && 'is-hit'
];

el-tag 对应的 span 标签会被加上两个 CSS 类名:el-tag--smallel-tag--dark。在 BEM 命名法中,B 代表的 block 为 tag,M 代表的 module 为 smalldark

这里的 b 函数和 m 函数,就代表着匹配 block,以及匹配 module 的能力。当 span 标签的 block 为 tag, module 为 smalldark 的时候,就会运行对应的 CSS 代码。

module 为 dark 的 HTML 元素,会被应用定义好的 genTheme 函数,传递对应的颜色设置: genTheme(100%, 100%, 0, 80%);

module 为 small 的 HTML 元素,会被设置 CSS 样式 height: 24px; padding: 0 8px; 等。

@include b(tag) {
@include genTheme(10%, 20%, 100%, 100%);
// 基础 CSS 样式
display: inline-block;
......

// 主题的变换
@include m(dark) {
@include genTheme(100%, 100%, 0, 80%);
}

// 主题的变换
@include m(plain) {
@include genTheme(0, 40%, 100%, 100%);
}

// 大小的变化
@include m(medium) {
...
}

@include m(small) {
height: 24px;
padding: 0 8px;
line-height: 22px;

.el-icon-close {
transform: scale(.8);
}
}

}

b 函数的定义和 m 函数的实现方法,都可以在 tag.scss 文件的最上面:@import "mixins/mixins"; 这个引用中找到。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK