欢迎来到前端开发领域一个极具争议的话题! 我相信你们中的大多数人在阅读本文时,都遇到过关于如何在 JavaScript 应用中处理 CSS 的众多激烈讨论。
我想在本文开头声明一点:**没有任何硬性规定确定在 React、Vue 或 Angular 应用中处理 CSS 的一种方法优于其他方法。** 每个项目都不同,每种方法都有其优点! 这可能看起来模棱两可,但我确实知道我们所处的开发社区充满了不断寻求新信息并希望以有意义的方式推动 Web 发展的人们。
抛开对该主题的先入为主的观念,让我们来看看 CSS 架构的迷人世界吧!
让我们数数有多少种方法
只需在 Google 上搜索“如何在 [插入框架名称] 中添加 CSS”,就会出现大量关于如何将样式应用于项目的强烈信念和观点。 为了帮助消除一些噪音,让我们从高层次上检查一些更常用的方法及其用途。
选项 1:一个普通的样式表
我们先从一种熟悉的方法开始:一个普通的样式表。 我们绝对可以在我们的应用中使用 <link>
链接到一个外部样式表,然后就完成了。
<link rel="stylesheet" href="styles.css">
我们可以编写我们习惯使用的普通 CSS,然后继续我们的生活。 这完全没有问题,但是随着应用变得越来越大,越来越复杂,维护单个样式表也变得越来越困难。 解析负责为整个应用设置样式的数千行 CSS 对于任何参与项目的开发人员来说都是一项痛苦的任务。 级联也是一件很棒的事情,但它也变得难以管理,因为您(或项目中的其他开发人员)编写的某些样式会引入应用其他部分的回归。 我们之前遇到过这些问题,并且像 Sass(以及最近的 PostCSS)这样的工具被引入来帮助我们解决这些问题。
我们可以继续沿着这条路走下去,利用 PostCSS 的强大功能编写非常模块化的 CSS 部分,这些部分通过 @import
规则连接在一起。 这需要在 webpack 配置中进行一些设置才能正确设置,但肯定是可以处理的!
无论您最终决定使用(或不使用)什么编译器,您都将通过标题中的 <link>
标签提供一个包含应用所有样式的 CSS 文件。 根据应用的复杂性,此文件有可能变得非常庞大,难以异步加载,并阻止应用的其余部分呈现。(当然,呈现阻止并不总是坏事,但出于所有意图和目的,我们将在此处进行一些概括,并在任何可能的情况下避免呈现阻止脚本和样式。)
这并不是说这种方法没有其优点。 对于小型应用,或由不太关注前端的团队构建的应用,单个样式表可能是不错的选择。 它在业务逻辑和应用样式之间提供了清晰的分离,并且因为它不是由我们的应用生成的,因此完全在我们控制之下,以确保我们编写的代码与输出的代码完全一致。 此外,单个 CSS 文件对于浏览器来说相当容易缓存,因此回头客在下次访问时不必重新下载整个文件。
但是假设我们正在寻找更强大的 CSS 架构,该架构允许我们利用工具的强大功能。 有些东西可以帮助我们管理需要更细致入微的方法的应用。 让我们引入 CSS Modules。
选项 2:CSS Modules
单个样式表中一个相当大的问题是回归的风险。 编写使用相当非特定选择器的 CSS 可能会最终更改应用完全不同区域中的另一个组件。 这就是“作用域样式”方法派上用场的地方。
作用域样式允许我们以编程方式生成特定于组件的类名。 从而将这些样式限定到该特定组件,确保它们的类名是唯一的。 这会导致自动生成的类名,例如 header__2lexd
。 末尾的部分是一个唯一的哈希选择器,因此即使您有另一个名为 header 的组件,您也可以向其应用 header 类,并且我们的作用域样式会生成一个新的哈希后缀,如下所示:header__15qy_
。
CSS Modules 提供了多种方法(取决于实现)来控制生成的类名,但我将把这留给 CSS Modules 文档 来介绍。
**最终,我们仍然生成一个通过标题中的 <link>
标签传递给浏览器的单个 CSS 文件。** 这与我们上面介绍的相同潜在缺点(呈现阻止、文件大小膨胀等)和一些优点(主要是缓存)相关联。 但是这种方法,由于其限定样式的目的,还附带另一个注意事项:至少最初会删除全局作用域。
假设您想要使用 .screen-reader-text
全局类,该类可以应用于应用中的任何组件。 如果使用 CSS Modules,则必须使用 :global
伪选择器,该选择器明确地将其中的 CSS 定义为允许应用中其他组件全局访问的内容。 只要您将包含 :global
声明块的样式表导入到组件的样式表中,您就可以使用该全局选择器。 这不是一个很大的缺点,但需要一些时间来适应。
以下是在实际中使用 :global
伪选择器的示例
// typography.css
:global {
.aligncenter {
text-align: center;
}
.alignright {
text-align: right;
}
.alignleft {
text-align: left;
}
}
您可能会冒着将大量用于排版、表单和大多数网站都具有的通用元素的全局选择器放到一个 :global
选择器中的风险。 幸运的是,通过 PostCSS Nested 或 Sass 等工具的魔力,您可以将部分直接导入到选择器中,使事情变得更清晰
// main.scss
:global {
@import "typography";
@import "forms";
}
这样,您就可以在没有 :global
选择器的情况下编写部分,然后直接将它们导入到主样式表中。
另一个需要适应的地方是如何在 DOM 节点中引用类名。 我将让 Vue、React 和 Angular 的各个文档自行说明。 我还将为您提供一个在 React 组件中使用这些类引用的示例
// ./css/Button.css
.btn {
background-color: blanchedalmond;
font-size: 1.4rem;
padding: 1rem 2rem;
text-transform: uppercase;
transition: background-color ease 300ms, border-color ease 300ms;
&:hover {
background-color: #000;
color: #fff;
}
}
// ./Button.js
import styles from "./css/Button.css";
const Button = () => (
<button className={styles.btn}>
Click me!
</button>
);
export default Button;
再次强调,CSS Modules 方法有一些很好的用例。 对于希望利用作用域样式同时保持静态但已编译样式表性能优势的应用,CSS Modules 可能是适合您的选择!
这里也值得注意的是,CSS Modules 可以与您喜欢的 CSS 预处理程序结合使用。 Sass、Less、PostCSS 等都可以集成到利用 CSS Modules 的构建过程中。
但是假设您的应用可以从包含在 JavaScript 中获益。 也许能够访问组件的各种状态,并根据不断变化的状态做出反应,也将是有益的。 假设您还想轻松地将关键 CSS 集成到您的应用中! 让我们引入 CSS-in-JS。
选项 3:CSS-in-JS
CSS-in-JS 是一个相当广泛的主题。 有几个软件包致力于使编写 CSS-in-JS 尽可能轻松。 像 JSS、Emotion 和 Styled Components 这样的框架只是构成该主题的众多软件包中的一小部分。
作为大多数这些框架的概括性解释,CSS-in-JS 的工作方式大体相同。 您编写与单个组件关联的 CSS,然后您的构建过程编译应用。 发生这种情况时,大多数 CSS-in-JS 框架将输出仅在任何给定时间呈现页面上的组件的相关 CSS。 CSS-in-JS 框架通过在应用的 <head>
中的 <style>
标签内输出该 CSS 来实现此目的。 这为您提供了一种开箱即用的关键 CSS 加载策略! 此外,与 CSS Modules 非常相似,样式是限定范围的,并且类名是哈希化的。
当你在应用程序中导航时,卸载的组件的样式将从<head>
中移除,而加载的组件的样式将追加到其位置。这为应用程序提供了提升性能的机会。它减少了一个HTTP请求,不会阻塞渲染,并确保用户在任何给定时间仅下载查看页面所需的内容。
CSS-in-JS 提供的另一个有趣的机会是能够引用各种组件状态和函数以渲染不同的 CSS。这可能像基于某些状态更改复制类切换一样简单,也可能像主题化一样复杂。
由于 CSS-in-JS 是一个相当#热议的话题,我意识到人们正在尝试使用很多不同的方法来解决这个问题。现在,我与许多其他将 CSS 视为高度重视的人有同样的感受,尤其是在利用 JavaScript 来编写 CSS 时。我最初对这种方法的反应相当负面。我不喜欢将两者交叉污染的想法。但我想保持开放的心态。让我们看看我们作为专注于前端的开发者为了甚至考虑这种方法需要哪些功能。
- 如果我们正在编写 CSS-in-JS,我们必须编写真正的 CSS。一些软件包提供了编写模板字面量 CSS 的方法,但要求你将属性转换为驼峰命名法——例如,
padding-left
变成了paddingLeft
。那不是我个人愿意牺牲的。 - 一些 CSS-in-JS 解决方案要求你在尝试设置样式的元素上内联编写样式。这种语法,尤其是在复杂的组件中,开始变得非常混乱,而且同样不是我愿意牺牲的。
- 使用 CSS-in-JS 必须为我提供强大的工具,否则使用 CSS Modules 或普通的样式表很难实现。
- 我们必须能够利用前瞻性的 CSS,如嵌套和变量。我们还必须能够整合诸如Autoprefixer之类的功能和其他插件来增强开发体验。
这对一个框架来说要求很高,但对于我们这些大部分时间都在学习和实施围绕我们所爱的语言的解决方案的人来说,我们必须确保能够尽可能地继续编写相同的语言。
以下快速了解一下使用 Styled Components 的 React 组件可能是什么样子
// ./Button.js
import styled from 'styled-components';
const StyledButton= styled.button`
background-color: blanchedalmond;
font-size: 1.4rem;
padding: 1rem 2rem;
text-transform: uppercase;
transition: background-color ease 300ms, border-color ease 300ms;
&:hover {
background-color: #000;
color: #fff;
}
`;
const Button = () => (
<StyledButton>
Click Me!
</StyledButton>
);
export default Button;
我们还需要解决 CSS-in-JS 解决方案的潜在缺点——当然不是为了引发更多争议。使用这种方法,我们很容易陷入一个陷阱,导致我们得到一个膨胀的 JavaScript 文件,其中可能包含数百行 CSS——所有这些都发生在开发者甚至看到任何组件方法或其 HTML 结构之前。但是,我们可以将其视为一个机会,非常仔细地检查我们构建组件的方式和原因。通过更深入地思考这一点,我们有可能将其用于我们的优势,并编写更精简的代码,以及更多可重用的组件。
此外,这种方法绝对模糊了业务逻辑和应用程序样式之间的界限。但是,通过良好的文档和经过深思熟虑的架构,项目中的其他开发者可以轻松地接受这个想法,而不会感到不知所措。
太长不看
在任何项目中,都有几种方法可以处理 CSS 架构这个难题,并且在使用任何框架时都可以这样做。事实上,我们作为开发者,拥有如此多的选择,既令人兴奋,又令人难以置信地不知所措。然而,我认为在最终进行的超短社交媒体对话中不断被遗漏的总体主题是,每种解决方案都有其自身的优点和缺点。关键在于我们如何仔细而有条理地实施一个系统,让未来的我们,或者可能接触到代码的其他开发者,感谢我们花时间建立这种结构。
这缺少了 Vue 和 Svelte 通过其单文件组件提供的最优雅的解决方案。你可以获得预/后处理器的所有功能,以及自动作用域的所有优势,并且你可以在需要时只提供所需的 CSS。
它具有传统样式表的所有优点以及 CSS-in-JS 的优点,而无需强制你的设计师或 HTML/CSS 开发人员深入学习框架或 JS。
这是两全其美,奇怪的是似乎没有人知道它。
很好的观点!这绝对是另一种我本人接触不多的方法。我确实看到了很多关于 Vue 和 Svelte 处理样式方式的赞誉。很多人肯定喜欢这种单文件方法。一旦我本人对这种方法有了更多了解,后续跟进绝对值得一试!
热门观点:将从短横线连接符命名法属性切换到驼峰命名法属性视为“牺牲”表明在没有考虑价值的情况下对新事物/不同事物的某种抵制。
以这种方式编写 CSS 属性可以让你完全放弃预处理,并使用功能齐全的编程语言来组合你的选择器。你可以声明性地合并和去重各个样式,在没有 PostCSS 解析器和 AST 开销的情况下尽情使用自定义属性,并使用 matchMedia 在样式和前端代码之间共享媒体查询。你可以让 JS 变量覆盖 CSS 变量,然后让 JS 根据视口或其他条件更改这些变量的值。等等,等等。
我认为并非每个团队都需要采用这种方法,我也不会假装它没有学习曲线。它是否与我们大多数人在过去 5-10 年中一直在实践的内容有很大不同?当然。它是否绝对地、声明性地、客观地成为 2019 年及以后编写 CSS 的更强大的方法?是的,100%。如果你不需要从 Sass/PostCSS/等中获得更多内容,或者你无法负担在拥有 310 名开发人员的整个组织中采用新技术和流程,那么请继续做你正在做的事情,只是不要花时间告诉其他人切换大小写是“牺牲”,或者我们仍然不能像成年人一样组织我们的代码(你的 CSS 不必在市场上任何 CSS-in-JS 解决方案中内联编写),因为这不是真的。
我可能在措辞上有点失误。希望像
text-align
或grid-template-columns
那样编写 CSS 规范中存在的现有 CSS 属性纯粹是个人喜好。作为那些实践 CSS 10 年的人之一,我更倾向于一种编写 CSS 的清教徒观点。CSS-in-JS 对我来说已经是与规范的巨大偏离,因此坚持像短横线连接符命名法 CSS 属性这样的东西对我个人来说很有吸引力。我毫无疑问地感谢你的观点,绝不打算贬低任何一种编写 CSS 属性的方式。JS 变量匹配 CSS 变量绝对很有吸引力,并且是我在继续尝试 CSS-in-JS 时将进一步探索的内容。整个生态系统都是新的和令人兴奋的,在未来 5-10 年中,看看这个领域如何标准化将会非常有趣!
Tailwind 是一个用于处理 CSS 和生成设计系统的简洁工具。
https://tailwind.org.cn/
在 JavaScript 应用程序中处理 CSS 架构的几种方法。虽然在前端开发人员中被认为是一个有争议的话题,但重要的是要记住,每种方法,无论是在 React、Vue 还是 Angular 应用程序中,都有其自身的优点和缺点。在最近发表在 CSS-Tricks(涵盖 Web 技术的领先数字出版物之一)上的文章中