如何设计 CSS 架构解放前端生产力?
前言
CSS 是前端开发最熟悉的部分,几乎每一个项目都会大量使用到这些基础的东西,但是越是最基础的往往是大家最忽略。 作为一名前端开发,除了大家追求的性能之外,是否忽略了极致的开发体验呢?我最近审视了下自己过往前端项目,问了自己几个问题。那就是自己日常开发中写 CSS
- 是否足够快?
- 是否规模可控?
- 是否简单明了?
- 是否在验收中频繁改动?
事实上当我真正看自己之前写的代码时候,发现即使自己严格遵循 Less + BEM 规则,我写的 CSS 代码依旧量很大,随着时间推移,规模持续增加,且难以复用和理解。
灵魂拷问是哪里出问题了呢?看了下项目的 CSS 命名规范、DOM层级结构看着也都是相对合理的,且上升空间不大了。一次和师兄聊这个事情的时候,他提到了可能需要的是CSS架构调整。
CSS架构属于是那种在记忆的尘埃中封存已久的事物了,它实际是伴随React和Vue的发展一起快速成长的,早在17年就已经大部分定型,也就是大家所熟知的CSS in JS和CSS预编译。本文期望通过对 CSS 发展的一些回顾、核心观念的轻解读和详尽的实践,来帮助大家设计合理的 CSS架构 来解放前端生产力。
CSS的现状
目前大家主流使用的是 CSS3,传统前端项目大部分采用的 React 或者 Vue 两者其中一个的全家桶,CSS 的编写方式也大多采用是的 Less、PostCSS、SASS 等支持 CSS Modules 的 CSS 预处理方案,结合一些 CSS 插件完美解决浏览器兼容问题,CSS体积问题,样式复用的问题。
到这里大家可以问下自己:可是这样就足够了吗?看下之前的前端项目 CSS 的体积
也就说即使在做了Code Split的情况下也要 112KB 的大小,同时对比了下淘宝网页的CSS体积 14.9KB。
特别是这些 CSS 很多都是你自己一行行敲出来的,并没有使用 JS 或者 HTML 等user snippets进行自动补全的,前端的开发效率可想而知了。
发现了问题就要解决问题,能在不影响代码质量的情况,少写一行,复用一行,少记忆一行都是巨大的进步。随着前端技术的成长,这些问题都有具体的解决方案,我们需要就是深入学习,动手实践,择优组合,就可以解决这些难题。
CSS 架构
首先介绍了 CSS 设计模式,这是最基础的内容。
- OOCSS(Object Oriented CSS)
- SMACSS(Scalable and Modular Architecture for CSS)
- BEM(Block - Element - Modifier)
- ITCSS(Inverted Triangle Cascading Style Sheets)
- Atomic CSS
其设计原因是为了一下方面:
- 减少选择器命名和样式的冲突
- 清晰的 CSS 整体结构
- 去除冗余代码,减少样式的体积
- 可重复利用,组件化的 CSS
- 提高 CSS 代码的可读性
本文在下面工程化实践中采用的是 BEM + Atomic css组合的方式,也比较推荐大家去使用。
Atomic CSS
Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.
译文:原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。
这种设计模式是从开发者实际使用的场景中延伸出来的,在前端开发过程中,样式的需要也就主要集中在margin,padding,flex,height,width等基础样式上,那为何不使用独立的缩写类,直接写到内联样表中,而要单独写个样式文件这样大费周章呢?
实际上很多网页都采用了这种方案,例如Github官网,就是如此。

这种方案确定也很明显,行内样式不支持伪类、媒体查询,而且随着前端组件模块化概念的兴起,CSS Modules的概念也兴起了,进而催生了CSS in JS和CSS modules的架构。
CSS 预处理器
CSS 预处理器是一个能让你通过预处理器自己独有的语法来生成 CSS 的程序。市面上有很多 CSS 预处理器可供选择,且绝大多数 CSS 预处理器会增加一些原生 CSS 不具备的特性,例如代码混合,嵌套选择器,继承选择器等。这些特性让 CSS 的结构更加具有可读性且易于维护。 —— 《MDN / CSS 预处理器》
下面列举下常见的 CSS 预处理器:
- PostCSS:2013/11/04
- Less:2009
- SASS:2006/11/28
- Stylus:2010/12/29
这里大家比较熟悉了,预编译起的功能就是将Less、Sass等编写的文件编译成常规CSS,并且结合Webpack等前端工程化工具自动插入到HTML中。
从上图中显而易见,编写 Sass 明显比 CSS 更加规范迅速,不宜出错。
美中不足的是预处理器方案,并不方便记忆,需要借助typescript-plugin-css-modules进行类名补全,且无法直接在组件模板里直接感知到其样式,需要频繁切换文件,记忆负担依旧很大。
CSS in JS
CSS in JS顾名思义,就是在 JS 中维护样式表,请抛弃那无用的样式表,岂不是更加符合组件化模式。最大的好处还是在于真正的避免了CSS 选择器冲突的尴尬,也就说你不用费尽心思去想名字了。
例如以本文实践的styled-components为例。
import React, { useContext } from 'react';
import styled from 'styled-components';
import { themeContext, IThemeConfig} from '../../../store/themeContext';
export default function() {
const { themeConfig, dispatch } = useContext(themeContext);
const Title = styled.h1`
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: ${themeConfig.color}
`
return (
<div>
<Title>展示每个小区的匹配雷达图</Title>
<div>主要展示交通,新旧,配套,检索符合度,学区等维度进行分析,需要相关的爬虫和算法支持</div>
</div>
);
}
你只需要关注这个标签的含义即可,配合BEM的命名思路,整体模板会变得更加可读,可维护。如果你还在用DIV + CLASS的方式来进行 DOM 层级命名,请放弃它吧。
CSS in JS 其他的好处如下:
- CSS-in-JS 利用 JavaScript 环境的全部功能来增强CSS。
- 真正的选择器隔离。范围选择器是不够的。CSS具有从父元素自动继承的属性(如果未明确定义)。
- CSS 要避免选择器冲突,例如 BEM 之类的命名约定可能在一个项目中有所帮助,但在集成第三方代码时则会存在很多问题。当 JSS 将 JSON 表示形式编译为 CSS 时,默认情况下会生成唯一的类名。
- 动态浏览器私有化前缀,使用 CSS-in-JS 可以避免臃肿的 CSS 代码。
- 代码共享,轻松在 JS 和 CSS 之间共享常量和函数。
- CSS-in-JS 的单元化测试。
- TypeScript 的支持。
- 减少项目编译的依赖,纯 JS 或 TS 项目。
- 动态变化的主题和变量。
CSS 架构和工程化推荐
来到本文的核心部分了,给大家介绍一套我自己用的比较舒服的 CSS 架构体系,方便大家能够将这部分知识快速消化。 采用以下技术体系:
tailwindcss进行行内样式表的直接书写,尽可能减少样式表文件的产生styled-components解决组件内部样式封装和动态主题功能polishedCSS 工具函数,阮一峰老师推荐,配合styled-components减少 CSS 代码量Less使用 Less 预处理器将公共组件的样式进行提取,进行公共样式的复用。typescript-plugin-css-modules帮助import styles from 'xxx.less'的字段推导和Typescript 的严格校验,避免无用类和样式漏写的尴尬。
下面介绍这套CSS架构使用场景。本人日常开发比较注意对潜在实用场景的预留扩展余地,方便对业务后续扩展的快速支持。
动态主题
动态主题比主题定制要更进一步,要实现能够在运行阶段进行样式主题的调整,而不是定制主题的CSS编译阶段调整,因此在日常开发中可以预留一部分这块的设计。
样式定制根据组件类别的不同,方案也有所调整。例如组件库或者抽象组件的动态主题往往要依赖CSS Variables来实现(有兴趣可以看下《一文讲透CSS变量和动态主题的内在联系》),业务组件由于没有这种机制,需要借助CSS in JS手动做,更加方便维护和易读。
import React, { useContext } from 'react';
import styled from 'styled-components';
import { themeContext, IThemeConfig} from '../../../store/themeContext';
export default function() {
const { themeConfig, dispatch } = useContext(themeContext);
const Title = styled.h1`
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
color: ${themeConfig.color}
`
return (
<div>
<Title>展示每个小区的匹配雷达图</Title>
<div>主要展示交通,新旧,配套,检索符合度,学区等维度进行分析,需要相关的爬虫和算法支持</div>
</div>
);
}
可以将样式定制的样式变量作为themeContext的默认值,同时将和 CSS variable 的变量修改能力进行融合,提供统一的样式动态变化的方法。
CSS 工具函数 && atomic CSS
从上面的代码大家实际写的 CSS 代码并没有减少,这里推荐大家使用 polished 这个库。它将一些常用的 CSS 属性封装成函数,用起来非常方便,充分体现使用 JavaScript 语言写 CSS 的优势。
配合styled-components的效果如下
import React from 'react';
import styled from 'styled-components';
import { clearFix, ellipsis } from 'polished';
export default function HouseIndex() {
const Title = styled.div`
${clearFix()}
${ellipsis('250px')}
`;
return (
<div className='container'>
<Title>展示抓取的数据,目的获取关注小区的最新售价,和挂牌价变化范围</Title>
<div className='font-bold underline'>加粗下划线</div>
</div>
);
}

配合一些原子化的CSS(tailwind本身支持按需加载,无需担心CSS的冗余),整体代码效果要比单纯使用 CSS 预处理器好太多了。首先没有了样式表,其次代码量行数少了,使用styled-components生成自定义标签让代码整体更加可读。
类名补全
不可避免的时候,对于一些公共组件还是要依赖Less方式进行样式管理,它毕竟更加规范严谨,功能也更加强大。
这里推荐一个工具帮助大家更加科学的书写样式文件-typescript-plugin-css-modules。
当你使用样式对象时,这个工具可以自动补全样式文件里的类名,同时加入你写了无用类名或者类名未定义,也会有对应的错误提示。
落地工程
本文的所有介绍均在Github《wheels》轮子项目中有具体的细节。
假如你在上面的实践过程中发现安装或者不生效的难题,可以进到项目中参照具体的配置进行调整。
贴心如我,是否可以获得你宝贵的赞呢!
结尾
个人觉得前端重复性工作无法避免,但是可以通过工程化、设计等方法减少这部分的消耗,解放大家的生产力,进而将更多的时间投入更加有意义的方向上去,也希望本文的探索能够帮助你节省到你的时间,解放自己的创造力和激情。
转载自:https://juejin.cn/post/7179158581462171705