likes
comments
collection

Element Plus 组件库相关技术揭秘:6. CSS 架构模式之 BEM 在组件库中的实践

作者站长头像
站长
· 阅读数 9

前言

在软件领域有很多的架构思想,通过不同的架构模式,可以让你的软件工程易拓展、易维护、易复用。同样在 CSS 工程当中,我们也需要使用架构思想,如果 CSS 没有使用架构思想的话,就会存在 CSS 代码极度混乱,难复用、难拓展、难维护等问题。特别是如果一个系统页面极度复杂的情况下,没有对 CSS 代码进行一个规划的话,那么后期维护 CSS 代码则堪称灾难。

所以我们需要像其他编程语言那样通过一些架构模式进行提高 CSS 代码的健壮性和可维护性。本文将探讨 Element Plus 组件库中的 CSS 架构思想。

CSS 设计模式之 OOCSS

我们如果对 Element Plus 的 CSS 架构稍微有些了解的话,就知道 Element Plus 的 CSS 架构使用了 BEM 设计模式,而 BEM 是 OOCSS 的一种实现模式,可以说是进阶版的 OOCSS。那么什么是 OOCSS 呢?

OOCSS 的全称为 Object Oriented CSS (面向对象的 CSS),它让我们可以使用向对象编程思想进行编写 CSS。

Element Plus 组件库相关技术揭秘:6. CSS 架构模式之 BEM 在组件库中的实践

图中画红色的部分,可以看成是有四个容器组成的,每个容器里面的内容又不一样。那么每个容器都有相同的样式,那么我们就可以进行封装

把每一个容器封装成一个叫 itemclass ,因为它们都有一些共同的样式。

.item {
    position: relative;
    margin-right: 20px;
    font-size: 13px;
    line-height: 20px;
    color: #4e5969;
    flex-shrink: 0;
}

然后如果我们需要对它们每一项进行拓展的话,那么我们只需要在原来的样式基础上进行新增一个 class,再针对这个 class 写不同的样式即可,这样达到继承原来基础部分的样式进行拓展自己独有的样式。

Element Plus 组件库相关技术揭秘:6. CSS 架构模式之 BEM 在组件库中的实践

通过上图可以得知我们相当于继承基础类型 item 后,然后分别拓展出 浏览 view点赞 like评论 comment更多 more 的 CSS 内容。

.item.view {
    // 浏览
}
.item.like {
    // 点赞
}
.item.comment {
    // 评论
}
.item.more {
    // 更多
}

通过这种模式就大大增加了 CSS 代码的可维护性,可以在没有修改源代码的基础上进行修正和拓展。同时通过上面的例子,我们可以引出 OOCSS 的两大原则:

  • 容器(container)与内容(content)分离
  • 结构(structure)与皮肤(skin)分离

例如在 Element Plus 组件库中就有两个经典的布局组件 Container 布局容器Layout 布局,这是 OOCSS 的典型应用:

Element Plus 组件库相关技术揭秘:6. CSS 架构模式之 BEM 在组件库中的实践

实质上我们在写 Vue 组件的时候,就是在对 CSS 进行封装,这也是 OOCSS 的实践方式之一。

<el-button class="self-button">默认按钮</el-button>
<style lang="stylus" rel="stylesheet/stylus" scoped>
.self-button {
    color: white;
    margin-top: 10px;
    width: 100px;
}
</style>

例如上述代码,我们的 Element Plus 组件库已经对 el-button 组件的样式进行了封装,但我们还可以基于 el-button 组件的样式进行拓展我们符合我们项目 UI 的样式,这就是典型的封装与继承

OOCSS 强调重复使用类选择器,避免使用 id 选择器,最重要的是从项目的页面中分析抽象出“对象”组件,并给这些对象组件创建 CSS 规则,最后完善出一套基础组件库。这样业务组件就可以通过组合多个 CSS 组件实现综合的样式效果,这体现了 OOCSS 的显著优点:可组合性高

OOCSS 为我们提供了一种编写 CSS 代码的思维模型或者说方法论,后续则演化出更加具体的一种实现模式,也就是 BEM。

CSS 设计模式之 BEM

BEM 是由 Yandex 团队提出的一种 CSS 命名方法论,即 Block(块)、Element(元素)、和 Modifier(修改器)的简称,是 OOCSS 方法论的一种实现模式,底层仍然是面向对象的思想。下面我们从 Element Plus 的 Tabs 组件进行讲解 BEM 的核心思想。

Element Plus 组件库相关技术揭秘:6. CSS 架构模式之 BEM 在组件库中的实践

那么整一个组件模块就是一个 Block(块),classname 取名为:el-tabs。Block 代表一个逻辑或功能独立的组件,是一系列结构、表现和行为的封装。 其中每个一个切换的标签就是一个 Element(元素),classname 取名为:el-tabs__item。Element(元素)可以理解为块里的元素。 Modifier(修改器)用于描述一个 Block 或者 Element 的表现或者行为。例如我们需要对两个 Block(块) 或者两个 Element(元素)进行样式微调,那么我们就需要通过 Modifier(修改器),Modifier(修改器)只能作用于 Block(块)或者 Element(元素),Modifier(修改器)是不能单独存在的。

例如按钮组件的 classname 取名为 el-button,但它有不通过状态譬如:primary、success、info,那么就通过 Modifier(修改器)进行区分,classname 分别为: el-button--primary、el-button--success、el-button--info。从这里也可以看出 BEM 本质上就是 OOCSS,基础样式都封装为 el-button,然后通过继承 el-button 的样式,可以拓展不同的类,例如:el-button--primary、el-button--success、el-button--info。

BEM 规范下 classname 的命名格式为:

block-name__<element-name>--<modifier-name>_<modifier_value>
  • 所有实体的命名均使用小写字母,复合词使用连字符 “-” 连接。
  • Block 与 Element 之间使用双下画线 “__” 连接。
  • Mofifier 与 Block/Element 使用双连接符 “--” 连接。
  • modifier-name 和 modifier_value 之间使用单下画线 “_” 连接。

当然这些规则并不一定需要严格遵守的,也可以根据你的团队风格进行修改。

在 OOCSS 中,我们通过声明一个选择器对一个基础样式进行封装的时候,这个选择器是全局的,当项目庞大的时候,这样就容易造成影响到其他元素。通过 CSS 命名方法论 BEM,则在很大程度上解决了这个问题。因为 BEM 同时规定 CSS 需要遵循只使用一个 classname 作为选择器,选择器规则中既不能使用标签类型、通配符、ID 以及其他属性,classname 也不能嵌套,此外通过 BEM 可以更加语义化我们的选择器名称。BEM 规范非常适用于公共组件,通过 BEM 命名规范可让组件的样式定制具有很高的灵活性。

此外通过 BEM 的命名规范可以让页面结构更清晰。

<form class="el-form">
    <div class="el-form-item">
        <label class="el-form-item__label">名称:</label>
        <div class="el-form-item__content">
            <div class="el-input">
                <div class="el-input__wrapper">
                    <input class="el-input__inner" />
                </div>
            </div>
        </div>
    </div>
</form>

我们以 Element Plus 的 Form 表单的 HTML 结构进行分析,我们可以看到整个 classname 的命名是非常规范的,整个 HTML 的结构是非常清晰明了的。

通过 JS 生成 BEM 规范名称

在编写组件的时候如果通过手写 classname 的名称,那么需要经常写 el-__--,那么就会变得非常繁琐,通过上文我们可以知道 BEM 命名规范是具有一定规律性的,所以我们可以通过 JavaScript 按照 BEM 命名规范进行动态生成。 命名空间函数是一个 hooks 函数,类似这样的 hooks 函数在 Element Plus 中有非常多,所以我们可以在 packages 目录下创建一个 hooks 模块(具体创建项目过程可参考本专栏的第二篇《2. 组件库工程化实战之 Monorepo 架构搭建》),进入到 hooks 目录底下初始化一个 package.json 文件,更改包名:@cobyte-ui/hooks。文件内容如下:

{
  "name": "@cobyte-ui/hooks",
  "version": "1.0.0",
  "description": "Element Plus composables",
  "license": "MIT",
  "main": "index.ts",
  "module": "index.ts",
  "unpkg": "index.js",
  "jsdelivr": "index.js",
  "types": "index.d.ts",
  "peerDependencies": {
    "vue": "^3.2.0"
  },
  "gitHead": ""
}

接着在 hooks 目录下再创建一个 use-namespace 目录用于创建 BEM 命名空间函数,再在 hooks 目录下创建一个 index.ts 文件用于模块入口文件。

index.ts 文件内容:

export * from './use-namespace'

本项目的 GitHub 地址:github.com/amebyte/ele…

首先引入一个命名空间的概念,所谓命名空间就是加多一个命名前缀。

import { computed, unref } from 'vue'
// 默认命名前缀
export const defaultNamespace = 'el'

export const useNamespace = (block: string) => {
    // 命名前缀也就是命名空间
    const namespace = computed(() => defaultNamespace)
    return {
        namespace,
    }
}

通过加多一个命名前缀,再加上 BEM 的命名规范就可以大大降低我们组件的 classname 与项目中的其他 classname 发生名称冲突的可能性。

通过前文我们知道 BEM 的命名规范就是通过一定的规则去书写我们的 classname,在 JavaScript 中则表现为按照一定规则去拼接字符串。

BEM 命名字符拼接函数:

// BEM 命名字符拼接函数
const _bem = (
  namespace: string,
  block: string,
  blockSuffix: string,
  element: string,
  modifier: string
) => {
  // 默认是 Block
  let cls = `${namespace}-${block}`
  // 如果存在 Block 前缀,也就是 Block 里面还有 Block,例如:el-form 下面还有一个 el-form-item
  if (blockSuffix) {
    cls += `-${blockSuffix}`
  }
  // 如果存在元素
  if (element) {
    cls += `__${element}`
  }
  // 如果存在修改器
  if (modifier) {
    cls += `--${modifier}`
  }
  return cls
}

这里值得注意的是 Block 也有可能有前缀,也就是 Block 里面还有 Block,例如:el-form 下面还有一个 el-form-item

通过 BEM 命名字符拼接函数,我们就可以自由组合生成各种符合 BEM 规则的 classname 了。

export const useNamespace = (block: string) => {
  // 命名前缀也就是命名空间
  const namespace = computed(() => defaultNamespace)
  // 创建块 例如:el-form
  const b = (blockSuffix = '') =>
    _bem(unref(namespace), block, blockSuffix, '', '')
  // 创建元素 例如:el-input__inner
  const e = (element?: string) =>
    element ? _bem(unref(namespace), block, '', element, '') : ''
  // 创建块修改器 例如:el-form--default
  const m = (modifier?: string) =>
    modifier ? _bem(unref(namespace), block, '', '', modifier) : ''
  // 创建前缀块元素 例如:el-form-item
  const be = (blockSuffix?: string, element?: string) =>
    blockSuffix && element
      ? _bem(unref(namespace), block, blockSuffix, element, '')
      : ''
  // 创建元素修改器 例如:el-scrollbar__wrap--hidden-default
  const em = (element?: string, modifier?: string) =>
    element && modifier
      ? _bem(unref(namespace), block, '', element, modifier)
      : ''
  // 创建块前缀修改器 例如:el-form-item--default
  const bm = (blockSuffix?: string, modifier?: string) =>
    blockSuffix && modifier
      ? _bem(unref(namespace), block, blockSuffix, '', modifier)
      : ''
  // 创建块元素修改器 例如:el-form-item__content--xxx
  const bem = (blockSuffix?: string, element?: string, modifier?: string) =>
    blockSuffix && element && modifier
      ? _bem(unref(namespace), block, blockSuffix, element, modifier)
      : ''
  // 创建动作状态 例如:is-success is-required
  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}` : ''
  }

  return {
    namespace,
    b,
    e,
    m,
    be,
    em,
    bm,
    bem,
    is,
  }
}

最后我们就可以在组件中引入 BEM 命名空间函数进行创建各种符合 BEM 命名规范的 classname 了,例如:

  • 创建块 el-form、
  • 创建元素 el-input__inner、
  • 创建块修改器 el-form--default、
  • 创建前缀块元素 el-form-item、
  • 创建元素修改器 el-scrollbar__wrap--hidden-default、
  • 创建动作状态 例如:is-success is-required

具体创建代码使用代码如下:

import {
  useNamespace,
} from '@cobyte-ui/hooks'
// 创建 classname 命名空间实例
const ns = useNamespace('button')

然后就可以在 template 中进行使用了:

<template>
  <button
    ref="_ref"
    :class="[
      ns.b()
    ]"
  >按钮</button>
<template>

通过 SCSS 生成 BEM 规范样式

我们在本专栏的第二篇《2. 组件库工程化实战之 Monorepo 架构搭建》中已经创建一个样式主题的目录 theme-chalk。现在我们接着在这个目录下创建组件样式代码,我们在 theme-chalk 目录下创建一个 src 目录,在 src 目录下创建一个 mixins 目录。

Element Plus 的样式采用 SCSS 编写的,那么就可以通过 SCSS 的 @mixin 指令定义 BEM 规范样式。在 mixins 目录下新建三个文件:config.scss、function.scss、mixins.scss。 其中 config.scss 文件编写 BEM 的基础配置比如样式名前缀、元素、修饰符、状态前缀:

$namespace: 'el' !default; // 所有的组件以el开头,如 el-input
$common-separator: '-' !default; // 公共的连接符
$element-separator: '__' !default; // 元素以__分割,如 el-input__inner
$modifier-separator: '--' !default; // 修饰符以--分割,如 el-input--mini
$state-prefix: 'is-' !default; // 状态以is-开头,如 is-disabled

在 SCSS 中,我们使用 $+ 变量名:变量 来定义一个变量。在变量后加入 !default 表示默认值。给一个未通过 !default 声明赋值的变量赋值,此时,如果变量已经被赋值,不会再被重新赋值;但是如果变量还没有被赋值,则会被赋予新的值。

mixins.scss 文件编写 SCSS 的 @mixin 指令定义的 BEM 代码规范。

定义 Block:

@mixin b($block) {
  $B: $namespace + '-' + $block !global;

  .#{$B} {
    @content;
  }
}

$B 表示定义一个一个变量,$namespace 是来自 config.scss 文件中定义的变量, !global 表示其是一个全局变量,这样就可以在整个文件的任意地方使用。#{} 字符串插值,类似模板语法。通过 @content 可以将 include{} 中传递过来的内容导入到指定位置。

定义 Element:

@mixin e($element) {
  $E: $element !global;
  $selector: &;
  $currentSelector: '';
  @each $unit in $element {
    $currentSelector: #{$currentSelector +
      '.' +
      $B +
      $element-separator +
      $unit +
      ','};
  }

  @if hitAllSpecialNestRule($selector) {
    @at-root {
      #{$selector} {
        #{$currentSelector} {
          @content;
        }
      }
    }
  } @else {
    @at-root {
      #{$currentSelector} {
        @content;
      }
    }
  }
}

首先定义一个全局变量 $E,接着定义父选择器 $selector,再定义当前的选择器 $currentSelector,再通过循环得到当前的选择器。接着通过函数 hitAllSpecialNestRule(hitAllSpecialNestRule 函数在 mixins 目录的 function.scss 文件中) 判断父选择器是否含有 Modifier、表示状态的 .is- 和 伪类,如果有则表示需要嵌套。@at-root 的作用就是将处于其内部的代码提升至文档的根部,即不对其内部代码使用嵌套。

定义修改器:

@mixin m($modifier) {
  $selector: &;
  $currentSelector: '';
  @each $unit in $modifier {
    $currentSelector: #{$currentSelector +
      $selector +
      $modifier-separator +
      $unit +
      ','};
  }

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

这个非常好理解,就是定义了父选择器变量 $selector 和 当前选择器变量 $currentSelector,并且当前选择器变量初始值为空,再通过循环传递进来的参数 $modifier,获得当前选择器变量 $currentSelector 的值,再定义样式内容,而样式内容是通过 @contentinclude{} 中传递过来的内容。

定义动作状态:

@mixin when($state) {
  @at-root {
    &.#{$state-prefix + $state} {
      @content;
    }
  }
}

选择器就是 config.scss 文件中的变量 $state-prefix 加传进来的状态变量,而样式内容是通过 @contentinclude{} 中传递过来的内容。

接着我们再看下上面定义 Element 的时候说到的 hitAllSpecialNestRule 函数,这个函数是定义在 mixins 目录下的 function.scss 文件中。function.scss 文件内容如下:

@use 'config';

// 该函数将选择器转化为字符串,并截取指定位置的字符
@function selectorToString($selector) {
  $selector: inspect(
    $selector
  ); // inspect(...) 表达式中的内容如果是正常会返回对应的内容,如果发生错误则会弹出一个错误提示。
  $selector: str-slice($selector, 2, -2); // str-slice 截取指定字符
  @return $selector;
}
// 判断父级选择器是否包含'--'
@function containsModifier($selector) {
  $selector: selectorToString($selector);

  @if str-index($selector, config.$modifier-separator) {
    // str-index 返回字符串的第一个索引
    @return true;
  } @else {
    @return false;
  }
}
// 判断父级选择器是否包含'.is-'
@function containWhenFlag($selector) {
  $selector: selectorToString($selector);

  @if str-index($selector, '.' + config.$state-prefix) {
    @return true;
  } @else {
    @return false;
  }
}
// 判断父级是否包含 ':' (用于判断伪类和伪元素)
@function containPseudoClass($selector) {
  $selector: selectorToString($selector);

  @if str-index($selector, ':') {
    @return true;
  } @else {
    @return false;
  }
}
// 判断父级选择器,是否包含`--` `.is-`  `:`这三种字符
@function hitAllSpecialNestRule($selector) {
  @return containsModifier($selector) or containWhenFlag($selector) or
    containPseudoClass($selector);
}

通过上述代码我们就可以知道 hitAllSpecialNestRule 函数是如何判断父选择器是否含有 Modifier、表示状态的 .is- 和 伪类的了。

测试实践 BEM 规范

接下来,我们要把上面实现的 BEM 规范应用到真实组件中,通过写一个简易的测试组件进行测试实践。首先我们的样式是基于 SCSS 所以我们需要安装 sass。

我们在根目录下执行:

pnpm install sass -D -w

接着我们把上新建的 hooks 模块也安装到根项目上:

我们在根目录下执行:

pnpm install @cobyte-ui/hooks -D -w

而 theme-chalk 模块,我们在本专栏的第二篇的《2. 组件库工程化实战之 Monorepo 架构搭建》已经进行了安装,这里就不用再进行安装了。

我们在 packages 目录下的 components 目录创建一个 icon 目录,再创建以下目录结构:

├── packages
│   ├── components
│   │   ├── icon
│   │   │   ├── src
│   │   │   │   └── icon.vue
│   │   │   └── index.ts
│   │   └── package.json

index.ts 文件内容:

import Icon from './src/icon.vue'
export default Icon

icon.vue 文件内容:

<template>
  <i :class="bem.b()">
    <slot />
  </i>
</template>

<script setup lang="ts">
import { useNamespace } from '@cobyte-ui/hooks'
const bem = useNamespace('icon')
</script>

我们通过导入上面使用 JS 生成 BEM 规范名称 hooks 函数,然后创建对应的命名实例 bem,然后生成对应的 Block 块的 classname。接下来,我们把这个测试组件渲染到页面上,看看具体生成的效果。

我们去到 play 目录下的 src 目录中的 App.vue 文件中把上面写的测试组件进行引入:

<template>
  <div>
    <c-icon>Icon</c-icon>
  </div>
</template>
<script setup lang="ts">
import CIcon from '@cobyte-ui/components/icon'
import '@cobyte-ui/theme-chalk/src/index.scss'
</script>
<style scoped></style>

我们也把 theme-chalk 目录中的样式也进行了导入。接着我们在 theme-chalk 目录下的 src 目录新建一个 icon.scss 文件,文件内容如下:

@use 'mixins/mixins' as *;
@include b(icon) {
  background-color: red;
  color: #fff;
}

这里我们可以看到 SCSS 的 @mixin@include 的用法: @mixin 用来定义代码块、@include 进行引入。

我们需要在 theme-chalk 目录下的 src 目录中的 index.scss 中导入 icon.scss 文件。

index.scss 文件内容:

@use './icon.scss';

这样我们就实现了所有文件的闭环,最后我们把 play 项目运行起来,看看效果,要运行 play 项目,我们专栏的前面的文章中已经说过了,就是在根目录下执行 pnpm run dev 即可。

Element Plus 组件库相关技术揭秘:6. CSS 架构模式之 BEM 在组件库中的实践

我们也看到已经成功实现了渲染并和如期一样,那么其他样式的测试,我们将在后续具体的组件实现上再进行测试。

经典 CSS 架构 ITCSS

本小节我们继续通过学习经典 CSS 架构 ITCSS 来对比学习 Element Plus 的样式系统架构。我们可以向一些经典的 CSS 架构去学习取经,看看人家是怎么做架构的,从而可以在我们做自身的 CSS 架构的时候可以带来一些的启发,然后取长补短,从而可以更加优化自身的 CSS 架构。

ITCSS 基于分层的概念把项目中的样式分为七层,分别如下:

  1. Settings 层: 维护一些包含字体、颜色等的变量,主要是变量层。
  2. Tools 层: 工具库,例如 SCSS 中的 @mixin 、@function
  3. Generic 层: 重置和/或标准化样式等,例如 normalize.css、reset.css,主要解决浏览器的样式差异
  4. Elements 层: 对一些元素进行定制化的设置,如 H1 标签默认样式,A 标签默认样式等
  5. Objects 层: 类名样式中,不允许出现外观属性,例如 Color,遵循OOCSS方法的页面结构类
  6. ,主要用来做画面的 layout
  7. Components 层: UI 组件
  8. Trumps 层: 实用程序和辅助类,能够覆盖前面的任何内容,也就是设置 important! 的地方

ITCSS 不是一个框架,只是组织样式代码的一种方案,ITCSS 的分层越在上面的分层,被复用性就越广,层的权重是层层递进,作用范围却是层层递减。除了 ITCSS 之外还有其他一些 CSS 架构,比如 SMACSS 、ACSS 等,但它们的核心思想并不是放之四海而皆准的,但是它维护项目样式的思想却是值得借鉴的。我们可以不完全遵守它们的规则,可以根据我们的项目需要进行删减或者保留。

那么根据上面 ITCSS 架构思想,Element Plus 也设置了 Settings 层 ,在 theme-chalk/src/common 目录下的 var.scss 文件中就维护着各种变量。我们上文中实现的 BEM 规范的 mixins 目录下的 function.scss、mixins.scss 等则是 Tools 层Generic 层 主要解决浏览器的样式差异,这些工作应该在具体的项目中进行处理,而 Element Plus 只是一个第三方的工具库,所以 Element Plus 在这一层不进行设置。Elements 层 主要是对一些基础元素拓展一些样式,从而让我们的网站形成一套自己的风格,例如对 H1 标签默认样式,A 标签默认样式的设置。Element Plus 则在 theme-chalk/src 目录下的 reset.scss 文件进行了设置。Objects 层Components 层 其实就是 OOCSS 层,也就是我们上文所说的 BEM 样式规范,又因为组件库的样式使用一般都分为全量引入和按需引入,所以就根据组件名称分别设置各自样式文件进行维护各自的样式Trumps 层 在 Element Plus 中也是没有的,同样是因为 Element Plus 只是一个第三方工具库,权重是比较低,所以不需要设置权重层。

总结

我们先从什么是 OOCSS 开始,OOCSS 主要运用了传统编程类中的封装继承的特性,OOCSS 为我们提供了一种编写 CSS 代码的思维模型或者说方法论,后续则演化出更加具体的一种实现模式,也就是 BEM。

接着我们从 Element Plus 的 Tabs 组件进行讲解 BEM 的核心思想。BEM 本质上就是 OOCSS,通过 BEM 可以更加语义化我们的选择器名称。BEM 规范非常适用于公共组件,通过 BEM 命名规范可让组件的样式定制具有很高的灵活性。

接着我们介绍如何通过 JS 生成 BEM 规范名称。在编写组件的时候如果通过手写 classname 的名称,那么需要经常写 el-__--,那么就会变得非常繁琐,通过上文我们可以知道 BEM 命名规范是具有一定规律性的,所以我们可以通过 JavaScript 按照 BEM 命名规范进行动态生成。

接着学习如何通过 SCSS 生成 BEM 规范样式,并学习了 SCSS 的一些核心知识。

之后写了一个 demo 组件进行验证我们所写的 BEM 规范代码。

最后我们通过学习经典 CSS 架构 ITCSS 的思想,从而更加深入理解 Element Plus 的 CSS 架构设置思想。我们可以看到 Element Plus 的 CSS 架构并不是单一的使用了具体那一种架构,而是融合了多种架构的思想。

所以所谓的 CSS 架构,它们的核心思想并不是放之四海而皆准的,但是它维护项目样式的思想却是值得借鉴的。我们可以不完全遵守它们的规则,可以根据我们的项目需要进行删减或者保留。

Element Plus 的 CSS 样式架构中还有非常多少值得学习的知识,后续具体组件的实现,我们再继续探讨。

欢迎关注本专栏,了解更多 Element Plus 组件库知识

本专栏往期文章:

1. Vue3 组件库的设计和实现原理

2. 组件库工程化实战之 Monorepo 架构搭建

3. ESLint 核心原理剖析

4. ESLint 技术原理与实战及代码规范自动化详解

5. 从终端命令解析器说起谈谈 npm 包管理工具的运行原理

6. CSS 架构模式之 BEM 在组件库中的实践