一些解决 CSS 命名的方案

4,530 阅读8分钟

CSS 选择器命名是一个哲学问题。--- 张鑫旭 《CSS 选择器世界》

为什么要给 CSS 命名?

首先,需要大家思考一下这个问题。

。。。 一分钟后。

在我看来命名的究极目的,只有一个样式复用。你品,你细品。

命名冲突

一个项目通常不是只有我们一个人在开发,并且我们也会引入一些第三方的库。所以命名冲突是一个非常容易发生的事情。解决命名冲突的方案我知道有三种:

  1. 私有作用域前缀
  2. css-module
  3. 不命名

私有作用域前缀

通常我们写一些希望被别人复用的第三方组件的时候会采用,私有作用域前缀的方案。比如:ant-design .ant-*, material-ui.Mui*

然而这并不能 100% 的解决命名冲突问题。比如你的团队之前约定了 .ant- 作为通用前缀,此时又需要引入 and.design 的组件。又或者你同时引用的两个不同的第三方库,采用了相同的前缀。此时就会显得比较的尴尬了。当然这个概率其实也不大啦。

私有作用域前缀,还有一种方法就是约定一些前缀来约束不同选择器的功能。比如网页NEC 里面 :布局(grid)(.g-);模块(module)(.m-);元件(unit)(.u-)... 如果团队里面大家都认可这个那么这甚是极好的。但是可能需要思考的点就在于。如果这些约定的过多或者过于复杂的时候,团队其它成员是否能够接受或者达成一致。

CSS-Module

CSS-module 是一个我认为非常赞的发明。它妙就妙在,我可以在我的组件里面用 .header, 你也可以在你的组件里面用 .header。但是我们却不用完全不用担心命名冲突的问题。

一个优秀的解决方案就应该是这样:在不改变我自身习惯的情况下,还帮我解决我的痛点。

当然它还是有它的代价的,原来我们写代码:

import "../styles/global.css";
import "./header1.css";
import "./header2.css";

<h2 className="header1">标题1</h2>
<h2 className="header2 float-left">标题2</h2>

引入 CSS-module 写代码:

import "../styles/global.css";
import _header1 from "./header1.module.css";
import _header2 from "./header2.module.css";

<h2 className={_header1.header}>标题1</h2>
<h2 className={`${_header2.header} float-left`}>标题2</h2>

首先我们需要用构建工具去构建 CSS-module(特别是在非 react 和 vue 构建的项目中)。

在使用的时候,我们需要还需要给我们的 CSS 文件,取一个额外的作用域名称,告诉构建工具这个选择器是来自哪儿(这个作用域名称本身也会命名冲突的问题)。

更尴尬的是,如果我们全局有一些通用样式和 CSS-module 混用的时候,还得借用字符串模版的能力去拼接 className,这很容易导致我们的代码编辑器的对于 CSS 选择器的提示失效(不知道小伙伴们有什么方案可以解决这个问题)。

不命名

在我之前的文章当中,我多次提到不用命名就是最好的命名。连名字都没有就不用纠结命名的问题。对于不用命名的方案我知道也是有三种方案:

  1. tailwind / function css / unit css :
  2. Atomic css
  3. css-in-js

tailwind / function css / unit css

直接用别人取好的名字。

目前可能最流行的就是 tailwind 这应该是 github 上 star 数最多的 CSS 项目了。这个方案其实不是不用命名,而是预制好了能涵盖我们大多数业务场景的一些 CSS 类。于是我们就基本上可以是很少,或者是不用重新去给 CSS 命名了。

但是这么多预制的规则上手是一个成本,并且也同样有需要说服同项目的小伙伴使用的问题。

Atomic css

定义 style 规则,每个人通过这个命名规则只能产生相同的名称

Atomic css 是一种比 tailwind 原子化更加极致的方案。

Atomic css 并没有预制 CSS hooks,它更像是重新定义了一套 style-inline 的编写方式。因为没有 CSS-hooks 所以也就不存在命名冲突的问题。

老实说,Atomic css 输就输在它的那套晦涩难懂的命名规则。但是你也不得不承认,如果整站都是用这个去编写 style,确实可以 100% 的解决命名的问题。并且你整站的 CSS 代码至少可以减少 50% 以上。

css-in-js

直接写 style

当我们进入到 js 盛行的时代,css-in-js 可以让我们在不需要理解 Atomic CSS 那套命名规则的情况下,而直接使用我们最熟悉的 style 的方式,100% 的解决 CSS 命名的问题。

这里说 100% 的解决 CSS 命名是不准确的。更确切的说,我们是直接把样式附着在了组件上,在复用组件的时候达到了样式复用的目的。而非 CSS-in-js 的方式,是我们在拥有组件命名的同时还要在组件内部为样式再创建一套命名规则。

尴尬的是,如果我们有很多样式需要复用的时候,我们就需要创建很多的样式组件。可是当我们一旦开始创建样式组件的时候,这又回到了我们命名冲突的问题。因为组件的名称也会容易冲突。只是相对来说,JS 能有比 CSS 更好的解决命名冲突的能力更好一些。

如何命名?

什么是好的命名?在我看来就是大多数人都能很自然理解的名字就是比较的好的名字。

  • .btn-s: 小按钮
  • .btn-m: 中号按钮
  • .btn-l: 大按钮
  • .btn-xl: 超大按钮

比如我们借用生活中表示衣物 size 的缩写 s,m,l,xl 来表示一些元素的大小就是一个比较不错的方案。既能保持可读性,又精简了我们 CSS 名称的长度。

  • .cs-radio: radio 元素
  • .cs-menu: menu 元素
  • .active: 元素可以用状态
  • .disabeld: 元素不可用状态

再比如张鑫旭老师 《CSS 选择器世界》中提到的 “从 HTML 标签中寻找灵感” 我也是非常赞同的。

借用大多数人了解或者熟知的东西去命名,显然比按照我们自己的习惯去创建名称要好得多。比如我喜欢跳舞,我用舞蹈里面的专有名字去作为我 CSS 的名称,这显然是要被其它同事怼死的。

命名规范

命名规范的意义在于,通过一些约定,减少团队成员的命名决策时间,以及统一团队的命名风格。在我们的团队,有两条比较有意思的规则这里分享给大家:

  1. _ (下滑线) 开头的类名不能直接创建样式;
  2. _ 带下滑线的类名表示全局样式;
/* ❌ */
._disabled{ background-color:#f5f5f5; }

/* ✅   */
.btn._disabled{ background-color:#f5f5f5; }

/* ❌ */
.header ._disabled{ background-color:#f5f5f5; }

/* ✅  */
.header._disabled .btn{ background-color:#f5f5f5; }

通常需要采用这种方式创建的类名都是表示某些状态的类名。这样做的好处在于,我们就有了很多不用担心命名冲突的公用类名。

.select._disabled{ /* */ }
.input._disabled{ /* */ }

.dialog._open{ /* */ }
.modal._open{ /* */ }

这样我们都可以放心的维护自己组件的状态了,而不用担心我的 *._disabled 会影响到你的 *._dsiabled。当然维护状态更好的方式应该是:

.select:disabled{ /* */ }
.input:disabled{ /* */ }

.dialog[open]{ /* */ }
.modal[open]{ /* */ }

_ 带下滑线的类名表示全局样式

对于这条规则,原因在于我们想要有简单的方式来区分全局样式局部样式。后面我们想到了用_- 来区分这两个状态。

/* 全局header */
.g_header{}

/* 局部header */
.news-header{}

照理来说我们用.g_ 来表示全局样式可读性会更强。但是后面发现这样的话就会发生这样的情况:

/* 全局加号图标 */
.g_i_add{ }

/* 局部加号图标 */
.i-add{ }

这明显没有直接用

/* 全局加号图标 */
.i_add{ }

/* 局部加号图标 */
.i-add{ }

这样的方式来的简单干净,并且这样还能拓展出其它的全局作用域。所以索性我们就直接利用这个符号本身来区分全局和局部的样式。

总结

对于命名来说,往往难的不是命名本身,而是在于你这个命名是否能被团队中大多数人接受和认知。你有可能发现或者发明了一种,各方面都看起来完美无缺的命名方案,可是团队里面的其它小伙伴就是不买账,那也是徒劳无功。

重点还是要看自己团队的痛点是什么。不管是什么样的命名,只要团队里面大家都能理解的就算是不错的命名了。