서론
BEM 은 yandex (러시아 최대의 검색 엔진) 가 실천에서 정리해낸 것으로,整套의 것은 매우 커 보입니다. 공식 문서는 항상 methodology, the BEM world 와 같이 크고 공허하게 들리는 단어를 강조하여, 사람을 겁내기 쉽습니다
만약 由此及彼,우리가 보편적으로 받아들이는 이론에서 BEM 으로 매핑하면, BEM 에 신비한 것은 아무것도 없고, 모듈화, 엔지니어링의 이념과 MVC 등의 디자인 패턴을 결합했을 뿐임을 알 수 있습니다. 그들이 스스로 the BEM world 라고 칭하는 것은, 독자적인 세계관을 구축하고, 문서를 통해 타인에게 강가하려는 것입니다 (《天才在左 疯子在右》중의 정절과 유사). 너무 신경 쓸 필요는 없습니다
BEM(Block-Element-Modifier) 에서는 BEM 의 이념을 간단히 소개하고, 모듈, 상태, 로직 계층화를 재정의하는 일련의 용어를 제안하며, 가능한 한 결합을 풀고, 높은 유지보수성과 엔지니어링의 미를 추구하는 것을 목적으로 합니다. 예를 들어:
-
엄격한 컴포넌트 간 인터랙션 제한 (Block 간 의존을 최대한 감소)
-
강제 통일 명명 규범 (환경 레벨의 강제약, 개발 인원은 반드시 준수)
-
유연한 로직 계층화 (Redefinition level 를 통해)
-
상태에 기반한 CSS 와 JS 구조 (Modifier)
매우 완벽해 보이지만, 많은의문이 존재합니다:
-
Q1.모듈 로드, 패키징 릴리스는 편리한가?
-
Q2.임의의 다층 레벨의 재정의를 서포트하는가?
-
Q3.build 는 제어 가능한가?
-
Q4.
body{margin: 0; font-size: 12px;}와 같은 base 스타일은 어디에 배치하는가? -
Q5.글로벌 로직은 어디에 배치하는가?
-
Q6.동적 데이터는 어떻게 처리하는가? (동적으로
page.bemjson.js를 수정? 아니면 동적으로 컴포넌트를 생성?) -
Q7.크로스 컴포넌트 비즈니스는 어떻게 실현하는가?
BEM 모드를 시작할 경우, 반드시 이러한 문제들에 직면합니다. 다음 목표는 BEM 을 동화 (由此及彼의 매핑) 하고, 답을 구하는 것입니다
一.페이지
新手教程 에서 입수한 test-project 구조는 다음과 같습니다:
common.blocks/ #라이브러리 모듈
desktop.blocks/ #프로젝트 모듈
desktop.bundles/ #프로젝트 build 결과
libs/ #서드파티 모듈
node_modules/
bower.json #bower 로 lib 를 관리
favicon.ico
package.json
README.md
README.ru.md
그 중에서 desktop.bundles/index/index.bemjson.js 는 프로젝트 홈페이지입니다. 맞습니다, xxx.html 이 아닙니다. 페이지는 BEM 중의 BEMJSON 에 대응하며, 페이지를 쓰는 것은 BEMJSON 설정을 쓰는 것입니다. 다음과 같습니다:
module.exports = {
block : 'page',
title : 'BEM-컴포넌트화',
favicon : '/favicon.ico',
head : [
{ elem : 'meta', attrs : { name : 'viewport', content : 'width=device-width, initial-scale=1' } },
{ elem : 'css', url : 'index.min.css' }
],
scripts: [{ elem : 'js', url : 'index.min.js' }],
content : [{
block : 'header',
content : [
'header is fixed'
]
}, {
block : 'goods',
goods : [{
title: 'Apple iPhone 4S 32Gb',
image: 'http://www.ayqy.net/image/logo.png',
price: '259',
url: 'http://www.ayqy.net/'
},
...]
}, {
block : 'footer',
content : [
'footer content goes here'
]
}]
};
page.bemjson.js 는 1 개의 페이지를 기술하며, 컴파일을 거쳐 page.html 을 생성합니다. 일상 개발은 이러한 설정을 쓰는 것으로, 기능, 스타일 등은 모두 컴포넌트에 캡슐화됩니다
구체적인 컴파일 프로세스는 다음과 같습니다:
-
pageName.bemjson.js는 각 페이지의 HTML 구조 및 데이터 모델을 선언 -
BEMHTML template engine 은 BEMJSON 을 해석하여 페이지 HTML 과 관련 리소스를 생성
템플릿 엔진에게, BEMJSON 은 컴포넌트 조직 구조, 즉 소위 BEMTree 를 제공합니다
페이지를 개발하는 프로세스는 컴포넌트를 조합하여 사용하는 것으로, 如果没有 또는 적절하지 않은 경우, 새로운 컴포넌트를 생성하거나, 상층에서 컴포넌트를 재정의할 수 있습니다. 각 컴포넌트에는 엄격한 제약이 있어, 재사용 가능함을 보증합니다. 이는매우 높은 컴포넌트 산출률을 의미합니다
二.컴포넌트
컴포넌트는 페이지를 조립하는 요소로, 각 컴포넌트는 독립된 파일 디렉토리에 격리되어 있습니다. 예를 들어:
my-block/
css/styl #CSS 파일/stylus 파일
js #JS 파일
bemhtml.js #HTML 구조를 정의
deps.js #의존항목을 선언
bemjson.js #테스트 페이지를 기술, 유닛 테스트에 사용
CSS
CSS 명명 공간은 항상 도덕적 제약이었지만, BEM 은 그것을강제 룰로 바꾸었습니다. 예를 들어:
.goods {
display: -webkit-flex;
display: -moz-flex;
display: -ms-flex;
display: -o-flex;
display: flex;
text-align: center;
padding-left: 0;
}
.goods__item {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
-o-flex: 1;
flex: 1;
list-style: none;
}
.goods__item_new {
background-color: #ff0;
}
쓰는 것은 확실히 괴롭고, 보는 것도 아름답지 않지만, 표의 명확합니다. 200 행의 CSS 에서 목표 행을 찾는 데 얼마나 걸립니까? 2 초면 충분합니다. 목표 행의 정확한 클래스명이 절대 명확하고, 자자손손의多处 수정의 문제가 존재하지 않기 때문입니다
P.S.B-name__E-name_M-name 룰은硬性規定이 아니며, 완전히 설정 가능합니다. 예를 들어 팀이 B_name--E_name-M_name 을 결정해도, 전혀 문제없습니다
P.S.BEM 개발 환경은 기본적으로 stylus 를 도입
JS
여기의 js 는普通の $('#id').on(...) 이 아니라, 상태에 기반한 것으로, i-bem.js 가 서포트를 제공합니다. 다음과 같습니다:
modules.define('box', ['i-bem__dom'], function(provide, BEMDOM) {
provide(BEMDOM.decl('box', {
onSetMod : {
'closed': {
'yes': function() {
this.domElem.animate({
'margin-left' : '54em'
}, 1000);
},
'': function() {
this.domElem.css({
'margin-left' : 'auto'
});
}
}
}
}));
});
의미는 box_close 가 yes 상태일 때, 왼쪽으로 접는 애니메이션을 실행 (맞습니다, i-bem__dom 은 jQuery 에 의존), box_close 상태가 존재하지 않을 때, 정상으로 돌아갑니다
$('id') 와 같은 DOM 검색도, $el.on(...) 와 같은 DOM Events 처리도 보지 못했습니다. 이렇게 하는 것은 JS 가 직접 DOM 에 액세스하여 뷰를 업데이트하는 것을 피하기 위함으로, 프론트엔드 MVC 의 기본 원칙입니다. 언제든지 어디서나 글로벌 DOM 검색으로 뷰를 업데이트하는 것으로 인한 컴포넌트 결합을 피하기 위해, i-bem.js 는 제한된 DOM API 를 제공합니다. 다음과 같습니다:
// Inside the block — On DOM nodes nested in the DOM node of the current block.
findBlocksInside([elem], block)
findBlockInside([elem], block)
// Outside the block — On DOM nodes that the current block DOM node is a descendent of.
findBlocksOutside([elem], block)
findBlockOutside([elem], block)
// On itself — On the same DOM node where the current block is located. This is relevant when multiple JS blocks are located on a single DOM node (a mix).
findBlocksOn([elem], block)
findBlockOn([elem], block)
BEMHTML
BEMHTML 은 BEMJSON 과 유사하며, HTML 구조를 선언하는 데 사용됩니다 (속칭: 템플릿). 예를 들어:
block('goods')(
tag()('ul'),
content()(function() {
return this.ctx.goods.map(function(item){
return [{
elem: 'item',
elemMods: {
new: item.new ? 'yes' : undefined
},
content: [{
elem: 'title',
content: {
block: 'link',
mix: [{block: 'goods', elem: 'link'}],
url: item.url,
content: item.title
}
}, {
block: 'box',
content: {
block: 'image',
url: item.image
}
}, {
elem: 'price',
content: item.price
}]
}];
});
}),
elem('item')(
tag()('li')
),
elem('title')(
tag()('h3')
),
elem('price')(
tag()('span')
)
);
실제로는 템플릿을 정의하고, 데이터 정의는 페이지 중 (page.bemjson.js) 에 있으며, 컴파일 시에 조립합니다
전통적인 템플릿 (jade, ejs) 과 大同小異하며,无非은 2 가지 일을 설명:
-
HTML 구조
-
데이터装入 룰
deps
package 의존과 유사합니다. 다음과 같습니다:
({
mustDeps: [],
shouldDeps: [
{ block: 'link' },
{ block: 'box' }
]
})
마찬가지로, 의존 설정은 build 시에 의존 컴포넌트가 이미 도입되어 있음을 확보하기 위한 것입니다
三.로직 계층
일련의 컴포넌트가 1 개의 로직 계층을 형성하며, BEM 은 이를 Redefinition level 라고 부릅니다. 다음과 같습니다:
common.blocks/
attach/
button/
checkbox/
checkbox-group/
control/
control-group/
dropdown/
icon/
image/
...
각 컴포넌트에는 독립된 디렉토리가 있으며, common.blocks 는 그것들이 소속하는 로직 층입니다
폴더イコール로직 층? 어떻게做到的?
매우簡単, build 시에 순서대로 로드하며, 나중에 오는 것이 자연스럽게 먼저 온 것을 커버합니다. 다음과 같습니다:
// .enb/make.js
levels = [
{ path: 'libs/bem-core/common.blocks', check: false },
{ path: 'libs/bem-core/desktop.blocks', check: false },
{ path: 'libs/bem-components/common.blocks', check: false },
{ path: 'libs/bem-components/desktop.blocks', check: false },
{ path: 'libs/bem-components/design/common.blocks', check: false },
{ path: 'libs/bem-components/design/desktop.blocks', check: false },
{ path: 'libs/j/blocks', check: false },
'common.blocks',
'desktop.blocks'
];
이것이 로직 계층의 실현으로, 즉 BEM Redefinition level 의 비밀입니다
컴포넌트는 폴더로 계층화되며, 내장의 common.blocks, 프로젝트 커스터마이즈의 desktop.blocks 가 있으며, 프로젝트 레벨에서 내장 컴포넌트를 재정의하거나, 새로운 레벨을 추가하여 프로젝트 레벨 컴포넌트를 재정의할 수도 있습니다
四.컴포넌트 간 인터랙션
위에서 소개한 컴포넌트는 격리 제한이 많아 보입니다 (로직 계층, 컴포넌트 독립 디렉토리). 만약 모든 로직을 컴포넌트에 배포하면, 물론 문제없지만, 그것은 불가능합니다. 항상 몇 가지 로직은 컴포넌트 인터랙션이 필요합니다
컴포넌트를 밀결합시키고 싶지 않지만, 컴포넌트 간이 인터랙션할 수 있게 하고 싶다면, 생각할 필요도 없이, 분명이벤트機制입니다
BEM 중의 컴포넌트 인터랙션에 관한 것은 4 조가 있습니다. 다음과 같습니다:
-
BEM Event 구독 처리
-
다른 Block 인스턴스의 공개 방법 및 Block 의 정적 방법을 직접 호출
-
다른 Block 의 상태를 검출
-
event channel
BEM 은 2 종류의 이벤트를 제공하며, DOM Event 와 BEM Event 로, 대응 API 는 다르며, 각각 bindTo/unbindFrom() 와 on/un() 입니다. 도덕적 각도에서 제약을 두었습니다:
컴포넌트를 건너 DOM Event 를 사용하지 마십시오. DOM Event 는 Block 내부에서만 사용
다른 Block 의 인스턴스 방법 및 정적 방법을 직접 호출할 수 있습니다. 그렇다면, 어떻게 인스턴스 오브젝트를 얻을 수 있는가? 앞에서 언급한 적이 있습니다:
// Outside the block — On DOM nodes that the current block DOM node is a descendent of.
findBlocksOutside([elem], block)
findBlockOutside([elem], block)
인스턴스를 찾은 후, 자연스럽게 hasMod/getMod() 로 그 상태를 검출할 수 있습니다
마지막의 event channel 은 관찰자 패턴 (기본 구조는 Subject 가 상태를 변경하고, Observer 가 상태 변경에 응답) 의 변종으로, 중계역에 유사합니다. 다음과 같습니다:
[caption id="attachment_1137" align="alignnone" width="438"]
event channel[/caption]
3 조의 선. 다음과 같습니다:
1.생산자 new item -> push 를 event channle -> event channel 이該 item 을 모든 소비자에게 push; 동시에 item 을暂定保存, 모든 소비자가 이 item 을 pull 할 때까지
2.소비자 pull item -> event channel 이 그에게 줌
3.event channel 이 모든 생산자를轮询 (pull 은 소비자와 event channel 에서发起 가능)
event channel 에 관한 보다 많은 정보는 CS635: Doc 8, Observer Variants (샌디에이고 대학?) 을 참조
五.enb 명령
make
node_modules/.bin/enb make
run a server
node_modules/.bin/enb server
node_modules/.bin/enb server -p portNum
css 파일을 생성
node_modules/bem/bin/bem create -l desktop.blocks -b header -T css
l 는 redefinition level
b 는 block
T 는 implementation technology
desktop.blocks 레벨에서 라이브러리 block 을 재정의
node_modules/bem/bin/bem create -l desktop.blocks -b input -T css
node_modules/bem/bin/bem create -l desktop.blocks -b page -T bemhtml.js
page 레벨, 구조를 수정 (wrapper), 스타일을 추가 (css 파일을 생성하는 방식과 동일)
node_modules/bem/bin/bem create -l desktop.blocks -b page -T bemhtml.js
node_modules/bem/bin/bem create -l desktop.blocks -b page -T css
bemhtml 과 css 를 동시에 생성
node_modules/bem/bin/bem create -l desktop.blocks -b goods -T bemhtml.js -T css
mix 組合 block
2 종류의 mix 를 서포트:
mix(block, elem)
mix(block, block)
컴포넌트의 bemhtml 중에서 mix 구조를 정의하고, 데이터를装入
elem: 'title',
content: {
block: 'link',
mix: [{block: 'goods', elem: 'link'}],
url: item.url,
content: item.title
}
page 의 BEMJSON 중에서 정의할 수도 있음
{
block : 'footer',
mix: [{
block: 'box'
}],
content : [
'footer content goes here'
]
}
block 의존을 선언, 의존 컴포넌트가 올바르게 도입되는 것을 확보
node_modules/bem/bin/bem create -l desktop.blocks -b goods -T deps.js
서드파티 라이브러리를 도입, 동様に BEM 의 라이브러리에 따름
bower.json 중에서 의존을 선언
node_modules/.bin/bower i
그 후 make.js 를 업데이트
재정의 계층을 설정
.enb/make.js
컴포넌트 js 를 재정의
node_modules/bem/bin/bem create -l desktop.blocks -b box -T js
새로운 페이지를 추가, 서비스는 처음으로該 페이지에 액세스할 때 컴파일
node_modules/bem/bin/bem create -l desktop.bundles -b about
http://localhost:8080/desktop.bundles/about/about.html 에 액세스
六.문제 답변
- Q1.모듈 로드, 패키징 릴리스는 편리한가?
설정 파일은按需로드, 수동 ��정은 거의 필요없고, 패키징 릴리스에는 커맨드라인 툴이 있어, 매우 편리
- Q2.임의의 다층 레벨의 재정의를 서포트하는가?
서포트 .enb/make.js
- Q3.build 는 제어 가능한가?
제어 가능, .enb/make.js 중의 build 룰을 수정 가능
- Q4.
body{margin: 0; font-size: 12px;}와 같은 base 스타일은 어디에 배치하는가?
body 자신도 1 개의 Block: block : 'page', title : 'BEM-컴포넌트화', page 컴포넌트를 재정의하면 됨. 예를 들어 .page { margin: 0; font-size: 12px; background-color: #fff; }
- Q5.글로벌 로직은 어디에 배치하는가?
글로벌 로직은 page 컴포넌트 중에 배치 (base 스타일의方案과 유사), body 의 일종의 상태로 가능. 예를 들어 mods : { map: 'show' }
- Q6.동적 데이터는 어떻게 처리하는가? (동적으로
page.bemjson.js를 수정? 아니면 동적으로 컴포넌트를 생성?)
동적으로 데이터를 리퀘스트하고, 취득 후 이벤트機制를 통해 관련 Block 에 통보
- Q7.크로스 컴포넌트 비즈니스는 어떻게 실현하는가?
컴포넌트 간 인터랙션 부분에서 언급한 4 종류의 방식을 참조
七.정리
BEM 은 조금 무거워 보이지만, 프로젝트의 모든 파일, 모든 행이条理清晰하며, 가독성이 매우 좋습니다
우세:
- 컴포넌트 산출률이 높음
페이지를 쓰는 것은 컴포넌트를 조합하는 것으로, 컴포넌트가 없으면 언제든지 커스터마이즈 가능. 컴포넌트 경계는 엄격하여, 재사용 가능함을 보증
- 컴포넌트는 유지보수 가능
각 컴포넌트에는 독립 폴더가 있으며, 경계한정으로, 다른 컴포넌트와 결합하는 것은 비교적 곤란
- 퍼포먼스 우세
컴포넌트 입자는 작고,按需導入으로, 冗長없음
- 코딩 스타일 통일
스타일 클래스명, JS 함수명 등은 모두 내장의一套의 룰로, 每个人的 코드가 거의 같게 보이는 것을 보증
- 상태로 제어
模糊이벤트 개념, 컴포넌트와 컴포넌트 상태만을關注, SoC 우세
- 로직 분리
모듈화, 로직 층, MVC 로 모든 것을 가능한 한 분해하여, 더욱清晰
[caption id="attachment_1138" align="alignnone" width="395"]
BEM 이 생성하는 HTML 구조[/caption]
높은 유지보수성을 가져올 수 있다면, 조금 길고, 추하고, 번거로워도 무슨 관계가 있을까요?
게다가 BEM 이 말하는ように, 그들은 1 개의 방법론만 제공하며, 우리는 실제 상황에 따라随便에 수정할 수 있습니다 (예를 들어微信팀이 사용하고 있는 것은 개량판으로, 상세는 참고자료를 참조)
또는 BEM 을 사용하지 않아도, 그 중의 많은 원칙은通用的으로 이용 가능합니다. 예를 들어「컴포넌트 개발 중에서 의존을 최소화를 보증」、「DOM 요소에의 직접 액세스를 금지」、「4 종류의 컴포넌트 인터랙션 방식」등
아직 댓글이 없습니다