3.8 Tag 标签

0x00 简介

组件Tag 多用于标记和分类。 本文将深入分析组件源码,剖析其实现原理,耐心读完,相信会对您有所帮助。packages/tag/src/tag.vue 文件是组件源码实现。 🔗 组件文档 Tag 🔗 github 源码 tag.vue

更多组件剖析详见 👉 📚 Element 2 源码剖析组件总览

0x01 组件源码

使用 渲染函数 创建组件,代码结构如下 👇。

<script>
  export default {
    name: 'ElTag',
    // 组件 prop
    props: {
      // ...
    },
    methods: {
      // 关闭 Tag 时触发的事件
      handleClose(event) {
        // ...
      },
      // 点击 Tag 时触发的事件
      handleClick(event) {
        // ...
      }
    },
    // 计算属性
    computed: {
      // 标签尺寸
      tagSize() {
        // ...
      }
    },
    // 渲染 标签 虚拟DOM
    render(h) {
      // ...
    }
  };
</script>

attributes 属性

组件定义了 8 个 prop

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;
    }
  }
},

prop详细描述如下(只有 7 个):

proptext 虽然定义了,但是在代码中没有被使用。 typesizeeffect 的值若没有匹配指定的字符串,根据值生成无效的 class(样式规则未定义)。

计算属性

tagSize 根据size$ELEMENT动态计算标签的尺寸。

组件定义了 prop size , 则tagSize 值为 size 定义值。若 size 值为 undefined'', tagSize 值为由全局配置 $ELEMENT决定。

$ELEMENT 对象不为空且包含 size 属性, tagSize 值为对象的属性值 $ELEMENT.size;否则 tagSize 值为 undefined,

// 标签尺寸
tagSize() {
    return this.size || (this.$ELEMENT || {}).size;
}

前文可知,组件入口文件中 install 方法中定义了对象 $ELEMENT 全局配置。其中组件的默认尺寸 size值为''

// src/index.js  组件入口文件
const install = function (Vue, opts = {}) {
  // ...
  // 全局配置   组件的默认尺寸size  弹框的初始z-index
  Vue.prototype.$ELEMENT = {
    size: opts.size || "",
    zIndex: opts.zIndex || 2000,
  };
  // ...
};

完整引入组件时,使用 vue.use()注册组件,会执行 install 方法,声明了全局属性 $ELEMENT

import Element from "element-ui";
Vue.use(Element);

// 手动设定 组件默认size
Vue.use(Element, {
  size: "small",
});

按需引入时, 需要手动配置 Vue.prototype.$ELEMENT = { size: 'small', zIndex: 3000 };,若不配置 $ELEMENT 未定义值为 undefined,若此调用 $ELEMENT.size 则会抛出异常Uncaught ReferenceError: $ELEMENT is not defined

import { Tag } from "element-ui";

// 全局配置
Vue.prototype.$ELEMENT = { size: "small", zIndex: 3000 };

// 引入组件
Vue.use(Tag);

表达式 (this.$ELEMENT || {}) 防止$ELEMENT 未定义赋予空对象防止异常调用。

events 事件

点击 Tag 时触发 click 事件。

// 点击 Tag 时触发的事件
handleClick(event) {
  // 触发当前实例上的事件
  this.$emit('click', event);
}

关闭 Tag 时触发 close 事件。

// 关闭 Tag 时触发的事件
handleClose(event) {
  // 阻止捕获和冒泡阶段中当前事件的进一步传播。
  event.stopPropagation();
  // 触发当前实例上的事件
  this.$emit('close', event);
},

render() 渲染函数

组件将创建一个 <span> 元素 VNode,动态添加 class,使用内联样式设置背景色,定义了组件点击事件。 <span> 元素包含 2 个子节点

  1. 默认插槽;

  2. 名为el-icon-closeIcon图标,定义了关闭 Tag 时 click 事件。当 closable 值为true时才会渲染。

disableTransitions值为true时,使用内置组件 transition 包裹 <span> 元素实现缩放zoom-in-center效果。

// 渲染 标签 虚拟DOM
render(h) {
  const { type, tagSize, hit, effect } = this;
  // 动态添加class
  const classes = [
    'el-tag',
    type ? `el-tag--${type}` : '',
    tagSize ? `el-tag--${tagSize}` : '',
    effect ? `el-tag--${effect}` : '',
    hit && 'is-hit'
  ];
  // tag 元素
  const tagEl = (
    <span
      class={ classes }
      style={{ backgroundColor: this.color }}
      on-click={ this.handleClick }>
      { this.$slots.default }
      {
        this.closable && <i class="el-tag__close el-icon-close" on-click={ this.handleClose }></i>
      }
    </span>
  );
  // 组件VNode
  return this.disableTransitions ? tagEl : <transition name="el-zoom-in-center">{ tagEl }</transition>;
}

根据组件 prop 动态添加 class

  • 'el-tag' 组件默认样式。

  • type ? 'el-tag--${type}' : '' 设置组件不同类型颜色。若 type 值不是以下success/info/warning/danger其中一个,设置无效(生成无效的 class)。

  • tagSize ? 'el-tag--${tagSize}' : '' 设置组件不同尺寸。若 tagSize 值不是以下medium/small/mini其中一个,设置无效(生成无效的 class)。

  • effect ? 'el-tag--${effect}' : '' 设置组件不同主题。若 effect 值不是以下dark/light/plain其中一个,设置无效(生成无效的 class)。 effect 默认值为 light,对应的 el-tag--light是无效的样式,未定义。

  • hit && 'is-hit' 边框描边效果 。

color 属性定义组件内联样式 backgroundColor,权重较高会覆盖其他样式的颜色。

0x02 组件样式

src/tag.scss

组件样式源码 packages\theme-chalk\src\tag.scss 使用混合指令 bmgenTheme 嵌套生成组件样式。

genTheme() 组件主题

混合指令 genTheme 用于生成组件的主题样式。

@mixin genTheme(
  $backgroundColorWeight,
  $borderColorWeight,
  $fontColorWeight,
  $hoverColorWeight
) {
  background-color: mix(
    $--tag-primary-color,
    $--color-white,
    $backgroundColorWeight
  );
  // ...

  // 生成 &.is-hit
  @include when(hit) {
    // ...
  }

  .el-tag__close {
    // ...
    &:hover {
      // ...
    }
  }
  // info / success / warning / danger 结构相同
  &.el-tag--info {
    // ...

    // 生成 &.is-hit
    @include when(hit) {
      // ...
    }

    .el-tag__close {
      // ...
      &:hover {
        // ...
      }
    }
  }
  // success
  // warning
  // danger
}

Mix 函数是将两种颜色根据一定的比例混合在一起,生成另一种颜色。 前两个参数是想混合的颜色(可以使用颜色变量、十六进制、RGBA、RGB、HSL 或者 HSLA 颜色值),第三个参数是第一种颜色的比例值。

mix($color1, $color2, $weight: 50%) //=> color

组件主题默认样式直接在 el-tag 下生成 。

// 生成 .el-tag
@include b(tag) {
  // 主题规则  默认light
  @include genTheme(10%, 20%, 100%, 100%);
  // ...

  // 生成 .el-tag--dark
  @include m(dark) {
    // 主题规则
    @include genTheme(100%, 100%, 0, 80%);
  }

  // 生成 .el-tag--plain
  @include m(plain) {
    // 主题规则
    @include genTheme(0, 40%, 100%, 100%);
  }
}

结合上述规则生成的主题样式如下

// el-tag 可以替换为 el-tag--dark   el-tag--plain
.el-tag {
  // ...
}
.el-tag.is-hit {
  // ...
}
.el-tag .el-tag__close {
  // ...
}
.el-tag .el-tag__close:hover {
  // ...
}
// info / success / warning / danger 结构相同
.el-tag.el-tag--info {
  // ...
}
.el-tag.el-tag--info.is-hit {
  // ...
}
.el-tag.el-tag--info .el-tag__close {
  // ...
}
.el-tag.el-tag--info .el-tag__close:hover {
  // ...
}
// success ...
// warning ...
// danger ...

组件样式

组件样式逻辑如下。

// 生成 .el-tag
@include b(tag) {
  // 默认light样式
  // ...

  // 生成 .el-tag .el-icon-close
  .el-icon-close {
    // ...
    // 生成 .el-tag .el-icon-close::before
    &::before {
      // ...
    }
  }

  // dark样式 ...
  // plaink样式 ...

  // 生成 .el-tag--medium
  @include m(medium) {
    // ...
    // 生成 .el-tag--medium .el-icon-close
    .el-icon-close {
      // ...
    }
  }

  // 生成 .el-tag--small
  @include m(small) {
    // ...
    // 生成 .el-tag--small .el-icon-close
    .el-icon-close {
      // ...
    }
  }
  // 生成 .el-tag--mini
  @include m(mini) {
    // ...
    // 生成 .el-tag--mini .el-icon-close
    .el-icon-close {
      // ...
    }
  }
}

lib/tag.scss

前文可知使用 gulpfile.js编译 scss 文件转换为CSS,经过浏览器兼容、格式压缩,最后生成 packages\theme-chalk\lib\tag.scss,内容格式如下。

.el-tag {
  // ...
}
.el-tag .el-tag__close {
  // ...
}
.el-tag .el-tag__close:hover {
  // ...
}

/* theme start --  el-tag / el-tag--dark / el-tag--plain
-------------------------- */
.el-tag.is-hit {
  // ...
}
.el-tag .el-icon-close {
  // ...
}
.el-tag .el-icon-close::before {
  // ...
}
/* type --  info / success / warning / danger
-------------------------- */
.el-tag.el-tag--info {
  // ...
}
.el-tag.el-tag--info.is-hit {
  // ...
}
.el-tag.el-tag--info .el-tag__close {
  // ...
}
.el-tag.el-tag--info .el-tag__close:hover {
  // ...
}
// success ...
// warning ...
// danger ...

/* el-tag--dark
-------------------------- */
// ...

/* el-tag--plain
-------------------------- */
// ...

/* theme end --  el-tag / el-tag--dark / el-tag--plain
-------------------------- */

/* size --  medium / small / mini
-------------------------- */
.el-tag--medium {
  // ...
}
.el-tag--medium .el-icon-close {
  // ...
}

0x03 📚 参考

“stopPropagation”,MDN “mix funciton color”,sass

最后更新于