vue-element-plus中的button组件

290 阅读2分钟

element-plus是一个开箱即用的ui组件库.看看简单的button组件,他们是如何设计的,官方给的contents如下:

image.png

通过阅读源码,总结以下几点:

  1. 封装了原生的button标签,它具备原来的功能,button
  2. 做公共ui组件样式需要进行隔离,这一点非常重要,提取公共组件的时候,也应该默认加入,很多ui组件库的做法都是通过转换,在某一个区域/块,增加唯一前缀标识,简称命名空间。
  <button
    ref="_ref"
    :class="[
      ns.b(),
      ns.m(_type),
      ns.m(_size),
      ns.is('disabled', _disabled),
      ns.is('loading', loading),
      ns.is('plain', plain),
      ns.is('round', round),
      ns.is('circle', circle),
      ns.is('text', text),
      ns.is('link', link),
      ns.is('has-bg', bg),
    ]"
    :aria-disabled="_disabled || loading"
    :disabled="_disabled || loading"
    :autofocus="autofocus"
    :type="nativeType"
    :style="buttonStyle"
    @click="handleClick"
  >
 </button>

可以看到是通过ns函数进行初始化、转换添加样式前缀,下面来看看这个命名空间函数怎么写的:

packages/hooks/use-namespace

import { useGlobalConfig } from '../use-global-config'

export const defaultNamespace = 'el' // 初始化前缀常量
const statePrefix = 'is-' // 是否拥有某些功能、样式前缀
// 对样式名称重新命名
const _bem = (
  namespace: string, // 空间名称,如el
  block: string, // 要命名的块,如 使用button
  blockSuffix: string, // 区块后缀 
  element: string, // 普通标签容器
  modifier: string // 可修改的样式名称
) => {
  let cls = `${namespace}-${block}` // 比如 el-button
  if (blockSuffix) { // el-checkbox-group
    cls += `-${blockSuffix}`
  }
  if (element) { // 如 el-scrollbar__wrap 标签容器
    cls += `__${element}`
  }
  if (modifier) { // el-button--warning 其中warning可能为success
    cls += `--${modifier}`
  }
  return cls
}
// 声明命名空间
export const useNamespace = (block: string) => {
  const namespace = useGlobalConfig('namespace', defaultNamespace)
  const b = (blockSuffix = '') =>
    _bem(namespace.value, block, blockSuffix, '', '')
  const e = (element?: string) =>
    element ? _bem(namespace.value, block, '', element, '') : ''
  const m = (modifier?: string) =>
    modifier ? _bem(namespace.value, block, '', '', modifier) : ''
  const be = (blockSuffix?: string, element?: string) =>
    blockSuffix && element
      ? _bem(namespace.value, block, blockSuffix, element, '')
      : ''
  const em = (element?: string, modifier?: string) =>
    element && modifier
      ? _bem(namespace.value, block, '', element, modifier)
      : ''
  const bm = (blockSuffix?: string, modifier?: string) =>
    blockSuffix && modifier
      ? _bem(namespace.value, block, blockSuffix, '', modifier)
      : ''
  const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
    blockSuffix && element && modifier
      ? _bem(namespace.value, block, blockSuffix, element, modifier)
      : ''
    /** 切换按钮(是否)作为 链接link、文字text(新版被废弃)、
    ** 禁用disabled、带loading、round圆角、原形按钮circle、朴素按钮plain的样式 
    **/
  const is: {
    (name: string, state: boolean | undefined): string
    (name: string): string
  } = (name: string, ...args: [boolean | undefined] | []) => {
    const state = args.length >= 1 ? args[0]! : true
    return name && state ? `${statePrefix}${name}` : ''
  }

  // for css var
  // --el-xxx: value;
  const cssVar = (object: Record<string, string>) => {
    const styles: Record<string, string> = {}
    for (const key in object) {
      if (object[key]) {
        styles[`--${namespace.value}-${key}`] = object[key]
      }
    }
    return styles
  }
  // with block
  const cssVarBlock = (object: Record<string, string>) => {
    const styles: Record<string, string> = {}
    for (const key in object) {
      if (object[key]) {
        styles[`--${namespace.value}-${block}-${key}`] = object[key]
      }
    }
    return styles
  }

  const cssVarName = (name`: string) => `--${namespace.value}-${name}`
  const cssVarBlockName = `(name: string) =>
    `--${namespace.value}-${block}-${name}`

  return {
    namespace, // 空间
    b, // 区块后缀
    e,// 标签容器
    m,// 可修改类型
    be, // b&&e
    em, // e&&m
    bm, // b&&m
    bem, // b&&e&&
    is, // 是否为某个状态
    // css
    cssVar, // style对象
    cssVarName,
    cssVarBlock, // style对象
    cssVarBlockName,
  }
}

export type UseNamespaceReturn = ReturnType<typeof useNamespace>

  • 首先定义默认前缀defaultNamespace,element-plus的就定义为el,定义是否拥有某些功能、样式前缀is-,使用该ui库后,查看dom结构都会看到对应的前缀,如下:

image.png

  • 创建命名空间箭头函数,内联style也要使用cssVar、cssVarBlock增加空间标识和前缀,如--el-border-style:solid; 就是增加了前缀--与开发者项目中的进行border-style进行区分。
  1. 代码整洁,如大篇幅的props,emit进行抽离,而不是堆积在一个文件内,导致维护成本高。
import { buttonEmits, buttonProps } from './button'
const props = defineProps(buttonProps)
const emit = defineEmits(buttonEmits)
  1. 原生ts event
    • MouseEvent 接口指用户与指针设备(如鼠标)交互时发生的事件
    • HTMLButtonElement 接口提供操作 button 元素 的属性和方法。