More Transparent UI Code with Namespaces

More Transparent UI Code with Namespaces

Published
September 15, 2018
Tags
FED
开发
Description
通过命名空间实现自文档化、透明 UI 代码可以有效解决CSS管理的问题
 
当我们的工作规模化之后,我们发现经常要花费大量的时间去阅读、维护和重构现有的代码,而不是编写和增加新功能。这就是我们如此关注架构、命名规范、方法论、预处理器、可伸缩性等等的原因:因为写 CSS 很容易;维护它却很难。
我们期望的是写出的代码尽可能的透明化和自文档化。透明化意味着它的目的是清晰和明显的(对别人);自文档化意味着我们不需要浪费时间去写冗长的补充文档。
当使用 HTML 和 CSS 语言时尤其如此。它们的声明性性质意味着没有控制流给项目状态或形态提供线索,事实上这两种语言是分开来写的却又如此紧密联系,执行的时候 CSS 的来源各不相同。就是说,我们在标记语言中到处都可看到 classes,但这仅仅是很小的一部分:还有别的地方相应的 CSS 来完成整个项目,交叉引用这些 classes 确保它们适当处理(在 DOM 的其它位置重用,绑定它们添加样式,删除它们以删除样式等等)要求开发人员非常勤奋,并且消耗大量时间。
有多少次你看着一段 HTML 却不知道那些 classes 是做什么的,哪些 classes 是相互关联的(如果有的话),哪些 classes 是可选的,哪些是可循环使用的,哪些是可删除的,如此等等?我敢打赌,这要花费很多时间。
类似 BEM 的命名约定就做了一个神奇的工作:帮助传达 classes 的角色与职责。如果你还没有使用过 BEM,那么我强烈的建议你立即停止阅读这篇文章,现在就去使用 BEM——本篇文章将介绍如何使 BEM 更上一层楼。
简要概括,BEM 给了我们两个非常有用的后缀——__element 和 --modifier——添加到我们的 classes,用来我们告诉 UI 的角色,例如:
/** * The top-level ‘Block’ of a component. */ .modal {} /** * An ‘Element’ that is a part of the larger Block. */ .modal__title {} /** * A ‘Modifier’ of the Block. */ .modal--large {}
在我们的 CSS 中,这个命名并不是很有用,但是当我们看 HTML 就能更好的理解它的意义:
<div class="modal modal--large"> <h1 class="modal__title">Sign into your account</h1> <div class="modal__content"> <form class="form-login"> </form> </div> </div>
我们能够看到有一系列和 .modal 相关的 classes,而 .form-login 开启了一个新的上下文。
能够从我们的标记中的 classes 中收集这种级别的信息实际上告诉了我们很多,有关相应的 CSS,有关它们如何以及为什么相互作用。这也告诉我们应该(或不应该)重用 DOM 中的哪些其它 classes:.modal--large.modal__title,和 .modal__content 都依赖于 .modal,因此不能在没有 .modal 作为父元素的情况下使用。
这给予我们很好的透明度——因为它就在我们的 classes 中——它也相当于自文档化。
这就是一个命名约定,最近我和客户研究和实施了很多的一个想法是增加命名空间使命名约定更进一步。
一个命名约定告诉我们一个组件中的 classes 如何关联另一个,但一个命名空间将确切的告诉我们 classes 在一个更广泛的全局上如何表现。一个命名空间确切的告诉我们一个(或一类) class 在不相关的条目中做什么。
在规模化使用 CSS 时有许多常见的问题,命名空间旨在解决的两个问题是清晰和信心:
  • 清晰:我们能从最小的可能来源收集多少信息?我们的代码有可读性吗?我们能从一个单一的上下文做安全性假设吗?我们必须依靠多少外部的或者补充的信息来了解一个系统?
  • 信心:我们有足够关于系统能够完全接入的知识吗?我们足够了解代码能够自信做出改变吗?我们有办法知道潜在副作用做出改变吗?我们有办法知道我们能够删除什么吗?
通常不幸的是,大部分这些问题的答案是否定的。这就是我们要终结臃肿的代码库、满是我们不敢碰的遗留的和未知的 CSS 的主要原因。我们缺乏修改已存在样式的信心,因为我们害怕 CSS 全局运行和天然漏洞的后果。几乎所有的 CSS 规模化的问题都归结为信心(或缺乏信心):人们不知道这些东西做什么,人们不敢改变因为他们不知道将影响什么。旧的 CSS 从不会被删除,因为你很难知道它在什么地方会被用到。
结果是,我们堆积使用新 CSS,使用新的 selector,为了避免接触已存在的任何东西。然后 CSS 变得越来越难管理,一些可能不需要的样式被增加,遗留的 CSS 仍然是核心代码库的一部分,最后一个唯一的可能就是每隔几年就完全推到重写。这样的代价是昂贵的。
因为像这样的大型项目的性质,我们经常发现,比起可能去阅读 CSS 源文件,我们花更多的时间通过开发者工具阅读标记和它的样式。这意味着和其它开发者交流丰富的信息时有意义的 class 名变的非常重要。
我们需要确切的知道一个 class 是做什么,它为什么存在,它可能在什么地方已经存在,我们是否可以在别的地方重用,当用它去绑定或修饰是否安全。这意味着 classes 的名字就变成文档说明,我们可以就在视图中阅读文档。仅仅从一个选择器的名字就可以确切的知道它的范围是不是很不错?

命名空间

没有特定的顺序,以下是一些独立的命名空间及其简要描述。我们将详细介绍每一个,但是下面的列表将使你熟悉我们希望实现的东西。
  • o-:表示一个对象(Object),可用于你当前看到的对象里任何数量的不相关的上下文。对这些类型的 class 进行修改可能会引起其它不相关地方的连锁反应。请谨慎对待。
  • c-:表示一个组件(Component),指一个具体的、特定实现的 UI。对它样式的所有修改应该在当前上下文中可检测。修改这些样式是安全的,没有副作用。
  • u-:表示一个 class 是一个通用工具(Utility) class,它有一个非常特殊的角色(通常只提供一个声明),不应该绑定或改变。可以被重用并且不依赖任何特定的 UI。你也许会从一些库和方法例如 SUIT 中识别这种命名空间。
  • t-:表示一个 class 用于给视图添加主题(Theme),它告诉我们当前 UI 组件的表现可能是由于一个主题的存在。
  • s-:表示一个 class 创建一个新的样式上下文或作用域(Scope)。和主题(theme)类似,但是,应该保守的使用——他们会被滥用并导致 CSS 丑陋。
  • is-,has-:表示当前 UI 块因为一种状态或条件而显示特定的样式。这种来自 SMACSS 的状态化的命名空间是极好的。它表示 DOM 当前临时的、可选的或者暂存的样式取决于一个特定的状态被调用。
  • _:表示这个 class 是糟糕的——一个 hack!有时,尽管很少,我们需要在标记中增加一个 class 强制使之产生作用。如果我们这么做了,我们需要让其他人知道这个 class 并不是理想的,希望它是暂时的(也就是不绑定这个)。
  • js-:表示这段 DOM 上有一些行为,JavaScript 绑定它提供行为。如果你不是 JavaScript 开发人员,别管它就好。
  • qa-:表示 QA 或者测试工程团队运行自动化 UI 测试需要找到或绑定到这些 DOM 上。类似 JavaScript 命名空间,这基本上只是 non-CSS 目的的储备钩子。
即使仅从这个简单的列表,我们就能看到,给我们已存在的 classes 前加一两个字母,就可以和开发者交流多少信息。
It is probably worth noting at this point that these namespaces do not exist for encapsulation and sandboxing of styles, but for clarity and informative reasons. Ben Frain’s FUN convention utilises namespacing as a means of soft encapsulation.

对象命名空间:o-

格式:
.o-object-name[<element>|<modifier>] {}
例子:
.o-layout {} .o-layout__item {} .o-layout--fixed {}
o- 对象命名空间对于任何使用面向对象 CSS 的团队非常有用。
OOCSS 是很神奇的,它教我们抽象出重复的、共享的和纯粹结构方面的 UI 使之成为可重用的对象。这意味着诸如 layout、wrappers、containers、媒体对象等等,都可以作为非装饰样式——处理大量 UI 组件的结构骨骼方面的样式而存在。
这可以避免尽可能少的重复,大幅度减小样式表,但是带来一个问题:我们如何知道哪些 classes 可能是纯粹的结构、因此可能被用于开放式的实例数量?
这种问题在项目中很常见,如下所示例子:
想象一下你是一个项目的新开发者,而且你不了解 CSS 或者 classes 的含义。产品负责人要求你给网站上的 testimonials 增加一些 padding。你右键,然后看到这个:
<blockquote class="media testimonial"></blockquote>
现在,已经相当清楚了,你需要做的就是在 CSS 中找到 .testimonial {} 规则集,然后添加 padding。然而,使用开发者工具,给 .media {} 规则集添加 padding 就能得到你期望的。搞定!那我们就去 CSS 源文件添加吧。
这里的问题是 .media 是一个抽象,根据定义,它是一个可重用的非装饰性的设计模式,可以支持任意数量的不同 UI 组件。当然,在这个实例中的改变它的 padding 给了我们预期的结果,但是它同样可能无意中损坏其它 20 个 UI 块。
因为对象不属于任何一个特定的组件,并且可以支撑若干截然不同的组件,修改任何一个都是非常危险的。这就是为什么我们应该引入命名空间,让其它开发人员知道这个 class 形成了一个抽象,任何改变都会映射到站点范围内的每一个对象。
通过给对象添加 o- 前缀,我们可以告诉其它开发者它们的普遍特性,并希望避免有人绑定它们。如果你看到一个 class 以 o- 开头,警钟就应该鸣响——远离它。
<blockquote class="o-media testimonial"></blockquote>
  • 对象是抽象的。
  • 它们可以在项目中的任何地方使用——甚至你可能没有看到的地方。
  • 避免修改它们的样式。
  • 小心任何 o- 相关的事物。

组件命名空间:c-

格式:
.c-component-name[<element>|<modifier>] {}
例子:
.c-modal {} .c-modal__title {} .c-modal--gallery {}
组件是我们遇到的一些最安全的选择器类型。组件是有限的、离散的、特定实现的部分 UI,多数人(用户、设计人员、开发人员、业务人员)都能识别:这是一个按钮;这是一个日期选择器;等等。
通常当我们改变组件的规则集,我们将立即在期望的每一个(而且只是)地方看到变化。与对象不同,改变 .c-modal__content 的 padding 不应该影响网站其他 modal 的内容区域。对象是不可知实现的,而组件是特定实现的。
如果我们回顾前面的示例,介绍对象和组件的命名空间,我们会用这个:
<blockquote class="o-media c-testimonial"></blockquote>
现在纯粹从这段 HTML 我可以说,任何对 .o-media class 的改变会影响整个网站,但是任何对 .c-testimonial 规则集的改变将只修改 testimonial,而不会改变其他。
  • 组件是特定实现的 UI。
  • 修改它们很安全。
  • 任何以 c- 开头的都是一个具体的事物。

工具命名空间:u-

格式:
.u-utility-name {}
例子:
.u-clearfix {}
你很有可能因为 SUIT 而熟悉 Utility,Utilities 是完全单一责任的规则,它有非常具体的目标任务。这些规则声明常常带有 !important 以保证不会被其他非特定的规则覆盖。它们以一种笨拙的、不够优雅的方式做一件事。在没有其他 CSS 钩子可用或处理很独特的情况时,它们就作为最后的手段,例如使用 .u-text-center 使文本居中一次,且仅一次。它们离内联样式仅一步之遥,所以请谨慎使用。
由于它们的笨拙的实现,全局的可重用性以及它们的特殊用例,给其他开发人员标志 Utilities 是非常重要的。我们不希望任何人试图将它们绑定到未来的选择器中。下面的例子,实际上发生在我做过的一个项目中。几个月之后,一个开发者写了以下 CSS:
.footer .text-center { font-size: 75%; }
这里有一个问题:当它出现在任何 .footer 之内的地方,.text-center class 都有两个职责。它现在有了副作用,这是 Utilities 永远不应该有的。
通过使用命名空间,我们可以引入一个简单而牢不可破的规则:如果它以 u- 开头,绝不重写。
Utilities 应该是被定义一次,不再需要改变。
Utility 解决的另一个问题是它实际上让人们知道有一个高优先级的规则被应用到 DOM 区域上。它将有助于解释为什么有些事情为什么发生以及难以覆盖。例如:
<div class="font-size-large"> ... <blockquote class="pullquote"></blockquote> ... </div>
开发人员接手这个可能会感到困惑,为什么 blockquote 的字体大小和他们期望的不同。这是因为它从 .font-size-large class 继承字体大小,进一步影响之内的 DOM 树。通过增加 classes 的清晰度,我们可以更快的识别任何潜在的“罪犯”:啊,这儿是个 Utility,它一定是罪魁祸首。(这是我们为什么应该有节制使用 Utilities 的相当好的例子)
<div class="u-font-size-large"> ... <blockquote class="c-pullquote"></blockquote> ... </div>
请参见姐妹篇 Immutable CSS 获取这些规则的更详细信息。
  • Utilities 是重量级的样式。
  • 它提醒人们它的存在。
  • 不要对任何以 u- 开头的 class 重写。

主题命名空间:t-

格式:
.t-theme-name {}
例子:
.t-light {}
当我们使用 Stateful 主题(也就是说,主题可以打开和关闭),我们通常是给 body 添加一个 class。改变主题的例子包括样式转换器(用户可以在不同主题之间切换)和给网站分区(博客文章有一个主题色,新闻页面有另一个主题色,等等)。对于特定页面,我们仅仅给 DOM 添加一个 class 以调用那个主题。
一种表示所有主题相关的 classes 的简单方法是给它们加上前缀 t-。在 HTML 中看到一个 t- 的 class 就是告诉你:“啊,对,视图看起来这个样子是因为我们有一个主题被调用。”
现在为止,所有的命名空间都是用在标记上,但是主题命名空间在 HTML 和 CSS 中都很有用。例如,标记中 t-light 告诉我们整个 DOM 应用当前的状态,调试时也需要知道。那个 CSS 中的 class 也告诉我们很多:它有助于设置沙盒和隔离命名空间规则集中任何主题相关的 CSS:
.c-btn { display: inline-block; padding: 1em; background-color: #333; color: #e4e4e4; .t-light & { background-color: #e4e4e4; color: #333; } }
这里我们可以看到 buttons 有 light grey 字体颜色以及 dark grey 背景色,但是当我们调用 .t-light主题时,它们的颜色调换了。我们封装样式信息,这意味着发现、调试和修改主题规则变得简单多了。
  • 主题命名空间有很高级别。
  • 它们给很多其他规则提供了一个上下文或作用域。
  • 它是当前 UI 状况的有用信号。

作用域命名空间:s-

格式:
.s-scope-name {}
例子:
.s-cms-content {}
CSS 中的作用域上下文解决了一个非常具体和特殊的问题:在使用作用域之前请确认你真的有这个问题,因为它们会被滥用最终导致糟糕的 CSS。
通常它有助于给你的 UI 特定区域建立一个全新的样式上下文。一个很好的例子就是用户生成内容的领域,CMS 产生的长篇文章/HTML。这些内容的样式通常不同于它周围的 app-like 的 UI。也许你会用 class-heavy UI 架构来提供类似导航、按钮、模块等复杂设计,在这里用户只能写纯文本而不能添加任何 classes 或其它复杂性,通过 CMS 发布一篇简单的博客。
一个有关作用域样式简洁有效的例子:请看 David Bushell 的 Scoping Typography CSS
也许你想要给形式自由的文本设置不同于其周围 UI 的样式,那么你可能会使用一个作用域上下文。例如:
<nav class="c-nav-primary"> ... </nav> <section class="s-cms-content"> <h1>...</h1> <p>...</p> <p>...</p> <ul> ... </ul> <p>...</p> </section> <ul class="c-share-links"> ... </ul> <a href="" class="c-btn c-btn--primary">Next article...</a>
.s-cms-content 里的一切都是不可访问的:我们不能在里面取得 DOM 元素然后添加 classes,所以我们会通过作用域设置样式。就像下面:
/** * Create a new styling context for any free-text CMS content (blog posts, * news pages, etc.). * * 1\\. Use a larger and more readable typeface for continuous prose. * 2\\. Force all headings to have the same appearance, regardless of their * hierarchy. * 3\\. Make links inside long text more apparent. */ .s-cms-content { font: 16px/1.5 serif; /* [1] */ h1, h2, h3, h4, h5, h6 { font: bold 100%/1.5 sans-serif; /* [2] */ } a { text-decoration: underline; /* [3] */ } }
不能强调更多,嵌套选择器是糟糕的行为:它将导致局部样式化,就是说样式会与 DOM 结构紧密耦合;它会阻止人们选择设置样式,因为嵌套选择器是非常“专制”的,用一个类型选择器作为关键选择器创建更多选择器,它会比你期望的匹配更多的 DOM 节点;优先级得到提高,意味着作用域的样式会覆盖之前定义的样式,反过来作用域本身定义的样式很难被覆盖。
上面的 sass 是一个很好的例子。当编译之后,会生成:.s-cms-content a {},这个选择器给链接添加下划线,而且它的优先级比 .c-btn {} 高,这意味着如果我们在作用域之内添加一个 button,它会带有下划线——这可能是我们不想要的。这个简单的例子概述了使用作用域潜在的问题,所以要谨慎使用。
在层层嵌套选择器之前请再三确认你需要使用一个作用域。如果你不确定,那么最好谨慎的不要使用。
除了警告,实际上 s- 命名空间对于提示开发者 DOM 的某一区域的作用域尤其有用。我们看到的任何一个样式都可能还有一个外层样式。
  • 作用域非常少见:请确定你需要使用它们。
  • 它们完全依赖于嵌套,确保所有人意识到这一点。

状态化命名空间:is- | has-

格式:
.[is|has]-state {}
例子:
.is-open {} .has-dropdown {}
状态化命名空间很不错。它们源自 SMACSS,它们表示 UI 根据样式需要使暂存临时的状态变化。
使用开发者工具查看一段交互 UI(例如一个 modal 层),我们也许会花一些时间切换状态。看 DOM 中的 classes 例如 .is-open 的出现或隐藏是学习状态的一种方式,这是高度可读和显而易见的。
<div class="c-modal is-open"> ... </div>
CSS 也十分方便的显示 UI 可能存在的状态,例如:
.c-modal { ... &.is-open { ... } } .c-modal__content { ... &.is-loading { ... } }
它们和其它 classes 连接在一起工作,例如 .c-modal.is-open。这个高优先级确保状态(State)在默认样式的基础上得到显示。这也意味着我们永远不会在样式表中看到赤裸的状态化 class:它必须和其它 class 连接使用。
状态(State)和 BEM 的 Modifiers 的不同是状态(State)是临时的。状态(State)(能)从一个状态向下一个改变,也许是基于用户操作(例如 .is-expanded),也许是来自服务器的推送(例如 .is-updating)。
  • 状态是临时的。
  • 确保 HTML 中的状态很容易被看到和被理解。
  • 不要写赤裸的状态 class

Hack 命名空间:_

格式:
._<namespace>hack-name {}
例子:
._c-footer-mobile {}
在某些通常非常罕见的情况下,我们可能需要在标记中添加一个 class,仅仅是为了帮助我们 hack 或覆盖一些东西。如果我们这样做,我们需要标记这个 class 是 hacky,它是(希望)临时的,我们想要在某个时机舍弃它,因此不绑定、重用或接入它。
以下划线开头的原因只是简单的参照编程语言里的私有变量。程序的私有变量不应该被其它开发者依赖或重用,我们的 Hack classes 也是一样。
这些类型的 class 在代码库中很容易识别,因此任何的 hack 变得更明显是一件好事。
@media screen and (max-width: 30em) { /** * We need to force the footer to be a fixed height on smaller screens. */ ._c-footer-mobile { height: 80px; } }
  • Hacks 是丑陋的——给它们丑陋的 classes。
  • Hacks 应该是临时的,不要重用或绑定到其它 classes
  • 留意代码库中的 Hacks classes 数量

JavaScript 命名空间:js-

格式:
.js-component-name {}
例子:
.js-modal {}
现在 JavaScript 命名空间很常见,大多数人倾向使用它们。关键是——为了适当的分离关注点——我们绝不应该将样式和行为绑定到相同的钩子上。这两种技术结合到相同的钩子上意味着我们不能将单独使用其中一个:我们的 UI 变的孤注一掷,这使得它片面和不可伸缩。
当我在 Sky 工作时,发生一次意外,一个开发者创建了一个有独特外观的 text-callout UI 组件,其上有一些隐藏显示文本的行为。一个产品负责人要求我们在其它地方重用这段 UI,但是我们不需要隐藏显示多行文本的行为;而它一直做同样的事情。因为这个组件将 JS 和 CSS 绑定到相同的钩子,就是我不能配置这个组件使之仅外观可用而不需要行为。大块的重构才能修正它,而它原本只需要简单的绑定单独的钩子就可避免。
这也意味着我们可以更安全的工作。意味着 CSS 开发者可以自由的重构而不需要担心会损坏一些 JS,反之亦然。分离关注点,使每个团队使用自己的钩子达到自己的目的。
可能需要注意的是因为 JS 命名空间和 CSS 没有任何关系,所以其格式应由你们的 JS 工程师决定。如果你们的 JS 团队对变量等的命名约定是驼峰式命名,那么他们也许会选择类似 .jsModal 作为钩子。
  • JavaScript 和 CSS 分离——给他们使用单独的钩子
  • 为了更安全的合作,给予不同的团队/角色不同的钩子

QA 命名空间:qa-

格式:
.qa-node-name {}
例子:
.qa-error-login {}
这是一个不同寻常但可能非常有用的命名空间,为质量团队准备。当运行自动化 UI 测试时,以下是常见操作:
  1. 访问 site.dev/login
  1. 输入错误的用户名。
  1. 输入错误的密码。
  1. 希望在 DOM 中看到错误出现。
在这些自动化 UI 测试被绑定到 CSS classes 之前我有个问题:例如 .message--error 在 DOM 中出现吗?这些测试绑定样式钩子的问题是,简单重构你的 CSS,使用一个不同的名字就会导致测试失败,即使功能是完全一样的。同 JS 钩子类似,自动化 UI 测试也不应该依赖 CSS classes。这样做会打破分离的观点。
我们需要做的是给 QA 团队绑定一套他们自己的 classes。如果我们这样开始:
<strong class="message error qa-error-login">
……然后重构这些 .message 和 .error classes,像这样:
<strong class="c-message c-message--error qa-error-login">
这样我们就能做想要的 CSS 改动了,只要我们确保 QA 团队的钩子保持不变。
  • 将自动化 UI 测试绑定到样式钩子是极其含糊的——不要这么做。
  • 将测试绑定到测试专用 classes。
  • 确保任何 UI 重构不会影响到 QA 团队的钩子。

方便的附加效果

一个极其神奇、异常有用、几乎偶然的免费的效果是增加命名空间之后,在文本编辑器中使用 CSS 自动提示:
notion image
只要输入 o- 就能得到我们项目中的每一个对象列表;输入 c- 就能得到每一个可用的组件;如此等等。
这是一个非常好的特性,对于那些知道要找什么的人来说,查找变的很方便,也更易于发现哪些组件可用。

命名空间检测

因为我们的 classes 现在有十分严格的命名,我们可以很容易的找到它们
  • 畸形的 classes
  • CSS 中规则的类型
  • HTML 中 classes 的类型

查找有(无)效的 classes

我写了一个很粗糙的正则用来查找有效的 classes:
^\\.(_)?[a-z]+-[a-z0-9-]+((_{2}|-{2})?[a-z0-9-]+)?(-{2}[a-z0-9-]+)?[a-z0-9]$
它将匹配以下所有:
.o-layout__item .c-modal--wide .u-text-center .c-nav-primary__link--home ._c-footer-mobile
却不包括以下:
.foo // No namespace .c-datePicker // Camel case .o-media_img // Single underscore .c-page-head-- // Trailing punctuation
原理是:
  • ^:确定字符串的开头。
  • \\.:必须以点开头(即是一个 class)。
  • (_)?:可以下划线开头(即是一个 Hack)。
  • [a-a]+:小写的一个或多个字母(即命名空间)。
  • :一个连字符。
  • [a-z0-9-]+:字母数字、小写、分隔符,一个或多个字符(即 Block 名字)。
  • (:打开可选匹配。
    • (_{2}|-{2})?:可选的两个下划线或分隔符(即 Element 或 Modifier)。
    • [a-z0-9-]+:字母数字、小写、分隔符,一个或多个字符(即 Element 或 Modifier 名字)。
  • )?:关闭可选匹配。
  • (-{2}[a-z0-9-]+)?:可选的字母数字,小写修饰符。
  • [a-z0-9]:确保以字母、数字结尾。
  • $:确保结束字符串。
是的,这看起来很讨厌。我之前从没写过正则,所以我决不怀疑有一个更简洁高效的方法来实现它,但是现在这个正则看起来也没什么问题。试试

高亮命名空间的类型

如果你想从总体上设置所有的组件:
[class^="c-"], [class*=" c-"] { outline: 5px solid cyan; }
其中:
  • [class^="c-"]:找到所有以 c- 开头的 class 属性,例如:
<blockquote class="c-testimonial">
  • [class*=" c-"]:找到所有包含 c- 的 class 属性,例如:
<blockquote class="o-media c-testimonial">
一个更复杂的例子:
[class^="o-"], [class*=" o-"] { outline: 5px solid orange; } [class^="c-"], [class*=" c-"] { outline: 5px solid cyan; } [class^="u-"], [class*=" u-"] { outline: 5px solid violet; } [class^="_"], [class*=" _"] { outline: 5px solid red; }
这能给我们的页面在整体视觉上添加一个快速的指示,太多的红色?呵呵,你用了太多的 Hacks。太多的紫色?你用了太多的 Utilities:你能稍微重构一下吗?

查找 CSS 中的类型

如果我们想找到 CSS 文件中的所有命名空间类型,我们只需要简单的运行:
$ git grep "\\.t-"
这将输出我们 CSS 源文件中的所有主题命名空间(\\ 是用来转义 . 使之匹配 . 字符串,而不是正则匹配所有)。
当然,将 t- 转换成 c- 就能得到所有的组件命名空间。

太多的类型?

如果你不太热衷于给每一个 class 添加 o- 或者 c-——而且尤其是你对于自动补全的好处不感兴趣——另一个我们可以使用的格式是 .object .Component。就是说,给所有广泛的对象 classes 用非命名空间、小写字母开头方式命名,给组件 classes 用非命名空间、大写字母开头方式命名。
这实际上几乎感觉自然:因为组件是命名的完整的 UI 片段,给它们标题式的命名也合适。例子:
<blockquote class="media Testimonial"> </blockquote> <ul class="list-inline Nav-Primary"> </ul> <ul class="box box--large Panel Panel--info"> </ul>
小写字母是通用的全局的抽象,标题式用于给特定 UI 片段命名。
这将失去我们增加的一些其它特性(即自动补全、正则、视觉强调 UI),但是会让你少敲一些键盘。不过决定权在你。

学习命名空间

因为每个命名空间往往是 class 的第一个字母,我们就发现学习命名空间相当简单:c- 表示组件,t- 表示主题,o- 表示对象。然而,这不代表我们不用在文档上正式的规范命名空间。
命名空间的美妙之处在于它们是完全基于规则的。不需要解释,也就是说:
  • 我们没有理由不遵从它。
  • 它可以被视为备忘单。
我建议你创建一个简单的备忘单,用 A3 纸将命名空间打印出来,然后挂在你们工程师对面的墙上。这些规则是如此简单,它们很容易被浓缩呈现为一张简单的人人都能遵守的备忘指南。

一个例子

下面是一个人为的、为了展示命名空间意义的例子。当然,这个例子有两个关键问题:
  1. 这是一个没有上下文的实际的大项目,所以尽管它展示了命名空间是什么,但是作为一个例子还是太小不足以表现命名空间的强大之处。
  1. 由于你可能还不熟悉命名空间,所以如果你先温习一下这些东西,也许能更快的阅读那些 HTML。
那么,我们能学到什么:
<body class="t-light"> <article class="c-modal c-modal--wide js-modal is-open"> <div class="c-modal__content"> <div class="s-cms-content"> ... </div> </div><!-- /.c-modal__content --> <div class="c-modal__foot"> <p class="o-layout"> <span class="o-layout__item u-1/3"> <a href="c-btn c-btn--negative qa-modal-dismiss">Cancel</a> </span> <span class="u-hidden">or</span> <span class="o-layout__item u-2/3"> <a href="c-btn c-btn--positive qa-modal-accept">Confirm</a> </span> </p> </div><!-- /.c-modal__foot --> </article><!-- /.c-modal --> <footer class="c-page-foot"> <small class="c-copyright _c-copyright">...</small> </footer> </body> </html>
我们能学到很多:
  • 有一个高优先级的主题被使用(.t-light):UI 因此而显示当前的外观。
  • 有一个 modal 组件(.c-modal),它使用了宽型变化(.c-modal--wide)。有一些 JS 绑定在上面(.js-modal),当前是 open 状态(.is-open)。
  • modal 由几块组成(.c-modal__content 和 .c-modal__foot)。
  • DOM 有一整块区域样式被定义了作用域(.s-cms-content)。我们不能从它的内容中获取 DOM 节点,因此我们在一个新的上下文定义样式。
  • 有一个 layout 对象(.o-layout)。
  • 一些 layout 条目显示 1/3 和 2/3 宽(.u-1/3.u-2/3)。
  • 这些宽度 classes 就是 Utilities,因此不要只在 layout 对象中使用——它们可以在任何地方使用。
  • 有一些 button 组件。
  • QA 钩子被绑定到自动 UI 测试(.qa-modal-dismiss.qa-modal-accept)。
  • 我知道这里的一些东西我可以在别的地方重用(对象,组件,工具)。
  • 一些东西我可以重用,但是不能绑定或改变(对象和工具)。
  • 一些东西我不应该动(JS 和 QA 人员的东西)。
  • 一些讨厌的 hacks 需要在某些时机被移除,不要重用、修改、移动。
仅仅是从自带含义的 classes 中就能得到所有这些,神奇吧。
和以下对比:
<body class="light"> <article class="modal wide open"> <div class="modal__content"> ... </div><!-- /.modal__content --> <div class="modal__foot"> <p class="layout"> <span class="layout__item 1/3"> <a href="btn btn--negative">Cancel</a> </span> <span class="hidden">or</span> <span class="layout__item 2/3"> <a href="btn btn--positive">Confirm</a> </span> </p> </div><!-- /.modal__foot --> </article><!-- /.modal --> <footer class="page-foot"> <small class="copyright">...</small> </footer> </body> </html>
相对 BEM 命名,我很难从这段 HTML 中得到什么信息。完全不知道哪些可以重用、修改、删除。

OK,现在已经写了超过 6,400 字了,让我们总结一下。
BEM 已经给我们的 classes 提供了相当清晰的命名方式。在此之上添加命名空间将给我们的 HTML 增加更加丰富的含义。当重构结构时,这种程度的清晰给了我们更大的信心,帮助我们做出更好的更明智的决定。
在团队(例如 JS 工程师,质量工程师等等)协作时,这也意味着更少的回归和碰撞。
如果我们的文本编辑器支持 classes 自动提示,我们也获得一些很酷的额外效果:一个输入就能找到的目录,显示项目中所有不同的样式分类。