본문으로 건너뛰기

BEM 개발 모드

무료2016-09-03#Front-End#前端工程化#前端bem#event channel#bem指南

높은 유지보수성을 가져올 수 있다면, 조금 길고, 추하고, 번거로워도 무슨 관계가 있을까요?

서론

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 을 생성합니다. 일상 개발은 이러한 설정을 쓰는 것으로, 기능, 스타일 등은 모두 컴포넌트에 캡슐화됩니다

구체적인 컴파일 프로세스는 다음과 같습니다:

  1. pageName.bemjson.js 는 각 페이지의 HTML 구조 및 데이터 모델을 선언

  2. 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_closeyes 상태일 때, 왼쪽으로 접는 애니메이션을 실행 (맞습니다, 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 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 구조 BEM 이 생성하는 HTML 구조[/caption]

높은 유지보수성을 가져올 수 있다면, 조금 길고, 추하고, 번거로워도 무슨 관계가 있을까요?

게다가 BEM 이 말하는ように, 그들은 1 개의 방법론만 제공하며, 우리는 실제 상황에 따라随便에 수정할 수 있습니다 (예를 들어微信팀이 사용하고 있는 것은 개량판으로, 상세는 참고자료를 참조)

또는 BEM 을 사용하지 않아도, 그 중의 많은 원칙은通用的으로 이용 가능합니다. 예를 들어「컴포넌트 개발 중에서 의존을 최소화를 보증」、「DOM 요소에의 직접 액세스를 금지」、「4 종류의 컴포넌트 인터랙션 방식」등

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성