ShiningDan的博客

一天一个 Element 组件 - Link

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

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

Link 组件使用文档:Link 文字链接

.vue 文件:/packages/link

.scss 文件:[/packages/theme-chalk/link.scss](https://github.com/ElemeFE/element/blob/dev/packages/theme-chalk/src/link.scss

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

el-link 的实现方案,基于原生 <a> 元素

Props

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

1
2
3
4
5
6
7
8
9
10
11
12
13
props: {
type: {
type: String,
default: 'default'
},
underline: {
type: Boolean,
default: true
},
disabled: Boolean,
href: String,
icon: String
}

type

type 字段是用来控制 el-link 组件的样式(主要是颜色),并不影响行为,我们来看一下实现方案:

首先,用户传入 type 属性,type 只会被自动注入到 a 标签的样式类中:

1
2
3
4
5
6
<a
:class="[
'el-link',
type ? `el-link--${type}` : '',
]"
>

如果设置了 typesuccess,则 a 标签会自动添加 el-link--success 的样式类。

我们来看一下 el-link--success 这个样式类做了什么,打开 scss 文件,/packages/theme-chalk/link.scss

定位到这一段相关的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@import "common/var";

$typeMap: (
primary: $--link-primary-font-color,
danger: $--link-danger-font-color,
success: $--link-success-font-color,
warning: $--link-warning-font-color,
info: $--link-info-font-color);

@each $type, $primaryColor in $typeMap {
&.el-link--#{$type} {
color: $primaryColor;
&:hover {
color: mix($primaryColor, $--color-white, 80%)
}
&:after {
border-color: $primaryColor
}
@include when(disabled) {
color: mix($primaryColor, $--color-white, 50%)
}
@include when(underline) {
&:hover:after {
border-color: $primaryColor
}
}
}
}

首先,引入 scss 全局颜色变量,然后定义 el-link 标签使用的 $typeMap 变量,将 type 和 颜色变量映射起来。

其次,遍历映射表 $typeMap,创建 el-link--#{$type} 样式类。比如当 typesuccess 的时候,就映射到 el-link--success 样式类了。

我们可以通过样式类的定义,大致看到 element 具体做了哪些样式的修改,比如

  1. 设置了 el-link 标签内的文字颜色 color
  2. 设置鼠标 hover 上去后,文字颜色会有 80% 的透明度
  3. 设置了 disable 属性后,50% 的透明度
  4. 设置了 underline 属性后,hover :after 伪元素也要设置边框颜色

所以当 <a> 标签设置了 el-link--success 这个样式类的时候,会应用到 scss 中 typesuccess的样式。

disable

同理,disable 属性设置后,对应也是修改的 <a> 标签的样式类,以及 href 属性:

1
2
3
4
5
6
<a
:class="[
disabled && 'is-disabled',
]"
:href="disabled ? null : href"
>

从样式上面来说,输了参考 type 中,有 disable 相关的样式改变外,disabled 还设置了鼠标上移时的鼠标样式反馈:

1
2
3
@include when(disabled) {
cursor: not-allowed;
}

除此以外,还有一个细节:当设置 disabled 的时候,点击这个链接应该是没有跳转行为的。此时,HTML 中的 <a> 标签,应该没有 href 这个属性。经过测试,如果是设置 href="",点击 <a> 标签会重复跳转当前页面。

1
2
3
4
5
# 点击不会跳转
<a class="el-link el-link--default is-disabled">

# 点击后会有跳转当前页面的行为
<a href class="el-link el-link--default is-disabled">

underline

underline 属性的设置,也是反映到 <a> 中 class 的变化。

1
2
3
4
5
6
<a
:class="[
'el-link',
underline && !disabled && 'is-underline'
]"
>

el-link 有一个比较细节的地方,就是它的下划线,并没有使用 HTML <a> 的原生 text-decoration: underline 属性,估计是满足不了设计师的设计,太丑了 hhhh。

其实现方案是,创建一个伪元素::after,通过 position: absolute 设置伪元素和当前 <a> 标签的位置与大小重合。设置 content: "" 设置伪元素没有内容,不会影响 <a> 标签内的内容。最后设置 border-bottom,来自定义下划线的样式。即 el-link 的下划线,其实是伪元素的 border-bottom 来模拟的。

参考 SCSS 的实现部分:

1
2
3
4
5
6
7
8
9
10
11
@include when(underline) {
&:hover:after {
content: "";
position: absolute;
left: 0;
right: 0;
height: 0;
bottom: 0;
border-bottom: 1px solid $--link-default-active-color
}
}

slot 与 icon

这两部分放在一起,是因为 sloticon 属性,都是在 <a> 标签的子元素:

1
2
3
4
5
6
7
8
9
<a>
<i :class="icon" v-if="icon"></i>

<span v-if="$slots.default" class="el-link--inner">
<slot></slot>
</span>

<template v-if="$slots.icon"><slot v-if="$slots.icon" name="icon"></slot></template>
</a>

从这段代码中可以看出来,el-link 支持 icon 属性,也支持默认 slot,以及具名 slot=icon,虽然具名 slot 并没有在例子中体现出来。

Template

v-bind="$attrs"

在封装 <a> 的时候,有一个属性大家要注意:

1
<a v-bind="$attrs"></a>

通过 v-bind="$attrs" 这种方法,可以将 el-link 中非 props 属性都传给 <a> 标签,这样我们在封装高级组件的时候,特别是封装基础标签的高级组件,不同将该标签的每一个属性都通过 props 单独设置一遍,这在封装基础 HTML 标签的时候很重要。(PS: 为什么 el-button 没有设置 v-bind="$attrs" 呢?我暂时没想出来,大家知道的,非常欢迎在下面留言,我会补充到文章里的~)

JS

click 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<a
@click="handleClick">
</a>
</template>
<script>
export default {
methods: {
handleClick(event) {
if (!this.disabled) {
if (!this.href) {
this.$emit('click', event);
}
}
}
}
}
</script>

当设置了 disabled 属性,或者没有设置 href 属性的时候,我们可以通过设置 el-linkclick 事件,来监听 a 标签的点击事件。

CSS

CSS 相关的源码解读,之后再补充