前言
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
解决组件内部样式封装和动态主题功能polished
CSS 工具函数,阮一峰老师推荐,配合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》轮子项目中有具体的细节。
假如你在上面的实践过程中发现安装或者不生效的难题,可以进到项目中参照具体的配置进行调整。
贴心如我,是否可以获得你宝贵的赞呢!
结尾
个人觉得前端重复性工作无法避免,但是可以通过工程化、设计等方法减少这部分的消耗,解放大家的生产力,进而将更多的时间投入更加有意义的方向上去,也希望本文的探索能够帮助你节省到你的时间,解放自己的创造力和激情。