如何设计 CSS 架构解放前端生产力?

lxf2023-03-12 19:03:01

前言

CSS 是前端开发最熟悉的部分,几乎每一个项目都会大量使用到这些基础的东西,但是越是最基础的往往是大家最忽略。
作为一名前端开发,除了大家追求的性能之外,是否忽略了极致的开发体验呢?我最近审视了下自己过往前端项目,问了自己几个问题。那就是自己日常开发中写 CSS

  • 是否足够快?
  • 是否规模可控?
  • 是否简单明了?
  • 是否在验收中频繁改动?

事实上当我真正看自己之前写的代码时候,发现即使自己严格遵循 Less + BEM 规则,我写的 CSS 代码依旧量很大,随着时间推移,规模持续增加,且难以复用和理解。

灵魂拷问是哪里出问题了呢?看了下项目的 CSS 命名规范、DOM层级结构看着也都是相对合理的,且上升空间不大了。一次和师兄聊这个事情的时候,他提到了可能需要的是CSS架构调整。

CSS架构属于是那种在记忆的尘埃中封存已久的事物了,它实际是伴随ReactVue的发展一起快速成长的,早在17年就已经大部分定型,也就是大家所熟知的CSS in JSCSS预编译。本文期望通过对 CSS 发展的一些回顾、核心观念的轻解读和详尽的实践,来帮助大家设计合理的 CSS架构 来解放前端生产力。

CSS的现状

目前大家主流使用的是 CSS3,传统前端项目大部分采用的 React 或者 Vue 两者其中一个的全家桶,CSS 的编写方式也大多采用是的 LessPostCSSSASS 等支持 CSS Modules 的 CSS 预处理方案,结合一些 CSS 插件完美解决浏览器兼容问题,CSS体积问题,样式复用的问题。

到这里大家可以问下自己:可是这样就足够了吗?看下之前的前端项目 CSS 的体积

如何设计 CSS 架构解放前端生产力? 也就说即使在做了Code Split的情况下也要 112KB 的大小,同时对比了下淘宝网页的CSS体积 14.9KB

如何设计 CSS 架构解放前端生产力? 特别是这些 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 架构解放前端生产力?

这种方案确定也很明显,行内样式不支持伪类、媒体查询,而且随着前端组件模块化概念的兴起,CSS Modules的概念也兴起了,进而催生了CSS in JSCSS 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

这里大家比较熟悉了,预编译起的功能就是将LessSass等编写的文件编译成常规CSS,并且结合Webpack等前端工程化工具自动插入到HTML中。

如何设计 CSS 架构解放前端生产力? 从上图中显而易见,编写 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 的优势。

如何设计 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 架构解放前端生产力?

配合一些原子化的CSS(tailwind本身支持按需加载,无需担心CSS的冗余),整体代码效果要比单纯使用 CSS 预处理器好太多了。首先没有了样式表,其次代码量行数少了,使用styled-components生成自定义标签让代码整体更加可读。

类名补全

不可避免的时候,对于一些公共组件还是要依赖Less方式进行样式管理,它毕竟更加规范严谨,功能也更加强大。 这里推荐一个工具帮助大家更加科学的书写样式文件-typescript-plugin-css-modules

如何设计 CSS 架构解放前端生产力? 当你使用样式对象时,这个工具可以自动补全样式文件里的类名,同时加入你写了无用类名或者类名未定义,也会有对应的错误提示。

落地工程

本文的所有介绍均在Github《wheels》轮子项目中有具体的细节。

假如你在上面的实践过程中发现安装或者不生效的难题,可以进到项目中参照具体的配置进行调整。

贴心如我,是否可以获得你宝贵的赞呢!

结尾

个人觉得前端重复性工作无法避免,但是可以通过工程化、设计等方法减少这部分的消耗,解放大家的生产力,进而将更多的时间投入更加有意义的方向上去,也希望本文的探索能够帮助你节省到你的时间,解放自己的创造力和激情。