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

9,107 阅读17分钟

本文为稀土掘金技术社区首发签约文章,14天内禁止转载,14天后未获授权禁止转载,侵权必究

前言

在软件领域有很多的架构思想,通过不同的架构模式,可以让你的软件工程易拓展、易维护、易复用。同样在 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。

面向对象有三大特征:封装、继承、多态 ,在 OOCSS 中,主要应用到了面向对象的封装继承的思想。我们以掘金的下图这个部分来进行说明:

item.png

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

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

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

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

li.png

通过上图可以得知我们相当于继承基础类型 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 的典型应用:

container.png

实质上我们在写 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 的核心思想。

tab.png

那么整一个组件模块就是一个 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 即可。

icon-play.png

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

经典 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方法的页面结构类,主要用来做画面的 layout
  6. Components 层: UI 组件
  7. 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 样式架构中还有非常多少值得学习的知识,后续具体组件的实现,我们再继续探讨。

此文章的实现代码仓库:github.com/amebyte/ele…

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

本专栏文章:

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

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

3. ESLint 核心原理剖析

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

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

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

7. 组件实现的基本流程及 Icon 组件的实现

8. 为什么组件库或插件需要定义 peerDependencies

9. 组件开发中 Vue3 相关知识的应用与解析及 Button 组件的实现

10. CSS 系统颜色和暗黑模式的关系及意义

11. 深入理解组件库中SCSS和CSS变量的架构应用和实践

12. 组件 v-model 的封装实现原理及 Input 组件的核心实现

13. 深入理解 Vue3 的 v-model 及自定义指令的实现原理

14. React 和 Vue 都离不开的表单验证工具库 async-validator 之策略模式的应用

15. Form 表单的设计与实现

16. 组件库的打包原理与实践详解