一个元素的 BEM 方法是否可以根据块的不同而不同?

BEM approach for an element than can belong and look different depending on the Block?

假设我有一个样式化的复选框(想想 material 设计,其中有一点要实现所需的复选框样式)。哪个块负责修改 parent-dependent child 块?

示例组件 - 复选框

所以我有以下内容:

<div class="Checkbox">
    <label class="Checkbox__label">
        <input class="Checkbox__input" type="checkbox" checked=true />
        <span class="Checkbox__icon glyphicon glyphicon-ok"></span>
        <span class="Checkbox__text">{label}</span>
    </label>
</div>

我为基本复选框设计了块中的每个元素。在应用程序的上下文中,复选框块可以存在于许多其他块中(具有它们自己的 BEM 结构)。

其他块的例子

复选框在 "Compact Panel":

中的外观略有不同
<div class="Panel Panel--compact">
    <p>Disclaimer.. [text]</p>
    <Checkbox label="I Agree" /> 
</div> 

选项一 - Parent "knows" 关于 child

所以.. Compact Panel 应该是各种 children 块中的 "aware",并设置它们的样式,所以:

// Checkbox.scss 
.Checkbox {
    margin: 15px;
    // .. other
}


// Panel.scss
.Panel {
    &.Panel--compact {
        margin: 0;
        padding: 2px;
    }
    &.Panel--compact .Checkbox {
        margin: 0;
        padding: 1px;
    }
}

选项二 - Child "knows" 关于 parent

或者,面板的感知度为零,复选框检查 parent 范围。

// Checkbox.scss 
.Checkbox {
    margin: 15px;
    padding: 15px;
    // .. other
}
.Panel.Panel--compact .Checkbox {
    margin: 0;
    padding: 1px;
}


// Panel.scss
.Panel {
    &.Panel--compact {
        margin: 0;
        padding: 2px;
    }
}

选项三 - ?

也许还有其他选择。

我最近开始了一个 React 项目,我想分享我在样式化 React 组件方面的经验。

React 组件中的级联样式确实令人困惑。所以第一件事是尝试为每个元素编写 class 。如果你愿意,你可以使用定义组件的 main class 进行包装。

.panel {
  .checkbox {

  }
}

我用css-modules。这太棒了。此工具可以创建唯一的 classes,因此您无需担心在其他组件中重复相同的名称。您可以从他们的 git 页面了解更多信息,网上有很多文章。

如果您正在使用 sass 并且需要使用变量,您可以在不同的文件夹(比如 ../assets/variables.scss)中定义一个单独的 sass 文件,在您的组件 scss 文件中可以导入它并使用所有变量。 mixin 和函数也是如此。

在您的情况下,不必担心父子关系,为您需要设置样式的所有元素编写单个 classes。有时您可能需要使用父对象定位子对象。

A good read

根据this documentation,在BEM中应该避免使用嵌套选择器,但是"is appropriate for changing elements depending on the state of a block or its assigned theme"。这意味着您可以使用块的修饰符为其元素设置样式,但我找不到任何提及使用块的修饰符为其子块设置样式的内容。

我认为最好的方法是为 .Pannel.Checkbox 块添加修饰符,例如:.Panel--compact.Checkbox--context-compact。这样你就不会在它们之间创建依赖关系,这是 BEM 原则。

如果您无法根据其上下文向 .Checkbox 添加修饰符,我认为最接近的选项是第二个选项,子项的行为取决于其父项的状态。

通常使用 BEM,如果它们 看起来 不同,那么它们 不同的。

通常。

使用 BEM 处理上下文和状态有许多不同的选择。每个都有不同的优缺点,因此您使用哪个在很大程度上取决于您的用例。

我要提到的第一个选项是使用后代选择器。您已经确定了这个选择,并且正在 运行 解决 "where does the code belong?"

的常见问题

对于以下示例,我将依赖 LESS 语法,这只是为了让我更容易在代码中演示关系。


后代选择器

如果您要使用后代选择器,我建议将代码与 child 块.

分组 widget.less
.widget {
  &__container {
    ...
  }
  ...
}
checkbox.less
.checkbox {
  &__label {
    ...
  }
  ...

  // using inversion to override checkbox styles in a widget
  // this will render to `.widget .checkbox` instead of
  // `.checkbox .widget` due to the `&` placement
  .widget & {
  }
}

我建议将样式与内部块相关联的原因是样式会影响复选框,并且级联顺序很重要。

如果样式与 parent 关联,相对于 child 样式重新排序 parent 样式可能会对样式的呈现方式产生不利影响。

考虑这个包含顺序:

site.less
@import 'widget';
@import 'checkbox';

如果样式是小部件的一部分,它们可以被 checkbox.less 中具有相同特异性的选择器覆盖。

修饰符

我建议使用状态修饰符。我通常不认为位置或上下文是 "state",因此修饰符可能不合适。此外,同一元素上的多个修饰符可能难以推理,因此也难以设计样式。

假设您没有在 checkbox 块上使用修饰符,那么在面板中使用修饰符的情况下添加修饰符可能会更简单。

.checkbox {
  &__label {
    ...defaults...
  }
  ...defaults...

  &--alt {
    &__label {
      ...overrides...
    }
    ...overrides...
  }
}

当然,这需要针对面板中使用的特定情况更新标记,但它也让您可以在其他地方使用具有相同样式的复选框。

不同的选择器

我要重申我的第一点:如果他们看起来不同,他们不同的。

这并不意味着您必须从头开始选择复选框。 BEM 允许面向 object 的样式。想出一个新名字,扩展*复选框:

checkbox.less
.checkbox {
  &__label {
    ...
  }
  ...
}
复选框-2.less
@import (reference) 'checkbox';
.checkbox-2 {
  .checkbox;

  &__label {
    ...overrides...
  }
  ...overrides...
}

* 在 LESS 中,我为此使用了一个 mixin,因为它通常比使用语言的 :extend 特性更适合扩展和覆盖样式。请随意使用 :extend 功能,请注意选择器顺序很重要。

重构需求

有时我 运行 我 想要 使用后代选择器或修饰符,因为我需要碰撞一个块以便在容器中定位。

在这些情况下,我经常发现 容器本身 才是应该更改的。当我需要更新 child 以具有不同的内容时,我通常可以说它是容器:

  • 边距
  • 填充
  • 位置
  • 上、右、下、左
  • 宽度
  • 身高
  • 弹性

重构伴随着其他挑战,但是我经常最终使用容器 div 来规范化包含其他块的块的插入区域; YMMV.


tl;dr:我应该选择哪个?

你能(合理地)更新标记吗?

是: 如果您可以轻松更新标记以使用不同的 类,我建议 扩展您的 checkbox 块作为一个新区块。虽然命名事物很困难,所以一定要在某处记录下哪个是哪个。

NO: 如果您无法轻松更新 类,使用修饰符也不是一个好的选择。我建议跳过那个,回到好的 后代选择器。在 BEM 中,您确实 想要避免后代选择器,但有时它们是完成这项工作的正确工具。

这是“正确”的方式:

我很惊讶为什么接受的答案没有提到 BEM“混合”。 在这些情况下,即当一个块在另一个块中使用时应该更改样式时,the official BEM documentation recommends 在同一 DOM 节点上使用块和元素 class。

因此,在您的情况下,这将是标记:

<div class="Panel Panel--compact">
    <p>Disclaimer.. [text]</p>
    <div class="Checkbox Panel__checkbox" /> <!-- Here is the change -->
</div> 

这将是 CSS/SCSS:

// Checkbox.scss 
.Checkbox {
    margin: 15px;
    padding: 15px;
    // .. other
}


// Panel.scss
.Panel {
    &__checkbox {
        margin: 0;
        padding: 2px;
    }
}

真的就是这么简单。其他答案不必要地使这个问题过于复杂,我不知道为什么,BEM 文档对这个问题有明确的答案。您还可以在网上找到许多解释我刚才描述的技术的文章。