BEM在Sass3.4中的提升

发布于 大漠

本文由大漠根据Marcmintel的《Pushing BEM to the next level with Sass 3.4》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://medium.com/@marcmintel/pushing-bem-to-the-next-level-with-sass-3-4-5239d2371321

——作者:Marcmintel

——译者:大漠

Sass3.4增加了对父选择器的处理,主要是帮助你更好的处理选择器。我想这是为了更好的使用mixins来定义BEM

.test {
    @debug type-of(&); //打印出.test
}

.test li a{
    $selector: &;
    test: $selector;//打印出.test li a
}

Sass3.3中父选择器的问题

自从选择器能结合任何字符串时,父选择器在Sass3.3中就可以正常工作。这篇文章很好的阐述了BEM在Sass3.3能很好的运行,这也是其新特性之一。

虽然下面这段代码是有效的,但是直到现在都无法通过mixins来调用类:

.block {
    &__element {
        background: green;
    }
}

这样照样不能正常运行:

@mixin element($selector) {
    #{&}__element {
        @content;
    }
}
.block {
    @include element(element){
        //mixin ‘element’ 依旧不能连接其父选择器
    }
}

你无法创建一个mixins,让block成为elementmodifier的前缀,而且elementmodifier也不知道他们自己是围绕着哪个block

在Sass3.4中改进父选择器的可能性

在Sass3.4中打印出来的选择器可以是一个列表。所以我们可以这样使用mixins:

$elementSeparator: "__"; 
$modifierSeparator: "--";

@mixin b($block) {
   .#{$block} {
       @content; 
   }
}
@mixin e($element) {
    @at-root {
        #{&}#{$elementSeparator + $element} {
            @content;
        }
    }
}
@mixin m($modifier) {
    @at-root {
        #{&}#{$modifierSeparator + $modifier} {
            @content;
        }
    }
}

接下来可以这样使用:

@include b(test) {
    background: red;
    @include e(element) {
       font-size: 14px;
       @include m(big) {
           font-size: 18px;
       }
    };
    @include m(modifier) {
        color: blue;
    }
}

//output CSS
.test {
  background: red;
}
.test__element {
  font-size: 14px;
}
.test__element--big {
  font-size: 18px;
}
.test--modifier {
  color: blue;
}

然而我还遇到几个问题。第一个问题就是在修饰符modifier中嵌套一个元素element。例如下面的这段Sass代码:

@include b(test) {
    background: red;
    @include m(modifier) {
        color: blue;
        @include e(subelement) { 
            background: gray;
        }
    }
}
//output CSS
.test {
    background: red;
}
.test--modifier {
    color: blue;
}
.test--modifier__subelement { 
    background: gray;
}

这其实不是我想要的结果。实际上我想在修饰符下面嵌套一个元素(如.test--modifier .test__subelement)。我实现这样的目标,我们可以检测$modifierSeparator是不是含有选择器字符串(' — —')。如果存在,不想添加更多的后缀,改成一个嵌套选择器。实现这个可以创建一个函数来检测。

如果父选择器返回的是列表类型,需要先将其转换成一字符串。下面的函数就是用来做这件事:

@function selectorToString($selector) {
    $selector: inspect($selector); //cast to string
    $selector: str-slice($selector, 2, -2); //remove bracket
    @return $selector;
}

接下来把selectorToString()函数放在containsModifier()函数中,做一些字符串的处理:

@function containsModifier($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, $modifierSeparator) {
        @return true;
    }
    @else { 
        @return false;
    }
}

除此之外,还需要在block选择器外做分割,因为我们是想在block中追加element而不是modifier

@function getBlock($selector) {
    $selector: selectorToString($selector);
    $modifierStart: str-index($selector, $modifierSeparator) — 1;
    @return str-slice($selector, 0, $modifierStart);
}

将他们都放到一个函数,他接收的是一个选择器,并且检测其是否含有一个modifier,然后匹配对应选择器:

@mixin e($element) {
    $selector: &;
    $block: getBlock($selector);
    @if containsModifier($selector) {
        @at-root {
            #{$selector} {
                #{$block+$elementSeparator+$element} {
                    @content;
                }
            }
       }
    }
    @else {
        @at-root {
            #{$selector+$elementSeparator+$element} {
                @content;
            }
        }
    }
}

这个时候使用下面的代码:

@include b(test) {
   background: red;
   @include m(modifier) {
       color: blue;
       @include e(subelement) { 
           background: gray;
       }
   }
}

编译出来的CSS代码:

.test {
    background: red;
}
.test--modifier {
    color: blue;
}
.test--modifier .test__subelement {
    background: gray;
}

最后的代码如下:

$elementSeparator: '__';
$modifierSeparator: '--';

@function containsModifier($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, $modifierSeparator) {
        @return true;
    } @else {
        @return false;
    }
}

@function selectorToString($selector) {
    $selector: inspect($selector); //cast to string
    $selector: str-slice($selector, 2, -2); //remove brackets
    @return $selector;
}

@function getBlock($selector) {
    $selector: selectorToString($selector);
    $modifierStart: str-index($selector, $modifierSeparator) - 1;
    @return str-slice($selector, 0, $modifierStart);
}

@mixin b($block) {
    .#{$block} {
        @content;
    }
}

@mixin e($element) {
    $selector: &;
    @if containsModifier($selector) {
        $block: getBlock($selector);
        @at-root {
            #{$selector} {
                #{$block+$elementSeparator+$element} {
                    @content;
                }
            }
        }
    } @else {
        @at-root {
            #{$selector+$elementSeparator+$element} {
                @content;
            }
        }
    }
}

@mixin m($modifier) {
    @at-root {
        #{&}#{$modifierSeparator+$modifier} {
            @content;
        }
    }
}

实际运用如下:

@include b(block) {
    background: red;
    @include e(header){
        font-size: 14px;

        @include m(css) {
            font-size: 18px;
        }
    };
    @include m(book) {
        color: blue;

        @include e(kindlebook) {
            background: gray;
        }
    }
}

编译出来的CSS:

.block {
  background: red;
}
.block__header {
  font-size: 14px;
}
.block__header--css {
  font-size: 18px;
}
.block--book {
  color: blue;
}
.block--book .block__kindlebook {
  background: gray;
}

总结

在Sass3.3中可以很容易使用BEM,但会有一定的限制性,如今在Sass3.4中可以说BEM的使用更简单。可以让你的代码量很小,简洁易懂,而且还易于维护。我希望我的这个思路能给你带来一定的灵感,让给你带来方便,更希望你也能在些基础上创新。

如果你想自己动后写一遍,你可以参照示例码一回,也可以直击@Sassmeister

如果你有任何意或者想法,希望与我一起探讨,我会非常感激您。

译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!

如需转载,烦请注明出处:

英文原文:https://medium.com/@marcmintel/pushing-bem-to-the-next-level-with-sass-3-4-5239d2371321

中文译文:https://www.fedev.cn/preprocessor/pushing-bem-to-the-next-level-with-sass-3-4.html

Nike Air More Uptempo Basketball Women Shoes White Light Blue