本期精读的文章是:css-in-js 杀鸡用牛刀
继 精读《请停止 css-in-js 的行为》 这篇文章之后,我们又读了一篇抵制 css-in-js 的文章,虽然大部分观点都有道理,但部分存在可商榷之处,让我们分析一下这篇文章,了解 css 还做了哪些努力,以及 css-in-js 会如何发展。
作者认为,模块化 jsx 让 html 结构与行为耦合在一起是很有价值的,然而样式却不应该与模块耦合起来,因为样式是一种全局行为。许多时候需要对网站进行全局的设计,将样式分散到模块中会导致更多的理解成本。
将样式与模块松耦合,系统会获得更大的自由度与拓展性。如果样式与结构松耦合,一套看似相似的的元素,可能拥有完全不同的底层结构。然而交互必须与结构紧耦合,因为交互依赖于结构。
局部样式会阻碍视觉一致性,只有全局化样式才能保证视觉一致性。
如果每个组件维护自己的样式,那么会存在许多样式代码复制粘贴的问题,复制粘贴的代码可维护性极低。
无论是 css-in-js 还是 css 预编译的尝试,各自都具有强大优点,本文对 css-in-js 提出的质疑我认为是欠妥当的,下面谈谈 css-in-js 如何解决作者提出的问题,以及简单介绍 OOCSS, SMACSS, BEM, ITCSS, 和 ECSS 的思路。
文中提出,网站样式要从全局考虑,模块化样式行为的优点是解决了样式冲突问题,但因此也削弱了对全局样式的把控。
开发单个组件的样式分为两种情况,分别是明确风格的组件与样式独立的组件,在样式独立组件中,由于不确定会被哪些主题的网站所引用,因此无论是全局 css 还是局部 css,都无法控制样式。在明确风格的情况下,可以先把此风格的基色确定下来,无论是抽成 sass 变量还是 js 变量,都具有可复用性。
全局 css 的开发,适合自上而下控制,组件通过定义 class 而不需要关心具体样式,通过全局 class 统一调控整体风格。而 css-in-js 是自下而上的,但需要预先抽出整体风格的样式模块,其效果与全局 css 是等价的。
全局 css 控制风格:
<style>
.container{}
.list-item{}
.submit-button{}
</style>
<div className="container">
<div className="list-item"></div>
<div className="list-item"></div>
<div className="submit-button"></div>
</div>
css-in-js 风格:
const CommonContainer = styled.div``
const CommonListItem = styled.div``
const CommonSubmitButton = css``
export const Container = styled(CommonContainer)``
export const ListItem = styled(CommonListItem)``
export const CommonSubmitButton = styled.div`
${CommonSubmitButton}
`
而 css-in-js 运行时的样式解析,让我们更轻易的切换主题。比如我们抽出一个公共样式包,业务代码中的色值都从此样式包中引用,那么在不同的环境下,公共样式包可能通过所在宿主环境的判断,返回给业务代码不同的色值,甚至与宿主环境配合,从宿主环境拿到注入的颜色,实现一套代码在运行时轻松换肤。
文中观点提出,css-in-js 这种局部样式行为,会导致公共样式、方法难以复用,导致各个模块参杂着大量重复代码。因为 sass 通过定义全局变量、mixins 方法让样式更具有复用性。
我觉得这是一种误解,在 css-in-js 模式中,通过全局合理的设计,使用 js 文件存放颜色变量、公共方法、可能会复用的 css 代码块,其复用能力远大于 sass。
OOCSS 成为 css 的面向对象加强版,每个 class 只处理一件事:
.size {width: 25%;}
.bgBlue {background:blue}
.g-bd2{margin:0 0 10px;}
网易 NEC 就大量使用了这种思想。
这样的好处在于避免了 class 之间的冗余,让我们更容易创建可复用的 class,也不会在命名上纠结。
然而,先不说 oocss 带来的巨大零散 class 导致的维护成本,以及修改 class 导致的巨大风险,class 的本意是语义化,如果让 class 使用一堆对象描述堆砌,我们将很难定位一个元素,也很难描述这个元素的含义。
SMACSS 认为 css 有 5 个类别:
- Base 基础样式
- Layout 布局样式
- Module 模块样式
- State 状态样式
- Theme 主题样式
我们通过这 5 种类别来拼凑出完整的 class,我感觉就是对 OOCSS 的进一步规范和约束。
对这 5 种类别,在命名时要加上对应前缀,分别是:
- Base 属于基础元素,比如
div
p
,不需要命名 - Layout 使用
.l-
或.layout-
前缀 - Module 使用模块名命名,比如文章区块就叫
.article
- State 使用
.is-
前缀,比如.is-show
- Theme 使用
.theme-
前缀
我觉得这样在语义化的基础上,拆分了状态、主题、布局,着实增强了 css 可读性。
尽可能减少适配层级,虽然增加适配层级会减少冲突发生率,但是会增加额外的阅读负担,以及一些 bug(旧版 ie 层级超过 255 导致样式失效)。
像 css-modules 这种解决方案恰恰反其道而行之,通过层级避免冲突,通过预编译解决阅读负担,然而在没有预编译的情况下,最小化适配深度原则依然是最有效的。
BEM 规范更像是 SMACSS 分类的加强版,通过 __element
表述后代,--modifier
表述状态,比如:
.article {}
.article__label {} /* label 元素 */
.article__label--selected {} /* label 元素处于被选中状态 */
类似 SMACSS 对 css 元素进行了分层:
- Settings – 与预处理器一起使用,包含颜色、字体等定义
- Tools – 工具与方法,比如 mixins,Settings 与 Tools 都不会产生任何 css 代码,仅仅是辅助函数与变量
- Generic – 通用层,比如 reset
html
、body
的样式 - Elements – 对通用元素的样式重置,比如
a
p
div
等元素的样式重置 - Objects – 类似 OOCSS 中的对象,描述一些常用的基础状态
- Components – 对组件样式的定义,一个 UI 元素基本由 Objects 与 Components 组成
- Utilities – 工具类,比如
.hidden
ITCSS 的分层是非常有借鉴意义的,即便在 css-in-js 设计中,也可以参考此模式定义结构。
ECSS 的规范是这样的:.nsp-Component_ChildNode-variant
- nsp 一个尽量简短的命名空间
- Component 文件名
- ChildNode 子元素名
- variant 额外内容
例子:
<div class="tl-MediaObject">
<a href="#" class="tl-MediaObject_Link">
<img class="tl-MediaObject_Media" src="mini.jpg" alt="User">
</a>
<div class="tl-MediaObject_Attribution">@BF 14 minutes ago</div>
</div>
更多细节可以看此 PPT
虽然我认为这篇文章提出的 css-in-js 缺点大部分存在漏洞,但它警示了我们,css 设计的初衷是全局化控制样式,即便产生了样式冲突、混乱的问题,但我们仍要记住,在模块化开发的今天,仍要保持网站风格的整体性,即便使用了 css-in-js 的开发方式。
虽然作者呼吁我们不要只顾着 css-in-js,要放眼看看 OOCSS, SMACSS, BEM, ITCSS, 和 ECSS 等基于原生 css 的解决方案,但我觉得把这些思想运用到 css-in-js 是个不错的选择 :p
如果你想参与讨论,请点击这里,每周都有新的主题,每周五发布。