element plus css bem架构之useNamespace

449 阅读3分钟

一、前言

css方案有很多,原生css以及传统的bootstrap框架、less、css in js,sass,以及原子化css Tailwind CSS以及unocss,最近两年盛行的原子化css的一些方案,个人认为本质上和bootstrap很相似,今天我们要来研究的是element plus组件库的css架构方案useNamespace。

二、不同css方案的适应场景以及优缺点

2.1 原生css以及传统的bootstrap框架

原生css以及bootstrap适应于以前前后端不分离时代,现在基本不用了。

2.2 less,sass等预处理器

这些预处理器封装了很多内置方法,在很多前端项目都会用到,相比较传统的css方案,他集成了变量、混入、嵌套、函数等特性,可以化繁为简,提升开发效率

2.3 原子化css方案

原子化css,诸如Tailwind CSS以及unocss,它可以帮助我们省去思考class名字的时间,与此同时,它的打包体积会减少一些,这里肯定会有人说,打包体积其实也就少了几kb,而且作为一个开发,给class取一个语意化名字也是基本的职责。原子化css的思想其实和bootstrap差不多,其实没有那么多新技术,无非都是站在巨人的肩膀上。

2.4 css in js

css in js这种方案在react里面比较常见,ant design源码底层也是用的css in js方案。

2.5 组件库css方案

不同组件库的css方案各有差异,其中element plus的css方案尤为特殊,element plus底层封装了一个hooks,接下来我们一起研究下element plus的技术方案。

三、useNamespace技术方案

这里我们从element plus的一个组件为切入点

如果所示,我们可以看到核心源码在button.vue中,其中style里面的css文件是用来按需引入用的,在button.vue里面可以看到调用了useNamespace这个方法

image.png

image.png 接下来我们细看这个源码 b代表block,用于生成块(Block)级别的类名 e代表element,用于生成元素(Element)级别的类名 m用于生成修饰符(Modifier)级别的类名 be用于同时生成块级后缀和元素级别的类名 em用于同时生成元素级别和修饰符级别的类名 bm用于同时生成块级后缀和修饰符级别的类名 is用于根据状态生成状态类名。

export const useNamespace = (
  block: string,
  namespaceOverrides?: Ref<string | undefined>
) => {
  const namespace = useGetDerivedNamespace(namespaceOverrides)
  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)
      : ''
  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,
    em,
    bm,
    bem,
    is,
    // css
    cssVar,
    cssVarName,
    cssVarBlock,
    cssVarBlockName,
  }
}

我们再细看下button组件里面对应的css样式,可以改文件中,结合sass的函数,它利用了Sass的高级功能,如变量、条件语句(mixins)、循环和函数,来创建一个可配置和可重用的按钮样式系统,为bem模式下不同类型类名封装了不同的方法,使用@include when()混合宏为按钮添加了特殊样式

image.png

image.png

总结

element plus是为数不多的将css bem架构落入到实践中的组件库,一方面可以实现css模块化以及命名清晰,更重要的是,这可以规避样式覆盖,尤其在开发和维护大型和复杂的前端项目变得更加容易和高效。