BEM命名管理样式
前言
做 VUE 开发最常使用的就是 elementUI/elementPlus,使用中发现 element 的元素类名起的非常规整,结合自己开发中的困难(指不知道怎么起类名),看一下如何优雅的使用 BEM 命名
BEM 介绍
BEM 是一种针对 CSS 的命名规范的简称,B/E/M 分别代表,块(Block),元素(Element),修饰符(Modifier)的简写。
以 elementPlus 举例:
- 块(Block):可以理解为一个模块或者一个组件,块是规范中最大的单位,块之间是相互独立的,块之间不能相互影响。比如组件 el-table,el-dialog,el-form 等等。
- 元素(Element):可以理解为一个块(Block)中的元素,它是依赖上下文的,它被块(Block)包裹,但是每个元素(Element)之间也是相互独立的,它主要用来描述当前元素是什么。比如单选框 el-radio 中的 input 的那个点和单选框中的文字部分,el-dialog 中的 header,footer 等等。
- 修饰符(Modifier):可以理解为对元素或者块的修饰,所以它不能单独出现,必须要跟随块(Block)或者元素(Element)使用。比如按钮的 success 状态 el-button–success,alert 组件 success 状态的 el-alert–success
块(Block)元素(Element)修饰符(Modifier)之间的书写规范为
.block{}
.block__element{}
.block--modifier{}
.block__element--modifier{}
示例
<!-- 按钮 -->
<button class="el-button el-button--primary"></button>
<!-- dialog -->
<div class="el-dialog">
<header class="el-dialog__header"></header>
<div class="el-dialog__body"></div>
<footer class="el-dialog__footer"></footer>
</div>
!注意在使用中一个项目中块(Block)的命名应该是唯一的
什么时候使用 BEM
当我们使用 BEM 方法命名时,我们要知道哪些东西是应该使用 BEM 格式的。只有当需要明确关联性的模块关系时,才需要使用 BEM 格式。
使用混合拆分样式
在 BEM 中,位置和布局样式通过父级块来进行设置。这就需要通过混合组合块与元素,组合多个实体(块、元素、修饰符都被称作 BEM 实体)的表现与样式,同时不耦合代码。
示例:
<!-- top 块 -->
<div class="top">
<!-- search-form块混合top块的search-form元素 -->
<form class="search-form top__search-form">搜索</form>
</div>
这样就通过混合的方式把位置样式从块中剥离了,可以在.top__search-form 中设置表单的位置或浮动等样式,保持了 search-form 块的样式独立,对其完整样式代码进行了解耦。在传统的命名方式中,我们经常通过嵌套的方式,如.top .search-form 来对局部样式进行调整。但是这样做会改变选择器的权重。在 BEM 的思想中,保持选择器扁平和低权重是一个准则。
因此在使用 BEM 命名时需要格外注意遵循它的工作方式:
- 不在块里设置位置、布局相关的样式,只设置基本样式。
- 通过混合的方式,在作为父级块的元素时设置布局样式。
- 适时拆分元素为独立的块,解耦样式并形成新的命名空间。
开放封闭原则
开放封闭原则是所有面向对象原则的核心,是说软件实体应该是可扩展,而不可修改的。即对扩展是开放的,而对修改是封闭的。如果将这个原则应用到 BEM 的使用上,就是说我们应该使用 modifier 去拓展 block 或 element 的样式,而不应该去修改 block 或 element 的基础样式。
例如有两个按钮,如下所示:
<div> <button class="btn">正确</button> <button class="btn">错误</button> </div> .btn{ font-size:16px; color:white; border:none; padding:10px 8px; display: inline-block; background-color:green; }
如果要将名为错误的按钮的背景颜色改为红色,我们需要给.btn 加一个 modifier,而不是直接去修改.btn 的样式:
<div> <button class="btn">正确</button> <button class="btn btn--error">错误</button> </div> .btn{ font-size:16px; color:white; border:none; padding:10px 8px; display: inline-block; background-color:green; } .btn--error{ background-color:red; }
具体使用方法
useNameSpace
element 中使用了 useNameSpace 的工具方法,每个块(Block)也就是 el-table/el-form 这些是一个命名空间
先准备需要的参数
// 定义一个组件的命名前缀,element中是‘el’ const defaultNamespace = 'el' // 再定义一个描述组件状态的变量,例如:is-active const statePrefix = 'is-'
再定义一个 bem 方法返回符合规范的类名
const _bem = ( namespace: string, block: string, blockSuffix: string, element: string, modifier: string, ) => { let cls = `${namespace}-${block}` if (blockSuffix) cls += `-${blockSuffix}` if (element) cls += `__${element}` if (modifier) cls += `--${modifier}` return cls }
最后定义一个 useNameSpace 方法,返回 BEM 方法,用来判断补全
export const useNamespace = (block: string) => { const namespace = computed(() => defaultNamespace) const b = (blockSuffix = '') => _bem(unref(namespace), block, blockSuffix, '', '') const e = (element?: string) => element ? _bem(unref(namespace), block, '', element, '') : '' const m = (modifier?: string) => modifier ? _bem(unref(namespace), block, '', '', modifier) : '' const be = (blockSuffix?: string, element?: string) => blockSuffix && element ? _bem(unref(namespace), block, blockSuffix, element, '') : '' const em = (element?: string, modifier?: string) => element && modifier ? _bem(unref(namespace), block, '', element, modifier) : '' const bm = (blockSuffix?: string, modifier?: string) => blockSuffix && modifier ? _bem(unref(namespace), block, blockSuffix, '', modifier) : '' const bem = (blockSuffix?: string, element?: string, modifier?: string) => blockSuffix && element && modifier ? _bem(unref(namespace), block, blockSuffix, element, modifier) : '' // 这个是is-active这种状态描述的方法 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, } }
使用举例
// 例如开发到了dialog组件,在script中先定义命名空间,在html中直接使用即可 import { useNamespace } from './compo/useNamespace' const bs = useNamespace('dialog') ns.b() // el-dialog ns.b('overlay') // el-dialog-overlay ns.e('header') // el-dialog__header ns.m('theme-dark') // el-dialog--theme-dark ns.be('header','close') // el-dialog-header__close ns.em('footer','small') // el-dialog__footer--small ns.bm('footer','small') // el-dialog-footer--small ns.bem('footer','btn','primary') // el-dialog-footer__btn--primary ns.is('closeable') // is-closeable
其他
另外 element 还使用了 sass 的 mixins 功能为 css 样式提供了具体使用,/element-plus/packages/theme-chalk/src/mixins