大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!
最近,Emotion 排名第二的维护者 Sam 所在公司弃用了 CSS-in-JS 方案,引起了不小的讨论。这也是我第一次开始重点关注 CSS-in-JS,我甚至在头条开了一个合集重点讨论 CSS-in-JS 的方案,下面是已经发表的关于 CSS-in-JS 的文章:
我希望通过系列文章的方式带着大家深入的了解 CSS-in-JS,包括它的优势、缺点、编译时运行时的不同等等,最终让大家对写下的每一行代码都持有足够的信心。话不多说,直接开始进入正题!
在现代 React 应用程序开发中,有许多组织应用程序样式的方法, 比如: CSS-in-JS ,CSS Modules 等等。 本篇文章将尝试回答 CSS-in-JS 和 CSS Modules 哪个更好?
很久以前,当网页主要功能是存储文本文档并且不包括用户交互时,会引入属性来设置内容的样式。 随着时间的推移,Web 变得越来越流行,站点变得越来越大,并且有必要重用样式, 此时 CSS ,即级联样式表应运而生。
随着时间的流逝,Web 变得越来越复杂,开发者也面临着 CSS 带来的诸多问题。比如团队合作面临的:样式重用、样式覆盖、样式一致性等诸多问题。此时,一系列的样式命名约定横空出世,例如: Yandex 的 BEM 或 Atomic CSS,通过这些方式在一定程度上解决了样式的可预测性、同时防止命名重复。
但是,约定是脆弱的,没有强制性而且需要过度分散开发者的注意力,最重要的是很可能因为人为因素被破坏。不过幸运的是,后面出现了一系列的 CSS 样式组织技术,比如:CSS Modules(基于文件名、路径、样式名称的散列来转换样式)、CSS-in-JS(JS 运行时将样式添加到 head) 等等。从而从根本上解决了编写 CSS 的原子性、可重用性和副作用。
CSS Modules 是一个 CSS 文件,默认情况下,所有类名和动画名称都在本地范围内(scoped locally)有效。除非使用:gloabl 语法显示声明为全局样式,比如下面的例子:
// 本地有效:local(.title) { color: red;}//全局有效//凡是类:global声明的class,都不会被编译成哈希字符串。:global(.title) { color: green;}
CSS Modules 允许开发者在 CSS 文件中编写样式,但程序将它们作为 JavaScript 对象使用以进行额外的处理,同时保证安全性。 CSS Modules 目前非常流行,因为它们自动使类和动画名称唯一,因此开发者无需担心选择器名称冲突。
从等式上看,CSS Modules = JS 对象中的 CSS。CSS Modules 与 CSS 代码结构大致相同,主要区别是调用方法的差异。以静态站点生成器 SSG 的 Gatsby 为例,后缀为.module.css 的文件将自动当做 CSS Module 处理,这与大多数框架的处理机制非常类似。假如有下面名称为 container.module.css 的文件:
// src/components/container.module.css.container { margin: 3rem auto; max-width: 600px;}
下面是对 container.module.css 文件的引用:
// src/components/container.jsimport React from 'react';import * as containerStyles from './container.module.css';export default function Container({ children }) { return <section className={containerStyles.container}>{children}</section>;}
在上面的 Container 组件示例中,导入了一个 CSS 模块并将其声明为名为 containerStyles 的 JavaScript 对象。 然后在带有 containerStyles.container 的 JSX className 属性中引用来自该对象的 CSS 类,它将自动替换为动态 CSS 类名称,比如:
container-module--container--3MbgH,最终渲染到 HTML 中。
CSS Module 提供的最明显的好处是消除了对 CSS-in-JS 的依赖,以解决作用域(scoping)和特异性问题。以下总结了几点 CSS Modules 的独特优势:
虽然 CSS Modules 提供了以上声明的诸多好处,但它也并不是一个完美的解决方案,可以总结为以下几个点:
比如下面的例子:
/** index.d.ts **/declare module "*.module.css";// CSS 模块文件的 TS 模块declare module "*.module.scss";// SCSS 中 CSS 模块文件的 TS 模块
当然也可以充分利用
typescript-plugin-css-modules 插件,它是向 TypeScript 语言服务提供有关包含导入的 CSS Modules 文件的类选择器的信息的插件。 对于 VSCode,需要设置 TypeScript LS 来使用工作区版本。
// 插件添加到tsconfig.json文件{ "compilerOptions": { "plugins": [{ "name": "typescript-plugin-css-modules" }] }}
CSS-in-JS 允许开发者通过 JavaScript 为组件编写 CSS 属性。CSS-in-JS 始于 2015 年一个名为 JSS 的 JavaScript 库,该库仍在积极维护中。 开发者必须使用 JavaScript 语法向选择器提供 CSS 属性,然后在页面加载后自动将这些属性应用于它们各自的选择器。
当 JavaScript 接管渲染和管理类 React 的前端库时,一个名为 styled-components 的 CSS-in-JS 解决方案应运而生。 另一种越来越流行的 Emotion 库也逐渐赢得开发者的好评。比如下面是使用 styled-components 的例子:
import styled from 'styled-components';const StyledButton = styled.a` padding: 0.75em 1em; background-color: ${({ primary }) => (primary ? '#07c' : '#333')}; color: white; &:hover { background-color: #111; }`;export default StyledButton;
当然在 CSS-in-JS 的细分场景又分为编译时和运行时方案,两者在输出样式的时机存在差异。
不论是编译时,还是运行时 CSS-in-JS 方案,两者都有一些各自的优缺点。
JavaScript 开发人员可能更喜欢使用 CSS-in-JS 来设计样式,而不是通过 CSS 类。 CSS-in-JS 方法解决的最大问题是全局范围。 如果您是 JavaScript 开发人员,它还有一些其他优势,这些优势非常有意义。
CSS-in-JS 确实很好地解决了作用域等诸多问题, 但开发者仍然面临着更大的挑战。比如:渲染阻塞直接影响用户体验。 除此之外,CSS-in-JS 还需要解决一些其他问题。
以上 CSS-in-JS 面临的诸多问题可能会共同导致产品性能低下、难以维护,其中就包含 UI 和 UX 的不一致。
如果有一个具有大型 UI 、性能关键的应用程序,那么 CSS Modules 是一个不错的选择。 由于 CSS Module 提供的所有内容最终都是基于传统的、非实验性的基础能力,因此这种方法可以更轻松地监控和修复性能。
同时,CSS Modules 文件很容易从选择的任何 CSS 框架中改进代码,因为所处理的只是 CSS。 如前所述,CSS 的一些基本知识足以让开发者快速完成任务。
目前 CSS 的诸多最新特性也值得开发者持续关注,例如:作用域指令和@scope 伪元素,其旨在解决传统 CSS 的老问题。
<div class="card"> <img src="..." /> <div class="content"> <p>...</p> </div> <!-- 内容 --></div><!-- 卡片 --><style> /* :scope CSS 伪类表示作为选择器匹配参考点的元素。 */ @scope (.card) { :scope { display: grid; } img { object-fit: cover; } .content { } }</style>
随着这个新功能的引入,以后开发者可能不再需要 CSS Module 或 CSS-in-JS 来解决作用域问题,大家拭目以待。
注意:当在样式表中使用时,:scope 与 :root 作用相同,因为此时没有明确建立作用域元素的方法。 当从 DOM API(例如: querySelector()、querySelectorAll()、matches() 或 Element.closest() 中使用时,:scope 匹配调用该方法的元素。
当在处理性能优先级较低、体积较小的应用程序时,CSS-in-JS 解决方案是理想的选择。 而在处理具有庞大设计系统的、性能关键型的应用程序时可以尽量避免使用 CSS-in-JS。
因为,随着应用程序变得越来越大,使用 CSS-in-JS 很容易将问题变得复杂。 将设计系统转换为 CSS-in-JS 需要做大量工作,在我看来,没有 JavaScript 开发人员愿意处理这个问题。
本文主要和大家探讨 “2023年CSS-in-JS 和 CSS Modules 谁才是最终赢家?”这个问题。因为篇幅有限,文章并没有过多展开,如果有兴趣,文末的参考资料提供了优秀文档以供学习。最后,欢迎大家点赞、评论、转发、收藏!
https://marketplace.visualstudio.com/items?itemName=mrmlnc.vscode-scss
https://dev.to/fyapy/sass-vs-css-modules-vs-css-in-js-vs-compile-time-css-in-js-who-wins-4cl
https://dev.to/alexsergey/css-modules-vs-css-in-js-who-wins-3n25
https://www.gatsbyjs.com/docs/how-to/styling/css-modules/
https://blog.logrocket.com/css-vs-css-in-js/
https://blog.logrocket.com/css-vs-css-in-js/#recommendations-where-use-css-in-js
https://github.com/mrmckeb/typescript-plugin-css-modules#visual-studio-code
https://developer.mozilla.org/en-US/docs/Web/CSS/:scope
https://www.ruanyifeng.com/blog/2016/06/css_modules.html
https://www.diogorodrigues.dev/blog/css-in-js-and-the-death-of-traditional-css