團隊分享,Bem規範調研及實踐

前端小智發表於2021-12-07
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

背景

最近老大在維護別人的程式碼時,發現我們團隊寫的樣式各有種的想法及風格,這在後續維護會增加一定的難度,所以老大決定統一樣式的會名規範,所以就安排我去調研及實踐,下面是我調研的結果。

什麼是 BEM 命名規範

BEM 由 Yandex 團隊提出的一種前端 CSS 命名方法論。BEM 是 BlockElementModifier 的縮寫 ,其中B 表示塊(block)、E 表示元素(element)、M 表示修飾符(modifier)。

這三個部分通常使用__--連線。即: .塊__元素--修飾符{}

  • Block:代表了一個獨立的塊級元素,可以理解為功能元件塊。一個 Block 就是一個獨立的區塊,比如頭部是個 block,表單功能輸入框是一個block,block可大可小。
  • Element:是 Block 的一部分不能獨立來使用的,所有的 Element 都是與 Block 緊密關聯的。例如一個帶有 icon 的輸入框,那麼 這個 icon 就是這個輸入框 Block 的一個 Element,脫離了輸入框的 Block 那麼這個 icon 就沒有意義。
  • Modifier:是用來修飾 Block 或 Element,表示 block 或者 element 在外觀或行為上的改變。例如,上面提到的輸入框 Block,當滑鼠懸停時邊框高亮,那麼這個高亮的效果就應該用 Modifier 來實現。

上圖綠色表 block, 藍色表示 element , 紅色表示 modifier

為什麼要用 BEM?

效能

CSS 引擎查詢樣式表,對每條規則都按從右到左的順序去匹配,以下這段程式碼看起來很快,實際上很慢。通常我們會認為瀏覽器是這樣工作的:找到唯一ID元素ul-id —> 把樣式應用到li元素上。

事實上: 從右到左進行匹配,遍歷頁面上每個 li元素並確定其父元素

#ul-id li {}

所以不要讓你的css超過三層。

語義化

BEM 的關鍵是光憑名字就可以告訴其他開發者某個標記是用來幹什麼的。 通過瀏覽HTML程式碼中的class屬性,你就能夠明白模組之間是如何關聯的:有一些僅僅是元件,有一些則是這些元件的子孫或者是元素,還有一些是元件的其他形態或者是修飾符。

常規的命名法示例:

 <div class="article">
    <div class="body">
        <button class="button-primary"></button>
        <button class="button-success"></button>
    </div>
</div>

這種寫法從 DOM 結構和類命名上可以瞭解每個元素的意義,但無法明確其真實的層級關係。在 css 定義時,也必須依靠層級選擇器來限定約束作用域,以避免跨元件的樣式汙染。

使用了 BEM 命名方法的示例:

<div class="article">
    <div class="article__body">
        <div class="tag"></div>
        <button class="article__button--primary"></button>
        <button class="article__button--success"></button>
    </div>
</div>

通過 BEM 命名方式,模組層級關係簡單清晰,而且 css 書寫上也不必作過多的層級選擇。

怎麼用 ?

假設我們要實現這樣的一個卡片功能:

根據上面的設計圖,我們用 bem 方式來給對應 class 命名,如下所示:

<div class="card">
    <img class="card__img" src="./img.jpg" alt="">
    <div class="card__content">
        <ul class="card__list">
            <li class="card__item card__item--active">手機</li>
            <li class="card__item">移動市場</li>
            <li class="card__item">科技</li>
        </ul>
        <p class="card__desc">商化前端是一個很有活力的團隊,能學到很多知識,你心動了嗎?</p>
        <a class="card__link" href="#">詳細內容</a>
    </div>
</div>

對應的 CSS 結構:

.card{
  // 省略...
}
.card__img{
  // 省略...
}
.card__content {
  // 省略...
}
.card__list{
  // 省略...
}
.card__item {
  // 省略...
}
.card__item--active {
  // 省略...
}
.card__link{
  // 省略...
}
.card__link:hover{
  // 省略...
}

從上面的程式碼可以看出,我們樣式類沒有一點巢狀關係,巢狀關係都以從命名的方式來代替。

這裡剛開始使用 bem 的時候容易犯一個問題,就是把 ulli 的樣式寫成 card__content__listcard__content__list__item 因為這樣更能體現層級的關係。

這有悖BEM命名規範,BEM的命名中只包含三個部分,元素名只佔其中一部分,所以不能出現多個元素名的情況。這樣的約定可以防止當層級很深命名過長的問題。

上面我們每個樣式都要寫一遍 card,如果 card 換成一個比較長的單詞,這樣也太冗長了,這也是大家不太喜歡 bem 的一個原因,但這個 sassless 是很好的解決的,我們可以用 & 表示根元素,上面在 less 或 sass 中可以改成如下結構:

.card{
  // 省略...
  &__img{
  // 省略...
  }
  &__content {
  // 省略...
  }
  &__list{
  // 省略...
  }
  &__item {
  // 省略...
  }
  &__item--active {
  // 省略...
  }
  &__link{
  // 省略...
  }
  &__link:hover{
  // 省略...
  }
}

外掛的使用

eslint 校驗類似,stylelint 也有一個配置檔案.stylelintrc.js(還有其他格式的,這裡以js檔案為例)。

module.exports = {};

為了讓小夥伴編寫符合 Bem 的規範,這裡我們使用 stylelint-selector-bem-pattern 外掛,它結合了外掛 postcss-bem-linter 的規則,可用於校驗 BEM 命名規範。

module.exports = {
  plugins: [
    'stylelint-selector-bem-pattern'
  ],
  "rules": {
       'plugin/selector-bem-pattern': {
          // 選擇Preset Patterns,支援suit和bem兩種,預設suit規範;
          // 不管哪種都需要手動指定,因為該外掛未給源外掛預設指定
          'preset': 'bem',
          /**
           * 自定義模式規則
           * 指定組合的選擇器檢查規則,其實就是指定class名稱規則
           * 支援正則字串、返回正則的函式、包含2個選項引數的物件等寫法
           */
          componentSelectors: {
            // 只初始的選擇器規則(可以理解為外層的class規則)
            initial: '^\\.{componentName}(?:__[-a-z]+)?(?:--[a-z]+)?$',
            // 可以理解為外層以內的選擇器的規則,
            // 如果不指定,則跟initial同樣的規則,
            // 注意這裡配置的時候比上面少一個問號,
            // 是希望內層就不應該只有componentName作為選擇器了
            combined: '^\\.{componentName}(?:__[-a-z]+)(?:--[a-z]+)?$'
          },
          "utilitySelectors": "^\\.u-[a-z]+$",
          ignoreSelectors: ['^\\.el-', '/deep/', '>>>', '^\\.icon-'],
          ignoreCustomProperties: [],
        }
   }
}

配置完成後,為了能讓 VsCode 給出錯誤提示,我們需要在 VsCode 中新增 stylelint外掛。

最後, 就是 git commit 校驗

// package.json
{
    "husky": {
        "hooks": {
          "pre-commit": "lint-staged"
        }
    },
   "lint-staged": {
    "*.{vue,ts,tsx,js,jsx}": "eslint --fix",
    "*.{vue,css,less,sass,scss}": "stylelint --fix"
  },
}

這裡涉及到 husky 的使用,如果不懂的,可以自行谷歌瞭解理詳細的資訊。

實戰

配置完成後,我們就需要動手驗證一下了

首先,我們需要定義一個上下文,這樣外掛才知道對 CSS 進行校驗。

比如我們有如下的 html 結構:

<div class="form form--theme-xmas">
  <input class="form__input" />
  <input class="form__submit form__submit--disabled" type="submit" />
</form>

對應的 css 要這樣寫:

/** @define formWrapper */
.formWrapper{
  padding: 0 20px;
  box-sizing: border-box;
}
.formWrapper--line{
  display: none;
}
.formWrapper__form-item{
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

這裡 @define formWrapper 宣告瞭一個 block formWrapper, 表示樣式必須是 formWrapper 開頭,否則報錯。

如果有多個 block,我們只要多個 @define 宣告即可。

/** @define Foo */
.Foo {}

/** @define Bar */
.Bar {}

如果一個類不屬於任何的 block,我們又要怎麼做,才不會導致 styleint 報錯呢?這裡我們可以加 /** @end */ 表示 block 的結束。

/** @define form */
.form{
  display: flex;
}
.form--theme-blue{
  text-align: center;
}
/** @end */

.isActive{
  display: flex;
}

如果我們想忽略對某塊樣式進行校驗,可以使用下面的語法來忽略:

/** @define MyComponent */

.MyComponent {
  display: flex;
}

/* postcss-bem-linter: ignore */
.no-flexbox .Component {
  display: block;
}

總結

BEM 最難的部分之一是明確作用域是從哪開始和到哪結束的,以及什麼時候使用或不使用它。隨著不斷使用的經驗積累,你慢慢就會知道怎麼用,這些問題也不再是問題。技術無好壞,合適方最好。

參考:
https://www.jianshu.com/p/54b...
https://juejin.cn/post/684490...
https://segmentfault.com/a/11...
https://www.kancloud.cn/kancl...
https://juejin.cn/post/688530...

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

交流

有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588... 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

相關文章