跳到主要內容
黯羽輕揚每天積累一點點

BEM(Block-Element-Modifier)

免費2016-08-06#Front-End#Tool#js BEM#JavaScript Block-Element-Modifier#js 修饰器#BEM与Yeoman

BEM 是什麼,有什麼用,怎麼用

##一.BEM 簡介

BEM 自稱是前端開發方法論(Frontend Development Methodology),提供了包括命名規則、CSS/JS 模組化原則、構建工具在內的一套用於開發環節的方法

P.S. 強調開發環節是為了與 Yeoman 之類的東西區分開,BEM 想說的是方法,而不是工具

提供了一套命名規則(BEM 命名規則),主要用於模組化 CSS,但 BEM 中也用在了 JS 以及檔案命名等各個方面

CSS 中主要解決了這些問題:

  • 團隊協作中樣式命名(比如 class)衝突

用長 class 名解決,不使用結構化選擇器(如子選擇器、後代選擇器等等),類名自帶層級關係

  • 實現程式碼自己會說話(self-documenting code)的目標

語義化類名,提供更多資訊,例如元素名、功能、所屬元件名等等

  • 避免元件之間相互影響

不依賴結構化選擇器,全靠類名

  • 怎樣才能在同一 DOM 節點上組裝各種特性,同時避免重複程式碼(複製貼上)

Mix,例如 div.classA>div.classB 組合 Block A 和 Block B

這些解決方案在 [如何寫好 CSS](/articles/如何寫好 css/) 都有說明,如果只是定義了一套長類名格式的話,確實沒什麼意義,所以 BEM 還為這一套命名實現了框架和工具,確保開發過程中能用好用

##二。術語概念

(建議跳過,直接看紅色部分,官方解釋等於沒解釋)

###Block

邏輯和頁面功能都獨立的頁面元件,是一個可複用單元,特點如下:

  • 可以隨意嵌套組合

  • 可以放在任意頁面的任意位置,不影響功能和外觀

  • 可複用,介面可以有任意多個相同 Block 的例項

###Element

Block 的組成部分,依賴 Block 存在(出了 Block 就不能用)

###Modifier

[可選] 定義 Block 和 Element 的外觀及行為,就像 HTML 屬性一樣,能讓同一種 Block 看起來不一樣

###BEM entity

上面三個都是

###Mix

單一 DOM 節點上各個 BEM entity 構成的一個例項,特點如下:

  • 能把幾個 BEM entity 的行為、樣式結合起來,避免程式碼重複

  • 基於現有 BEM entity 語義化地建立新的介面元件

###BEM tree

用 BEM 對 web 頁面結構進行描述

###Block implementation

很多不同的技術決定了 BEM entity 的行為、外觀、測試、模板、文件、依賴描述、額外資料(例如圖片)等方面

###Implementation technology

用來實現一個 Block 的技術,可以是一種或者多種

###Block redefinition

在不同層面上通過給 Block 新增新特性來修改 Block 的實現

###Redefinition level

一系列 BEM entity 及其部分實現

使用中可看作邏輯層級,例如 project level、library level,前者可以重寫後者 Block 的功能及外觀

P.S.實際 是通過檔案目錄和按順序引入實現的層級隔離和重寫

需要關注 的點:

  • B(Block):表示模組,最小的可複用單元,功能獨立,可以嵌套、組合使用

  • E(Element):B 的組成部分

  • M(Modifier):表示 E 的狀態(不同狀態下的 E 有不同的功能和外觀),也是 B 的組成部分

  • BEM tree:用 BEM 術語描述頁面/專案結構

  • Block implementation:實現 Block 需要的東西,包括所有相關內容,比如 JS,CSS,image 等等

  • Redefinition level:可以看作邏輯層級,在高層可以重寫/擴充套件低層 Block(模組)

##三.BEM 命名規則

block-name__element-name_mod-name,例如 nav-menu__item_active

block-name 本身可能並不對應樣式,而只作為 Block 的邏輯名

BEM 只是提供一般方法,並沒有限定必須使用這種命名規則

##四.JavaScript for BEM

###命名規則同樣適用於 JS

JS 中,Modifier 用來表達 Block 或者 Element 的邏輯(CSS 中,Modifier 用來定義外觀),JS 通過一系列狀態來描述 Block 和 Element 的行為

不通過 class 來查詢元件,而是通過 Block 名(HTML 中,不只通過 class 唯一標識,也可以是標籤、屬性等等),例如:

document.querySelector('.button')
  .addEventListener('click', function() {
    document.querySelector('.popup').classList.toggle('popup_visible');
}, false);

// BEM Style(偽程式碼)
block('button').click(function() {
    block('popup').toggleMod('visible');
});

實際應用需要引入 i-bem.js

Modifier 可以設定 Block 的具體狀態,通過新增和移除 Modifier 來實現 Block 的狀態切換,對 Modifier 的操作會觸發事件,通知相關 Block。為了確保這套機制正確執行,不應該允許時隨意切換狀態或者直接修改某個 DOM 節點的 class,這些操作必須通過 BEM 提供的 helper 來實現(這種限制類似於 svn 本地倉���)

Modifier 對應狀態,狀態關聯功能。新增 Modifier 時,狀態發生變化,自動應用相應功能;移除 Modifier 時,相應功能會被自動移除

定義 Modifier 和狀態以及底層之間的關聯:

block('button').onSetMod({
    focused: {
        true: this.onFocus,
        '': this.onBlur
    }
});

遮蔽 了狀態之下的東西(可能是新增/移除 Modifier 引起狀態改變,或者是使用者行為觸發的),在程式設計層面需要關注的最小單元是狀態(Modifier)。可以通過給 Modifier 新增樣式來定義各個狀態的外觀,也可以通過 Redefinition level 來改變或者完全重寫 Block 的行為

###把程式碼分離到各檔案中,遵循檔案目錄結構規則

規則:

  • 每個 Block 的邏輯和可選的 Element 以及 Modifier 都放在單獨檔案裡

  • 每個元件的 JS 檔案目錄結構遵循 BEM 檔案目錄結構規則

示例:

logo/
logo.css   # Block's appearance
logo.tmpl  # Templates for generating the block's HTML representation
logo.js    # Dynamic behavior of the block in the browser

便於管理、複用、移植、支援 Redefinition level 和組合使用

###把邏輯分離到各個 Redefinition level

在不同的 Redefinition level 實現新 Block 可以繼承並擴充套件現有的 Block,或者完全重寫該 Block,例如:

// 完全重寫
block('button').onSetMod({
    focused: {
        true: this.someCustomOnFocused
    }
});
// 部分重寫
block('button').onSetMod({
    focused: {
        true: function() {
            this.__base.apply(this, arguments);
            this.someCustomOnFocused();
        }
    }
});

###原則

JS 中的 Block 與 DOM 節點沒有強對應關係,比如有的 Block 沒有 DOM 表現,只是單純的邏輯塊

Block 之間的互動通過事件訂閱來實現,比如:

  • 訂閱其它 Block 例項的事件

  • 訂閱 Modifier 變更

  • 直接呼叫其它 Block 的公開介面

  • 其它任何互動模式,比如 Event Channel(訊息機制)

Element 只能通過所屬的 Block 提供的 API 來訪問,不能 直接訪問某個 Block 中的 Element

##五。檔案目錄結構

程式碼被分解成獨立的小部分,便於開發獨立 Block,釋出前組裝起來進行優化

###特點

  • 把 Block implementation 分解到各個單獨的檔案中

便於管理(開發過程中)和移植(植入其它專案)

  • 可選的 Element 和 Modifier 都存放在各個單獨的檔案中

只引入相關的 Block implementation(按需載入)

  • 檔案按語義分組,而不是檔案型別

比如所有 Block 都放在 block 目錄下,以保證只引入需要的 Block

  • 專案被劃分成各 Redefinition level

為了消除重複程式碼,便於重寫/擴充套件現有 Library Block,當 Library Block 更新時,不會影響上層的重寫/擴充套件

###目錄結構示例

blocks/
    input/          # input block directory
        _type/                        # type modifier directory
            input_type_search.css     # Implementation of modifier type
                                      # with value search in CSS technology
        __box/                        # box element directory
            input__box.css
        input.css
        input.js
    button/         # button block directory
        button.css
        button.js
        button.png
    popup/          # popup block directory
        _target/
            popup_target.css           # Common code of  modifier target
            popup_target_anchor.css    # Modifier target with value anchor
            popup_target_position.css  # Modifier target with value position
        _visible/
            popup_visible.css          # Boolean modifier visible
    popup.css
    popup.js

檔名也遵循 BEM 命名規則,Modifier 之間的共享部分可以提出來,作為單獨檔案(popup_target.css

###Redefinition level 示例

library.blocks/
    button/
        button.css    # CSS implementation in the linked library (height 20px)
project.blocks/
    button/
        button.css    # Redefinition of CSS implementation (height 24px)

檔案存放在不同的層級上,分離開避免互相影響

或者按平臺分層:

common.blocks/
    button/
        button.css    # Generic CSS implementation of the button
desktop.blocks/
    button/
        button.css    # Desktop platform-specific button features
mobile.blocks/
    button/
        button.css    # Mobile platform-specific button features

build 過程會自動按層級引入,例如:

@import(common.blocks/button/button.css);    /* Generic CSS rules */
@import(desktop.blocks/button/button.css);   /* Desktop platform-specific */

##六.build

BEM 專案都有多級檔案結構,build 完成的工作是:

  • 把 Block 相關的單獨檔案整合起來

  • 按需引入 Block、Element 和 Modifier

  • 確保按正確順序引入(保證 Redefinition level)

當然,需要預先定義一些依賴資訊,比如:

  • 建立頁面時列出相關的 Block、Element 和 Modifier

  • 指明頁面中的 Block、Element 和 Modifier 的依賴關係

依賴宣告可以通過工具完成,比如能夠自動根據 HTML 中應用的 class,生成 BEM Tree,或者根據專案結構生成依賴資訊(非按需生成,所有東西都會被包進來,適用於確定專案目錄下所有東西都是必須的情況,例如類庫),關於依賴宣告方式的更多資訊,請檢視 Declarations in BEM

依賴引入方案與實現有關,比如,CSS 用 @import,JS 用 AMD 等方案

###build 結果

// build 前
blocks/           # Directory containing the blocks
bundles/          # Directory containing all build results
  hello/          # Directory for the hello page
  hello.decl.js   # List of BEM entities required for the hello page
An example of a post-build file structure of a BEM project:

// build 後
blocks/           # Directory containing the blocks
bundles/          # Directory containing all build results
  hello/          # Directory for the hello page
  hello.decl.js   # List of BEM entities required for the hello page
  hello.css       # Built CSS file for the hello page
  hello.js        # Built JavaScript file for thehello page

上例中 hello.decl.js 中定義了依賴的元件及其依賴關係

###build tool

官方提供的 build 工具是 ENB,據說很強大:

The BEM platform uses ENB, which is a tool suitable for building BEM projects of any level of complexity.

想要嘗試請檢視新手教程:Starting your own BEM project

###參考資料

評論

暫無評論,快來發表你的看法吧

提交評論