构建时的CSS-in-JS:为何它正在流行起来
两次重要的革命重塑了我们编写和管理CSS的方式:运行时CSS-in-JS和最近从运行时到构建时CSS-in-JS的转变。这两种方法都与可组合的UI组件相契合,能够与现代UI框架无缝集成,并促进了使代码更易于维护的模式。
为了更好地理解后者,我们先来看看前者带来的好处。
运行时的 CSS-in-JS(一种将CSS写入JS中的技术)(无需在构建时提取 CSS)CSS-in-JS 是一种使用 JavaScript 编写 CSS 样式规则的技巧,其中 CSS 是通过 JavaScript 来书写的,通常会与被其样式的组件写在同一文件中。例如
import { css } from '@emotion/css';
const primaryColor = '#4287f5';
const myClass = css\`padding: 32px; color: ${primaryColor};\`;
const StyledDiv = () => <div className={myClass} />;
这项技术及其支持工具变得非常流行,主要是因为它们与可组合的应用程序配合得非常好,以及它们能够提供的更好开发体验。
参见此 Material UI Bit 范围 中的一个示例,或可以参阅这篇关于 创建自定义 Material UI 组件 的文章:
专为组件设计:范围限定且上下文无关的样式,便于组件重用纯CSS本质上是全局的。它在传统的单体应用程序中表现得非常好,这些应用的不同部分紧密耦合在一起,并且“高度依赖于”它们的上下文。在这些应用程序中,我们创建全局CSS文件,应用样式到不同的元素上,通常使用基于特定HTML结构的选择器。
/* 主内容区域文章标题的样式 */
.main-content article h2 {
font-size: 28px;
color: navy;
text-align: center;
}
相比之下,现代的可组合UI常常会重用组件,不仅限于应用的不同部分,甚至跨应用使用。
组件会被插入到DOM的不同位置,这意味着它们的样式应该不受具体上下文的影响。不应通过全局选择器定义样式,这些选择器期望在DOM树中的特定位置找到元素(即,这些选择器在组件被放置在其他位置时将不再适用)。
此外,这些组件通常由不同的开发者编写并交付。如果样式规则没有限制在各自的组件内,命名冲突肯定会发生。
开发人员体验:使用JS和TS的好处JavaScript 和,尤其是在更大程度上是 TypeScript,提供了远优于 CSS 的开发体验。类型检查、代码补全和更强大的代码检查(linting)支持,让开发者能够编写高度标准化且易于维护的代码,进而构建出更健壮的用户界面(UI)。
基于类型的标准化在可组合性中也扮演着至关重要的角色。例如,主题类型可以作为组织内各团队扩展和修改基础主题或默认主题的协议,同时遵循相同的规则和样式标记。
import { createTheme, defaultTheme, type ThemeOptions } from '@my-org.design/theme';
/**
* 导出一个名为暗黑主题的函数,用于创建一个符合暗黑主题规范的ThemeOptions对象。
*
* @returns {ThemeOptions} 返回一个符合暗黑主题规范的ThemeOptions对象。
*/
export function darkTheme(): ThemeOptions {
return createTheme(defaultTheme(), {
palette: {
type: 'dark',
primary: {
main: '#6580f9',
secondary: '#ab7bb3',
},
background: {
default: '#454546',
paper: '#000000',
},
text: {
primary: '#ffffff',
},
},
});
}
集成现代UI框架
此外,现代用户界面(UI)是使用抽象的JavaScript层来操作DOM树的框架实现的。开发人员很少直接与应用的HTML交互,因此,基于JS的样式解决方案更为自然。
例如,在 JS 世界中的组件状态或属性更容易用来影响组件样式,因为样式也在同样的环境中。
interface ButtonProps {
primary?: boolean;
}
const Button = styled.button<ButtonProps>`
background-color: ${props => props.primary ? '蓝色' : '灰色'}; // 根据 primary 属性设置背景颜色
`;
CSS-in-JS 构建方案
如前所述,CSS-in-JS 库通过允许开发人员直接在 JavaScript 中编写 CSS 来改进了样式。然而,随着应用程序的增大,缺点也愈发明显——主要是由于包大小增加和运行时性能下降的问题,因为客户端需要计算样式。
构建时间 CSS 提取在这时特别重要。它保留了 CSS-in-JS 的优势,同时消除了其性能损耗的缺点。
在构建时的 CSS-in-JS 解决方案不仅解决了性能问题,还解决了 SSR(服务器端渲染)的近期趋势问题。虽然运行时的 CSS-in-JS 可以用于 SSR,但它们并没有针对 SSR 进行优化。 此外,一些 SSR 应用使用的模式和技术并未得到支持。
例如,React 服务器组件(RSC)无法支持如 useContext
或 useState
这样的运行时注入机制来操作 CSS,因为它们是在服务器上渲染的,没有浏览器中的 React 生命周期机制可用。而对于 RSC 来说,仅在服务器上执行,静态且不依赖于应用状态的样式是必需的。
一些流行的 CSS-in-JS 工具和库包括 Vanilla Extract、Linaria、Panda CSS、StyleX,最近,Material UI 还推出了对 Pigment CSS 的实验性支持。
许多工具(但并非全部)基于wyw-in-js,这是一个简称为“wyw-in-js”的工具包,用于零运行时CSS的工具包。
构建时CSS提取是怎么回事?不同的解决方案采用不同的技术和优化方法。然而,任何构建时间CSS提取的核心都包括三个阶段:解析出CSS-in-JS声明,生成静态CSS文件,并更新代码以引用新生成的静态文件中的类。
第一步:使用静态分析来分析源代码该工具扫描你的代码库中的 CSS-in-JS 声明。例如以下,工具会查找这类声明。
import { styled } from 'linaria/react';
// 定义一个按钮组件,并使用样式函数设置其背景色和文字颜色
const Button = styled.button`
// 设置背景颜色为#327fa8
background-color: #327fa8;
// 设置文字颜色为#ffffff
color: #ffffff;
`;
步骤2:创建静态CSS。
解析后的 CSS-in-JS 声明会被转换为标准的 CSS 样式。生成唯一的类名(通常使用哈希算法)以避免命名冲突。
.styles_button-fhg2jf {
background-color: #327fa8;
color: #ffffff;
}
优化措施通常包含在构建时的 CSS-in-JS 解决方案中,比如合并重复的 CSS 规则、移除死代码等。
步骤 3:在代码中引用生成的静态类名 const Button = (props) => <button className="styles_button-fhg2jf" {...props} />; // 这是一个按钮组件的定义,使用了 JSX 语法和 React 的 props 传递属性。
由于此过程发生在构建阶段,可以通过配置打包器并使用合适的插件来实现。例如,以下是Vanilla Extract的基本 Webpack 配置如下:
/**
* @filename: webpack配置文件.js
*/
const {
VanillaExtractPlugin
} = require('@vanilla-extract/webpack-plugin');
module.exports = {
plugins: [new VanillaExtractPlugin()]
};
/**
* @filename: webpack配置文件.js
*/
const {
VanillaExtractWebpackPlugin
} = require('@vanilla-extract/webpack-plugin');
module.exports = {
plugins: [new VanillaExtractWebpackPlugin()]
};
如何避免在使用 CSS-in-JS 组件时遇到兼容性问题
在现代 web 开发中,组件通常作为 Bit 组件跨项目共享,以提高代码库的可维护性,加快开发速度,并促进团队协作。
例如,比如这些如下所示的在 Bit 平台上共享和维护的自定义 Shadcn/UI 组件:
https://bit.cloud/bitdesign/shadcn-ui/~components?aggregation=none
自定义的 shadcn/ui 组件库,可在 Bit 平台上共享和协作使用
然而,组件也需要像完整的应用程序一样,一定的上下文才能发挥功能。这种上下文可以被称为“组件运行时环境”。我们应该尽量使这种上下文尽可能灵活,以便我们的共享组件可以在更多不同的应用程序中重复使用。
一个使用(内存中)状态的 UI 组件在所有上下文中可能无法正常工作。我已经举过的一个例子是 React 服务端组件。为此,要么避免在所有组件中使用状态进行样式,要么确保在不支持状态的上下文中可以使用备用方案。
更多详情请参阅此博客贴。
了解组件的运行时环境。学习如何利用组件开发环境来定义组件的运行时设置 要了解更多 Bit. 可组合软件平台。专为创新团队打造的完整开发平台,打造可组合的软件产品。bit.cloud 构建社区驱动的UI库:促进协作与采用的指南:增强您的UI库bitsrc.io 2025年的九大React组件库启动您的下一个React项目,使用既适合开发者又方便用户的UI库:美观的用户界面……blog.bitsrc.io 发布 NPM 包的现代方法:发布多个 NPM 包及其他注册表包的最简单方法共同学习,写下你的评论
评论加载中...
作者其他优质文章