卷首语 | 我对项目中 css 架构的理解和使用
作者:孟笑晨
在实现业务的过程中,我们难免会发现之前由于各种原因存在的代码中正在产生大量的冗余。这时候就需要优化代码,如果有功能的迭代,就是进行重构的好时机了!
在我负责重构公司项目的过程中,越来越意识到“架构思维”的重要性,以及它对于后续维护甚至是新功能迭代的帮助!
ITCSS
这是由 csswizardry 提倡的一个 CSS 设计方法论,他可以让你更好的管理、维护你的项目的 CSS。
它可以帮助你
管理 CSS 代码的书写顺序
通过分层来明确每层 CSS 的作用
更好地使用 CSS cascade(权重)
安全的使用继承
ITCSS 把 CSS 分成了以下的几层:
也就是常说的“七层架构”。
但在实际项目中,我们只需借鉴其思路,达到维护一套完善利于阅读、扩展、复用的 css 代码即可。
我是怎么做的
首先,上面说的第一层 —— Settings 是很重要的。我们可以在其中放一些公共 css 变量。比如负责更改主题色的变量。常见的有:
颜色
边框
字体大小
阴影
层级
排版
…
注意:这里必须是抽取的全局的、多个地方会使用到的公共的样式变量。更加细节的可以放在后面层级中单独写。
但通常,主题色不是这么容易实现的。它甚至需要大量的函数计算以及 js 的介入 —— 目前流行
Ant Design
采用了“三套主题色变量” 的方式(将 Settings 和 Theme 合并为一层);而elementUI
是在 Base 层下又加了一层 Theme(这也是为什么选择 ITCSS 方案的原因:随意扩展和缩减)。
然后是 tools 层,也是不可或缺的。这里面经常被用来放一些“工具样式”:比如当你使用了scss
后的一些需要全局处理的mixin
函数、比如水平垂直居中、比如溢出省略、清除浮动等样式类或function
…
关于这一层,网上有好多人推荐_sassMagic.scss
库。据说挺好用的!
值得注意的是:上面两层都是 全局层面 的。一般笔者是这样安排的:(在我司的大部分项目中,这两层都属于自研脚手架中内置的)
这里说一句题外话:其实原生 css 越想写简单(提高复用)就越会发现,如果 css 中能引用 css 就好了(非@import
形式) —— 这样 css 的东西就可以在 css 内部解决,完成一次复用。到 html 中只需引入一个类/属性名即可!scss
的mixin
就达到了这个效果。
(css 的自定义变量也可以达到这样的效果 —— 不过你要写在:root
中。而且 scss 中的自定义变量更加强大!)
对这一层感兴趣的可以研究
elementUI
库源码,它的mixin
写的非常之精妙。
注意⚠️:这两层的主要代码(涉及创建了 变量、mixin
和function
的)是要在vue.config.js
中引入的 —— 这样就能在其它 css 和页面中使用到:
第三层 Generic。这一层就是专门放置一些 css 样式初始化等功能。你可以选择 normalize.css 这样成型的第三方 css;也可以根据项目中用到的标签做针对性初始化处理。
这一层没什么说的。
第四层 Base 层:这一层可以用来放定制化 css 样式 —— 它是对基础样式的补充。比如你的网站中 a
链接点击后是什么样、或者 li
的前面几个点是什么样的。
这两层是 组件级别 的。
一般来说,“全局级别”的样式主要负责供应“全局”、“其它低级别样式文件”以及“极少量独立样式代码”;而“组件级别”主要负责供应所有构成页面的组件中需要的样式、制定本项目样式规范以及特殊情况。
第五层 Object 和第六层 Components 其实可以合并为一层:component。它其实就是写组件。
这一层首先在结构上不再维护在和其它目录同级的目录下(如上面的 style),而是放在组件存放的 components 目录下。
在这一层你要做的就是:自行/利用第三方库封装一个具有“基本架子(结构)”的组件。考虑到复用性,所以这里使用最多的就是slot
了。比如:
由于这一层的“特殊性”,再加上根据 css 中的就近原则来说他们对 html 的影响是最大的,也是最小的(只负责一个文件的样式,一般一个文件就是一个部分的功能)。所以推荐 OOCSS(面向对象 css)的进阶写法:BEM。
BEM 规范
场景一:开发一个弹窗组件,在现有页面中测试都没问题,一段时间后,新需求新页面,该页面一打开这个弹窗组件,页面中样式都变样了,一查问题,原来是弹窗组件和该页面的样式相互覆盖了,接下来就是修改覆盖样式的选择器…每次为元素命名都心惊胆战
场景二:承接上文,由于页面和弹窗样式冲突了,所以把页面的冲突样式的选择器加上一些结构逻辑,比如子选择器、标签选择器,借此让选择器独一无二。一段时间后,新同事接手跟进需求,对样式进行修改,由于选择器是一连串的结构逻辑,看不过来,嫌麻烦,就干脆在样式文件最后用另一套选择器,加上了覆盖样式…接下来又有新的需求…最后的结果,一个元素对应多套样式,遍布整个样式文件…
以往开发组件,我们都用“重名概率小”或者干脆起个“当时认为是独一无二的名字”来保证样式不冲突,这是不可靠的。
理想的状态下,我们开发一套组件的过程中,我们应该可以随意的为其中元素进行命名,而不必担心它是否与组件以外的样式发生冲突。
BEM 解决这一问题的思路在于,由于项目开发中,每个组件都是唯一无二的,其名字也是独一无二的,组件内部元素的名字都加上组件名,并用元素的名字作为选择器,自然组件内的样式就不会与组件外的样式冲突了。
这是通过组件名的唯一性来保证选择器的唯一性,从而保证样式不会污染到组件外。
这也可以看作是一种“硬性约束”,因为一般来说,我们的组件会放置在同一目录下,那么操作系统中,同一目录下文件名必须唯一,这一点也就确保了组件之间不会冲突。
BEM 的命名规矩很容易记:block-name__element-name--modifier-name
,也就是模块名 + 元素名 + 修饰器名。
这里面还涉及到一个问题:要不要用
scope
?这个问题值得深思,比如 vue 中的 scoped 会形成一个样式隔离。如果需要样式复用还需要样式穿透的介入,非常麻烦。但是一味的遵循“开放”反而会引来“无妄之灾”。
OOCSS 中,最重要的便是“结构与皮肤分离”。结构就是指“基础对象”,也就是我们说的“搭好一个架子”。
遇到上面的 HTML,一般会先给一个“固定的样子”:
这时候如果有新的样式或者颜色之类的改动。就需要另写一个类名:
但是在其它组件中,就完全不受影响:
OOCSS 的复用就是体现在“架子的重复使用”上。这一点和“组件复用”有异曲同工之妙!也是这一层的基本思想。
ACSS 规范
有时候我们还会在 component 上(样式的优先级比 component 低)再加一层:ACSS。
ACSS 是原子类样式。通俗的讲就是“一个类只写一个样式”。这样的好处是可以达到对 css 的极限复用。而不好的地方就是让 css 失去了语义化。
可以用属性选择器解决无语义化的痛点。
在 scss 强大的函数加持下,比如你写不同透明度的background
可以这样:
像我司这种电商平台,会有“今日必抢”、“限时抢购”这样的为了抓住用户眼球而区别于一般字体的艺术字存在。而这种字体并不是所有地方都要用到的,而且它只作用于字体样式。我们就可以在 ACSS 层下新建 font 文件夹实现。
最后一层 Trumps,其实就是在你的业务(组件)中进一步“描述”功能的差异性。结合之前层次样式的复用展现想要的效果。
为什么架构
css 架构的目的不是“为了在页面不写 css 代码”,而是“为了更好的复用,更简单的维护,和更清晰的结构”!
在上面的内容中,很明显看到:
“层级越靠前,优先级越低、复用性越强”
“下一层永远继承上面(所有)层”
意犹未尽,说点其他的
在只有少量功能迭代的场景下,如果碰上周期不那么长其实是没法“推翻重来”的。这时候我们只能做一点点的优化(当然,本文说的都是结构上的,这里也不例外)。
上面的文字翻来覆去的看,其实核心也就两个字 —— 复用 。尽量减少重复代码的编写、甚至是文件的数量。而这一点在大多数项目中都可以优化:
比如提升层级:有时候刚开始写就是为了功能的实现,但是有可能这个 div 下包裹的文字和 div 同级的某个地方的文字是一样的,不管大小还是 family,简直就是一个 family 的!其实可以将文字抽离出来,作为单独的一个样式类(或者属性选择器)。甚至是按照上面的 ACSS 层规范来,因为字体这玩意不可能只有一个页面有。花点时间嘛,哪怕花一点呢!
还比如溢出省略这些效果、可能某些公司业务场景还有 css 特效。这些不是妥妥的 Tools 层吗?
最后,我和某人聊起的时候他说会不会到后面文件(夹)太多。我觉得不会影响大局,它带来的收益是大于损耗的。而且像我司自研脚手架是确保文件按需加载的,就完全没有这个担心了,哈哈。
目录
热点 | Hot
终于!RocketMQ 迎来第五个大版本,深度解读“消息、事件、流”超融合处理平台
今年你关注 XDR 了吗?谷歌云安全和七家公司成立 XDR 联盟
观点|Opinion
左耳朵耗子:你得知道,技术不是用来写 CRUD 的
混沌工程 = 可观测性 + 探索性测试?
推荐文章 | Article
用尽一切手段降低 MTTR,混沌工程在华泰证券的落地实践
让 Flutter 在鸿蒙系统上跑起来
专题|Topic
Adobe 将 PB 级数据迁移到 Iceberg 的实践与经验教训
评论