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成为element和modifier的前缀,而且element和modifier也不知道他们自己是围绕着哪个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
中文译文:http://www.w3cplus.com/preprocessor/pushing-bem-to-the-next-level-with-sass-3-4.html
     
     
     


