龙空技术网

如何管理你的 CSS 代码?

程序员探险记 224

前言:

目前看官们对“简述解决css冲突的规则”大体比较重视,同学们都想要了解一些“简述解决css冲突的规则”的相关内容。那么小编同时在网摘上汇集了一些关于“简述解决css冲突的规则””的相关资讯,希望同学们能喜欢,看官们一起来了解一下吧!

上一文我们从技术细节的角度分析了 CSS 布局的相关内容。本文我们提升一下思考维度,从组织管理的角度探讨如何管理好项目中的 CSS 代码。

接下来我们先解决 CSS 原生语法未能很好实现的模块化和作用域的问题,然后再对代码结构进行优化,提升代码的复用率。

如何组织样式文件

尽管 CSS 提供了 import 命令支持文件引用,但由于其存在一些问题(比如影响浏览器并行下载、加载顺序错乱等)导致使用率极低。更常见的做法是通过预处理器或编译工具插件来引入样式文件,因此本课时的讨论将不局限于以 .css 为后缀的样式文件。

管理样式文件的目的就是为了让开发人员更方便地维护代码。

具体来说就是将样式文件进行分类,把相关的文件放在一起。让工程师在修改样式的时候更容易找到对应的样式文件,在创建样式文件的时候更容易找到对应的目录。

下面我们来看看热门的开源项目都是怎么来管理样式文件的。

开源项目中的样式文件

我们先来看看著名的 UI 相关的开源项目是怎么管理样式文件的。

以 Bootstrap 4.4 为例,项目样式代码结构,可以看出项目使用的是 Sass 预处理器。

该目录包括了 5 个目录、组件样式文件和一些全局样式。再来分析下目录及内容:

forms/,表单组件相关样式;

helpers/,公共样式,包括定位、清除等;

mixins/,可以理解为生成最终样式的函数;

utilities/,媒体查询相关样式;

vendor/,依赖的外部第三方样式。

根目录存放了组件样式文件和目录,其他样式文件放在不同的目录中。目录中的文件分类清晰,但目录结构相对于大多数实际项目而言过于简单(只有样式文件)。

我们再来看一个更符合大多数情况的开源项目 ant-design 4.2,该项目采用 Less 预处理器,主要源码放在 /components 目录下:

从目录名称上不难猜测,各个组件代码通过文件夹区分,点击其中的 alert 文件夹查看也确实如此,组件相关的代码、测试代码、demo 示例、样式文件、描述文档都在里面。

至于全局样式和公共样式则在 /components/style 目录下:

其中包括 4 个目录:

color/,颜色相关的变量与函数;

core/,全局样式,根标签样式、字体样式等;

mixins/,样式生成函数;

themes/,主题相关的样式变量。

将组件代码及相关样式放在一起,开发的时候修改会很方便。 但在组件目录 /comnponents 下设置 style 目录存放全局和公共样式,在逻辑上就有些说不通了,这些“样式”文件并不是一个单独的“组件”。再看 style 目录内部结构,相对于设置单独的 color 目录来管理样式中的颜色,更推荐像 Bootstrap 一样设立专门的目录或文件来管理变量。

最后来看看依赖 Vue.js 实现的热门 UI 库 element 2.13.1 的目录结构。项目根路径下的 packages 目录按组件划分目录来存放其源码,但和 ant-design 不同的是,组件样式文件并没有和组件代码放在一起。下图是 /packages 目录下的部分内容。

element 将样式文件统一放入了 /packages/theme-chalk 目录下,目录部分内容:

其中包含 4 个目录:

common/,一些全局样式和公共变量;

date-picker/,日期组件相关样式;

fonts/,字体文件;

mixins/,样式生成函数及相关变量。

和 antd 有同样的问题,把样式当成“组件”看待,组件同级目录设立了 theme-chalk 目录存放样式文件。theme-chalk 目录下的全局样式 reset.scss 与组件样式同级,这也有些欠妥。这种为了将样式打包成模块,在独立项目中直接嵌入另一个独立项目(可以简单理解为一个项目不要有多个 package.json 文件)并不推荐,更符合 Git 使用规范的做法,即是以子模块的方式引用进项目。 而且将组件样式和源码分离这种方式开发的时候也不方便,经常需要跨多层目录查找和修改样式。

样式文件管理模式

除了开源项目之外,Sass Guidelines 曾经提出过一个用来划分样式文件目录结构的 7-1 模式也很有参考意义。这种模式建议将目录结构划分为 7 个目录和 1 个文件,这 1 个文件是样式的入口文件,它会将项目所用到的所有样式都引入进来,一般命名为 main.scss。

剩下的 7 个目录及作用如下:

base/,模板代码,比如默认标签样式重置;

components/,组件相关样式;

layout/,布局相关,包括头部、尾部、导航栏、侧边栏等;

pages/,页面相关样式;

themes/,主题样式,即使有的项目没有多个主题,也可以进行预留;

abstracts/,其他样式文件生成的依赖函数及 mixin,不能直接生成 css 样式;

vendors/,第三方样式文件。

由于这个划分模式是专门针对使用 Sass 项目提出的,从样式文件名称看出还留有 jQuery 时代的影子,为了更加符合单页应用的项目结构,我们可以稍作优化。

main.scss 文件存在意义不大,页面样式、组件样式、布局样式都可以在页面和组件中引用,全局样式也可以在根组件中引用。而且每次添加、修改样式文件都需要在 main.scss 文件中同步,这种过度中心化的配置方式也不方便。

layout 目录也可以去除,因为像 footer、header 这些布局相关的样式,放入对应的组件中来引用会更好,至于不能被组件化的“_grid”样式存在性也不大。因为对于页面布局,既可以通过下面介绍的方法来拆分成全局样式,也可以依赖第三方 UI 库来实现。所以说这个目录可以去除。

themes/ 目录也可以去除,毕竟大部分前端项目是不需要设置主题的,即使有主题也可以新建一个样式文件来管理样式变量。

vendors/ 目录可以根据需求添加。因为将外部样式复制到项目中的情况比较少,更多的是通过 npm 来安装引入 UI 库或者通过 webpack 插件来写入对应的 cdn 地址。

所以优化后的目录结构如下所示:

src/||– abstracts/|   |– _variables.scss    |   |– _functions.scss    |   |– _mixins.scss       |   |– _placeholders.scss ||– base/|   |– _reset.scss        |   |– _typography.scss   |   …                     ||– components/|   |– _buttons.scss      |   |– _carousel.scss     |   |– _cover.scss        |   |– _dropdown.scss     |   |- header/|      |- header.tsx|      |- header.sass|   |- footer/|      |- footer.tsx|      |- footer.sass|   …                     ||– pages/|   |– _home.scss         |   |– _contact.scss      |   …                     |

这只是推荐的一种目录结构,具体使用可以根据实际情况进行调整。比如我在项目的 src 目录下创建了模块目录,按照模块来拆分路由以及页面、组件,所以每个模块目录下都会有 pages/ 目录和 components/ 目录。

如何避免样式冲突

由于 CSS 的规则是全局的,任何一个样式规则,都对整个页面有效,所以如果不对选择器的命名加以管控会很容易产生冲突。

手动命名

最简单有效的命名管理方式就是制定一些命名规则,比如 OOCSS、BEM、AMCSS,其中推荐比较常用的 BEM。

这里简单补充一下 BEM 相关知识,熟悉 BEM 的可以直接跳过。

BEM 是 Block、Element、Modifier 三个单词的缩写,Block 代表独立的功能组件,Element 代表功能组件的一个组成部分,Modifier 对应状态信息。

从命名可以看到 Element 和 Modifier 是可选的,各个单词通过双横线(也可以用双下划线)连接(双横线虽然能和单词的连字符进行区分,但确实有些冗余,可以考虑直接用下划线代替)。BEM 的命名方式具有语义,很容易理解,非常适用于组件样式类。

工具命名

通过命名规范来避免冲突的方式固然是好的,但这种规范约束也不能绝对保证样式名的唯一性,而且也没有有效的校验工具来保证命名正确无冲突。所以,聪明的开发者想到了通过插件将原命名转化成不重复的随机命名,从根本上避免命名冲突。比较著名的解决方案就是 CSS Modules。

下面是一段 css 样式代码:

/* style.css */.className {  color: green;}借助 css Modules 插件,可以将 css 以 JSON 对象的形式引用和使用。复制import styles from "./style.css";// import { className } from "./style.css";element.innerHTML = '<div class="' + styles.className + '">';编译之后的代码,样式类名被转化成了随机名称:复制<div class="_3zyde4l1yATCOkgn-DBWEL"></div><style>._3zyde4l1yATCOkgn-DBWEL {  color: green;}</style>

但这种命名方式带来了一个问题,那就是如果想在引用组件的同时,覆盖它的样式会变得困难,因为编译后的样式名是随机。例如,在上面的示例代码中,如果想在另一个组件中覆盖 className 样式就很困难,而在手动命名情况下则可以直接重新定义 className 样式进行覆盖。

如何高效复用样式

如果你有一些项目开发经历,一定发现了某些样式会经常被重复使用,比如:

display:inline-blockclear:bothposition:relative......

这违背了 DRY(Don't Repeat Yourself)原则,完全可以通过设置为全局公共样式来减少重复定义。

哪些样式规则可以设置为全局公共样式呢?

首先是具有枚举值的属性,除了上面提到的,还包括 cursor:pointer、float:left 等。

其次是那些特定数值的样式属性值,比如 margin: 0、left: 0、height: 100%。

最后是设计规范所使用的属性,比如设计稿中规定的几种颜色。

样式按照小粒度拆分之后命名规范也很重要,合理的命名规范可以避免公共样式重复定义,开发时方便快速引用。

前面提到的语义化命名方式 BEM 显然不太适合。首先全局样式是基于样式属性和值的,是无语义的;其次对于这种复用率很高的样式应该尽量保证命名简短方便记忆,所以推荐使用更简短、更方便记忆的命名规则。比如我们团队所使用的就是“属性名首字母 + 横线 + 属性值首字母”的方式进行命名。

举个例子,比如对于 display:inline-block 的样式属性值,它的属性为“display”缩写为“d”,值为“inline-block”,缩写为“ib”,通过短横线连接起来就可以命名成“d-ib”;同样,如果工程师想设置一个 float:left 的样式,也很容易想到使用“f-l”的样式名。

那会不会出现重复定义呢?这个问题很好解决,按照字母序升序定义样式类就可以了。

延伸:值得关注的 CSS in JavaScript

我们都知道 Web 标准提倡结构、样式、行为分离(分别对应 HTML、CSS、JavaScript 三种语言),但 React.js 的一出现就开始颠覆了这个原则。

先是通过 JSX 将 HTML 代码嵌入进 JavaScript 组件,然后又通过 CSS in JavaScript 的方式将 CSS 代码也嵌入进 JavaScript 组件。这种“all in JavaScript”的方式确实有悖 Web 标准。但这种编写方式和日益盛行的组件化概念非常契合,具有“高内聚”的特性,所以未来标准有所改变也未尝不可能。这也正是我们需要关注 CSS in JavaScript 技术的原因。

相对于使用预处理语言编写样式,CSS in JavaScript 具有两个不那么明显的优势:

可以通过随机命名解决作用域问题,但命名规则和 CSS Modules 都可以解决这个问题;

样式可以使用 JavaScript 语言特性,比如函数、循环,实现元素不同的样式效果可以通过新建不同样式类,修改元素样式类来实现。

我们以 styled-compoents 为例进行说明,下面是示例代码,第一段是源代码:

// 源代码const Button = styled.button`  background: transparent;  border-radius: 3px;  border: 2px solid palevioletred;  color: palevioletred;  margin: 0.5em 1em;  padding: 0.25em 1em;  ${props => props.primary && css`    background: palevioletred;    color: white;  `}`;const Container = styled.div`  text-align: center;`render(  <Container>    <Button>Normal Button</Button>    <Button primary>Primary Button</Button>  </Container>);第二段是编译后生成的:复制<!--HTML 代码--><div class="sc-fzXfNJ ciXJHl">  <button class="sc-fzXfNl hvaMnE">Normal Button</button>  <button class="sc-fzXfNl kiyAbM">Primary Button</button></div>/*CSS 代码*/.ciXJHl {  text-align: center;}.hvaMnE {  color: palevioletred;  background: transparent;  border-radius: 3px;  border-width: 2px;  border-style: solid;  border-color: palevioletred;  border-image: initial;  margin: 0.5em 1em;  padding: 0.25em 1em;}.kiyAbM {  color: white;  border-radius: 3px;  border-width: 2px;  border-style: solid;  border-color: palevioletred;  border-image: initial;  margin: 0.5em 1em;  padding: 0.25em 1em;  background: palevioletred;}

对比以上两段代码很容易发现,在编译后的样式代码中有很多重复的样式规则。这并不友好,不仅增加了编写样式的复杂度和代码量,连编译后也增加了冗余代码。

styled-components 只是 CSS in JavaScript 的一种解决方案,其他解决方案还有很多,有兴趣的同学可以点击这里查阅 GitHub 上的资料学习,上面收录了现有的 CSS in JavaScript 解决方案。

总结:

对于样式文件的管理,推荐使用 7-1 模式简化后的目录结构,包括 pages/、components/、abastracts/、base/ 4 个目录。对于样式命名,可以采用 BEM 来命名组件、面向属性的方式来命名公共样式。

最后留一道思考题:说说你在项目中是如何管理样式代码的?

标签: #简述解决css冲突的规则