[关闭]
@lxjwlt 2016-07-07T12:09:03.000000Z 字数 2627 阅读 660

JS模块的BEM命名实践

博文


更好阅读体验>>

在项目开发中我多次遇到由于命名冲突导致页面样式混乱,甚至跨模块获取同名元素导致功能失效的情况。对于这种情况,我之前的一篇文章中介绍过如何使用BEM命名法来解决命名冲突的问题。

当项目中引入了BEM命名后,不仅CSS代码,JS代码也应该做相应的调整,以下是我个人实践的总结,如果你也在项目中使用BEM命名,希望本文对你有所帮助。

JS统一管理模块标识

当我们提起BEM命名,我们知道一个元素的名称由三部分组成:

BEM = block + element + modifier = 模块名 + 元素名 + 修改器

假设我们有张联系页面,使用BEM命名的模板如下:

  1. <div class="contact-page">
  2. <div class="contact-page_header"></div>
  3. </div>

联系页面的模块标识为contact-page,所以该页面的所有子元素的名称都以contact-page为前缀来命名。

考虑到JS中需要通过模块标识来获取页面元素,而且HTML中手工编写模块标识影响可读性,不利于维护,所以我们将模块标识交由JS来统一管理,而HTML中的模块标识则使用JS模板引擎进行动态插入。

这里我们使用的是Ext.XTemplate引擎:

  1. var $ = require('jquery'),
  2. R_Xtpl = require('xtpl');
  3. var C = function (wrap, options) {
  4. this.modName = 'contact-page';
  5. this.$elem = $((new R_Xtpl(require('./index.html'))).apply({
  6. modName: this.modName
  7. }));
  8. $wrap.html(this.$elem);
  9. };

在模板中,我们用modName变量代替模块标识:

  1. <div class="{modName}">
  2. <div class="{modName}_header"></div>
  3. </div>

这样,阅读HTML时我们只需要关注页面元素名(BEM的E)即可,可读性更好,而且我们能够在JS获取元素。

模块元素的选择器

模块标识存储在this.modName中,所以按照BEM命名,我们可以编写以下函数来获取元素的选择器:

  1. C.prototype.selector = function (selector) {
  2. return this.modName + '_' + selector;
  3. };

联系页面的header元素的选择器,我们可以这么获取:

  1. '.' + this.selector('header'); // .contact-page_header

目前的selector函数不好的地方在于,每次获取元素选择器都要进行字符串的结合,如果点号(.)或井号(#)写在元素名中就好了,比如this.selector('.header'),我们对selector函数进行改进:

  1. C.prototype.selector = function (selector) {
  2. var self = this,
  3. reg = /^(\.|#)?/;
  4. return selector.replace(reg, function (match, $1) {
  5. $1 = $1 || '';
  6. return $1 + self.modName + '_';
  7. });
  8. };

这样我们就可以根据不同的场景分别获取元素的“名称”或“选择器”:

  1. // .contact-page_header元素有类名contact-page_header
  2. $(this.selector('.header')).hasClass(this.selector('header')); // true

获取模块元素

我们已经有了selector方法来获取元素选择器,那么接下来我们可以轻易的实现获取模块元素的接口:

  1. C.prototype.elem = function (selector) {
  2. return selector ?
  3. this.$elem.find(this.selector(selector)) : this.$elem;
  4. };

以下用elem方法获取联系页面的头部元素:

  1. this.elem('.header');

引入了VueJS之后

引入了VueJS之后,我们彻底抛弃了模板引擎库Ext.Xtemplate,我们使用Vue来作为视图层。

  1. var $ = require('jquery'),
  2. R_Vue = require('Vue');
  3. var C = function (wrap, options) {
  4. this.modName = 'contact-page';
  5. this.initVm(wrap);
  6. };
  7. C.prototype.initVm = function (wrap) {
  8. $elem = $(require('./index.tpl'));
  9. this.vm = new R_Vue ({
  10. el: $elem[0],
  11. data: {
  12. modName: this.modName
  13. }
  14. });
  15. $(wrap).html($elem);
  16. };
  17. module.exports = C;

引入vue之后我们不再使用类名来获取元素了,因为Vue提供了元素钩子,用来给元素建立索引,我们通过Vue实例的$els访问这些元素。

所以我们的模板可以改为:

  1. <div class="{{modName}}">
  2. <div class="{{modName}}_header" v-el:header></div>
  3. <!-- ... -->
  4. </div>

(注:类名中保留modName的写法是为了CSS中编写样式)

上面我们使用v-el语法为header元素建立了索引。

获取元素的方法改写为:

  1. C.prototype.elem = function (selector) {
  2. selector = selector.replace(/-([a-zA-Z])/g, function (match, $1) {
  3. return $1.toUpperCase();
  4. });
  5. return this.vm.$els[selector];
  6. };

获取header元素:

  1. this.elem('header');

不使用CSS选择器(类名或ID)获取元素的好处在于:
* CSS获取元素和JS获取元素的方式彻底分隔开,互不影响(记得有这么个规定:使用js-前缀的类名获取元素,就是为了达到这个目的)
* 元素已经缓存在Vue实例中,我们不需要手动获取元素,也不需要手动进行缓存,由Vue进行统一的模板管理、元素缓存以及元素的注销

VueJS大法好啊!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注