BEM 规范及实战快速生成

这篇文章我将基于Yike-Design组件库的开发具体讲述BEM规范

在此基础上我将介绍在实际开发过程中运用的命名空间方法以快速生成符合BEM规范的类名

1. 什么是BEM

BEM(Block, Element, Modifier)是一种前端编码规范,用于命名 HTML 和 CSS 中的类和选择器。它旨在提供一种一致的方式来组织和命名代码,使其易于理解、扩展和维护。

以下是 BEM 规范的基本原则:

  1. 块(Block) :块是一个独立的可重用组件,它代表一个完整的实体。它是整个 BEM 结构中最高层级的部分,应该有一个唯一的类名。 示例:.yk-button、.yk-navbar
  2. 元素(Element) :元素是块的组成部分,不能独立存在。它们依赖于块的上下文,并且有属于块的类名作为前缀。 示例:.button__text、.navbar__item
  3. 修饰符(Modifier) :修饰符用于修改块或元素的外观、状态或行为。它们是可选的,可以单独使用或与块或元素的类名结合使用。 示例:.yk-button--large、.yk-upload__item--active

2. 为什么我们要使用BEM

  1. 提供一种一致的命名约定,使团队可以更轻松地理解和维护代码
  2. 促进可重用性和模块化开发
  3. 减少 CSS 的特异性(specificity)问题,避免组件间样式冲突

bem这套规范其实非常符合"组件化"、"模块化"的直觉,用"块"维护一个组件,用"块儿"中的元素维护一个独立的组成部分,这种具备层级的结构其实也是我开发过程中经常采用的思路,后续在开发过程中也应当采用此规范,并根据我们的公共方法进行快速的类名定义

3. 实例

理论说完了,让我们用一个实例来具体说说我在开发时的具体的结构设计和思路,这边我挑一个并不是我写的组件Checkbox尝试一下~

image.png

 

Step1. 确定组件结构和"块"的划分

显然,从组件设计上讲,我们会倾向于用checkbox-group.vue和checkbox(-item).vue这两个组件来分别维护多选组和多选选项,那么我们在编码时,便确定了yk-checkbox和yk-checkbox-group这两个顶层"块儿"

  • checkbox.vue
html复制代码<div class="yk-checkbox">
    ...
</div>
  • checkbox-group.vue
html复制代码<div class="yk-checkbox-group">
    ...
</div>

Step2. 区分元素

对于一个chekbox而言,可以划分的元素就应该是左边的选框box和右边的选项文字 label 了,我们可以将选框内部的icon图标作为选框内部的一个元素

块儿与元素应当用 __ 进行链接,并使用小写字母和短横线进行命名

例如 yk-checkbox__box-container

  • checkbox.vue
html复制代码<div class="yk-checkbox">
    <div class="yk-checkbox__box">
        <div class="yk-checkbox__icon"></div>
    </div>
    <div class="yk-checkbox__label">
        {{ name }}
    </div>
</div>

Step3. 确定修饰器

接下来我们要确定各个元素应当具备几个修饰器、每个修饰器又有几种具体的状态

从设计图上来看, box 这个元素具有禁用(disabled),选中状态(status)这两个修饰

其中,disabled的取值是 true和false 我们通常的做法是取值为true的时候为他加上这个修饰

而status的状态有三种,分别是未选(normal),选中(active),半选(indeterminate)

修饰器采用--与元素和块直接链接

我们用两个实例来看一下具体的类名应该是啥样

image.png

 

  • checkbox.vue
html复制代码<div class="yk-checkbox">
    <div class="yk-checkbox__box yk-checkbox__box--normal">
        <div class="yk-checkbox__icon"></div>
    </div>
    <div class="yk-checkbox__label">
        {{ name }}
    </div>
</div>

image.png

 

  • checkbox.vue
html复制代码<div class="yk-checkbox">
    <div class="yk-checkbox__box yk-checkbox__box--active yk-checkbox__box--disabled">
        <div class="yk-checkbox__icon"></div>
    </div>
    <div class="yk-checkbox__label">
        {{ name }}
    </div>
</div>

4. 快速生成BEM

当然,在项目的实战过程中,实现这套命名规范的方式有很多,常规的可能是采用计算属性为class赋值,比如我们最初的yk-button为了实现根据props传入的内容区分样式,我们曾经的代码是

javascript复制代码const ykButtonClass = computed(() => {
  return {
    'yk-button': true,
    'yk-button--loading': props.loading,
    'yk-button--long': props.long,
    'yk-button--disabled': props.disabled || props.loading,
    [`yk-button--${props.status}`]: props.status,
    [`yk-button--${props.type}`]: props.type,
    [`yk-button--${props.size}`]: props.size,
    [`yk-button--${props.shape}`]: props.shape,
  }
})

而采用命名方法后,类名的定义可以这样去实现

html复制代码  <button
    :class="[
      bem([type, status, shape, size], {
        loading: loading,
        long: long,
        disabled: disabled,
      }),
    ]"
  >
      ...
  </button>

个人认为这套方法在开发效率上还是有一定的优势的,我们在后文中继续介绍它的使用方法和源码解析

5. 使用方法

引入并定义块

javascript复制代码import { createCssScope } from '../../utils/bem'
const bem = createCssScope('button')

后续,我们可以在模板中采用bem()方法快速地定义附带前缀的各个元素和修饰器

定义元素

还记得我们前文的例子么,现在类名的定义可以这样去实现了

image.png

 

html复制代码<div :class="bem()">
    <div :class="bem('box',[status],{disabled})">
        <div :class="bem('icon')"></div>
    </div>
    <div :class="icon('label')">
        {{ name }}
    </div>
</div>

const disabled = true;
const status = 'active'

可以总结为以下几句话

  • 根元素用bem()定义块
  • 用 bem('element')定义子元素
  • 多种状态的修饰器用列表 bem([])
  • 状态为布尔类型的修饰器用对象 bem({})
  • 一个节点只用一个bem()

6. 源码分析

源码我先干上来

javascript复制代码
const createModifier = (prefixClass: string, modifierObject?: BEMModifier) => {
  let modifiers: string[] = [];
  if (isArray(modifierObject)) {
    modifiers = modifierObject
      .map((modifier) => {
        if (!modifier) return '';
        return `${prefixClass}--${modifier}`;
      })
      .filter(Boolean);
  } else if (isObject(modifierObject)) {
    modifiers = Object.entries(modifierObject).map(([modifier, value]) => {
      if (!value) return '';
      return `${prefixClass}--${modifier}`;
    });
  }
  return modifiers;
};

export const createCssScope = (prefix: string, identity = 'yk') => {
  const prefixClass = `${identity}-${prefix.replace(identity, '')}`;

  return (
    elementOrModifier?: BEMElement | BEMModifier,
    modifier?: BEMModifier,
    modifierLater?: BEMModifier,
  ) => {
    if (!elementOrModifier) return prefixClass;
    if (isString(elementOrModifier)) {
      const element = `${prefixClass}__${elementOrModifier}`;
      if (!modifier) return element;
      return [
        element,
        ...createModifier(element, modifier),
        ...createModifier(element, modifierLater),
      ];
    }
    return [
      prefixClass,
      ...createModifier(prefixClass, elementOrModifier),
      ...createModifier(prefixClass, modifier),
    ];
  };
};

  • 首先是初始化这块儿,拿到传入的组件名拼上yk-前缀作为我们的顶层块,后面采用返回的函数生成的类名都会拼上这个块的前缀
javascript复制代码
export const createCssScope = (prefix: string, identity = 'yk') => {
  const prefixClass = `${identity}-${prefix.replace(identity, '')}`;
};

  • 返回的函数提供了三个入参,这边是为了区分针对块的修饰和针对元素的修饰,若首个入参为字符串,则此函数用于元素,采用后面两个入参作为修饰器,返回函数的出参均为一个类名的列表,为了避免多个修饰器存在重复元素,我们可以通过解构过滤掉
javascript复制代码if (!elementOrModifier) return prefixClass;
    if (isString(elementOrModifier)) {
      ...
      // 元素修饰
      return [
        element,
        ...createModifier(element, modifier),
        ...createModifier(element, modifierLater),
      ];
    }
    // 块修饰
    return [
      prefixClass,
      ...createModifier(prefixClass, elementOrModifier),
      ...createModifier(prefixClass, modifier),
    ];
  • createModifier这边有两个入参,即元素前缀和修饰器对象,最终返回的都是一个列表,列表的每个内容即一个修饰器对应的类名
javascript复制代码const createModifier = (prefixClass: string, modifierObject?: BEMModifier) => {
  let modifiers: string[] = [];
   modifiers = obj.map()=>{
       return `${prefixClass}--${modifier}`;
   }
  return modifiers;
};
  • 当然,这边我们区分了两种传入的方式,如上文,列表为多状态修饰,对象为单状态修饰,其中的filter和逻辑!部分的代码是为了过滤掉键值为undefined的修饰
javascript复制代码 if (isArray(modifierObject)) {
    modifiers = modifierObject
      .map((modifier) => {
        if (!modifier) return '';
        return `${prefixClass}--${modifier}`;
      })
      .filter(Boolean);
  } else if (isObject(modifierObject)) {
    modifiers = Object.entries(modifierObject).map(([modifier, value]) => {
      if (!value) return '';
      return `${prefixClass}--${modifier}`;
    });
  }

7. 总结

综上,Yike-Design组件库中关于bem的介绍和实现方法到这里就结束了

有建议和指导欢迎随时指出~

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值