element-ui源碼解讀-基於scss的bem方法的實現

 導讀

首先來看一個bem命名示例css

.el-message-box{}
.el-message-box__header{}
.el-message-box__header--active{}

若是使用已經封裝好的bem方法的話,那麼能夠寫成sass

@include b('message-box') {
    @include e('header') {
        @include m('active');
    }
}

接下來咱們來看一下bem方法是如何實現的函數

 bem方法解析

首先咱們找到style/mixins/config.scss文件,裏面定義了以下幾個變量spa

$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';

而後咱們再找到style/mixins/config.scss文件,找到b,e,m方法debug

/* BEM
 -------------------------- */
@mixin b($block) {
  $B: $namespace+'-'+$block !global;
  .#{$B} {
    @content;
  }
}
@mixin e($element) {
  $E: $element !global;
  $selector: &;
  $currentSelector: "";
  @each $unit in $element {
    $currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};
  }
  @if hitAllSpecialNestRule($selector) {
    @at-root {
      #{$selector} {
        #{$currentSelector} {
          @content;
        }
      }
    }
  } @else {
    @at-root {
      #{$currentSelector} {
        @content;
      }
    }
  }
}
@mixin m($modifier) {
  $selector: &;
  $currentSelector: "";
  @each $unit in $modifier {
    $currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
  }
  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

代碼量很少,邏輯也不復雜,可是語法有點晦澀難懂,接下來咱們一個一個解釋
!global 變量提高,將局部變量提高爲全局變量,在其餘函數體內也能訪問到此變量
@at-root將父級選擇器直接暴力的改爲根選擇器調試

.header{
    @at-root {
        .content{color:red}
    }
}

編譯爲code

.header{}
.content{color:red}

#{}插值,能夠經過 #{} 插值語法在選擇器和屬性名中使用 SassScript 變量ip

$name: foo;
$attr: border;
p.#{$name} {
      #{$attr}-color: blue;
}

編譯爲ci

p.foo {
  border-color: blue;
}

在大多數狀況下,這種作可能還不如使用直接變量來的方便,但使用 #{}意味着靠近它的運算符都將被視爲純CSSelement

p {
   $font-size: 12px;
   $line-height: 30px;
   font: #{$font-size}/#{$line-height};
}

編譯爲

p.foo {
  font:12px/30px;
}

如今咱們在從新看一下b方法,定義了一個全局變量,拼接了一下字符串,邏輯很簡單,e方法稍微複雜點,調用了一個hitAllSpecialNestRule方法(判斷父級選擇器是否包含'--','is',':'),hitAllSpecialNestRule定義在style/mixins/function文件中,代碼以下:

@import 'config';
/* BEM support Func
 -------------------------- */
// inspect Returns a string representation of $value
//@debug inspect("Helvetica"); unquote('"Helvetica"')
@function selectorToString($selector) {
    $selector: inspect($selector);
    $selector: str-slice($selector, 2, -2);
    @return $selector;
}
// 判斷選擇器(.el-button__body--active) 是否包含 '--'
@function containsModifier($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, $modifier-separator) {
        @return true;
    } @else {
        @return false;
    }
}
// 判斷選擇器(.el-button__body.is-active) 是否包含 'is'
@function containWhenFlag($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, '.' + $state-prefix) {
        @return true;
    } @else {
        @return false;
    }
}
//  判斷選擇器(.el-button__body:before) 是否包含僞元素(:hover)
@function containPseudoClass($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, ':') {
        @return true;
    } @else {
        @return false;
    }
}
// hit:命中 nest:嵌套
@function hitAllSpecialNestRule($selector) {
    @return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}

inspect:以字符串的形式返回表達式

@debug meta.inspect(10px 20px 30px); // unquote("10px 20px 30px")
@debug meta.inspect(("width": 200px)); // unquote('("width": 200px)')
@debug meta.inspect(null); // unquote("null")
@debug meta.inspect("Helvetica"); // unquote('"Helvetica"')

Maps不能轉換爲純CSS。做爲變量的值或參數傳遞給CSS函數將會致使錯誤。使用inspect($value) 函數以產生輸出字符串,這對於調試 maps 很是有用。
從新回到e方法中,也是先拼字符串,而後再判斷父級class是否存在嵌套關係,而後輸出結果。

.container {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
        }
    }
}
.container--fix {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
        }
    }
}

編譯爲

.container .el-button {
  width: 200px;
  height: 200px;
}
.el-button__body {
  color: #ccc;
}
.container--fix .el-button {
  width: 200px;
  height: 200px;
}
.container--fix .el-button .el-button__body {
  color: #ccc;
}

最後一個e方法,流程和b一致,區別在拼接currentSelector字符串時,使用了$父級選擇器,尚未使用全局變量B+全局變量E來拼接,由於結構不必定是B-E-M,有多是B-M。最後附上完整的編譯結果

.container {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success');
        }
    }
}
.container--fix {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success');
        }
    }
}

編譯爲

.container .el-button {
  width: 200px;
  height: 200px;
}
.el-button__body {
  color: #ccc;
}
.container--fix .el-button {
  width: 200px;
  height: 200px;
}
.container--fix .el-button .el-button__body {
  color: #ccc;
}

scss完整代碼以下,能夠在scss在線編譯
中編譯調試

$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
@function selectorToString($selector) {
    $selector: inspect($selector);
    $selector: str-slice($selector, 2, -2);
    @return $selector;
}
// 判斷選擇器(.el-button__body--active) 是否包含 '--'
@function containsModifier($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, $modifier-separator) {
        @return true;
    } @else {
        @return false;
    }
}
// 判斷選擇器(.el-button__body.is-active) 是否包含 'is'
@function containWhenFlag($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, '.' + $state-prefix) {
        @return true;
    } @else {
        @return false;
    }
}
//  判斷選擇器(.el-button__body.is-active) 是否包含僞元素(:hover)
@function containPseudoClass($selector) {
    $selector: selectorToString($selector);
    @if str-index($selector, ':') {
        @return true;
    } @else {
        @return false;
    }
}
// hit:命中 nest:嵌套
@function hitAllSpecialNestRule($selector) {
    @return containsModifier($selector) or containWhenFlag($selector) or containPseudoClass($selector);
}
@mixin b($block) {
    $B: $namespace + '-' + $block !global;
    .#{$B} {
        @content;
    }
}
@mixin e($element) {
    $E: $element !global;
    $selector: &;
    $currentSelector: '';
    @each $unit in $element {
        $currentSelector: #{$currentSelector + '.' + $B + $element-separator + $unit + ','};
    }
    @if hitAllSpecialNestRule($selector) {
        @at-root {
            #{$selector} {
                #{$currentSelector} {
                    @content;
                }
            }
        }
    } @else {
        @at-root {
            #{$currentSelector} {
                @content;
            }
        }
    }
}
@mixin m($modifier) {
    $selector: &;
    $currentSelector: '';
    @each $unit in $modifier {
        $currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ','};
    }
    @at-root {
        #{$currentSelector} {
            @content;
        }
    }
}
@mixin configurable-m($modifier, $E-flag: false) {
    $selector: &;
    $interpolation: '';
    @if $E-flag {
        $interpolation: $element-separator + $E-flag;
    }
    @at-root {
        #{$selector} {
            .#{$B + $interpolation + $modifier-separator + $modifier} {
                @content;
            }
        }
    }
}
@mixin spec-selector($specSelector: '', $element: $E, $modifier: false, $block: $B) {
    $modifierCombo: '';
    @if $modifier {
        $modifierCombo: $modifier-separator + $modifier;
    }
    @at-root {
        #{&}#{$specSelector}.#{$block + $element-separator + $element + $modifierCombo} {
            @content;
        }
    }
}
@mixin meb($modifier: false, $element: $E, $block: $B) {
    $selector: &;
    $modifierCombo: '';
    @if $modifier {
        $modifierCombo: $modifier-separator + $modifier;
    }
    @at-root {
        #{$selector} {
            .#{$block + $element-separator + $element + $modifierCombo} {
                @content;
            }
        }
    }
}
@mixin when($state) {
    @at-root {
        &.#{$state-prefix + $state} {
            @content;
        }
    }
}
@mixin extend-rule($name) {
    @extend #{'%shared-' + $name};
}
@mixin share-rule($name) {
    $rule-name: '%shared-' + $name;
    @at-root #{$rule-name} {
        @content;
    }
}
@mixin pseudo($pseudo) {
    @at-root #{&}#{':#{$pseudo}'} {
        @content;
    }
}
.container {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success');
        }
    }
}
.container--fix {
    @include b('button') {
        width: 200px;
        height: 200px;
        @include e('body') {
            color: #ccc;
            @include m('success');
        }
    }
}
相關文章
相關標籤/搜索