为什么我从 Styled Components 转向 (S)CSS 模块
什么是 styled-components?
什么是 CSS 模块?
有问题的应用程序
为什么?🧐
它有用吗?
花了多少钱?
卢卡斯·本杰明(Lucas Benjamin)的作品
这篇博文将探讨我从 Styled Components 迁移到 SCSS Modules 的原因。这将是一篇比较原始且非技术性的文章(也就是说,你可能不会从中学到任何新东西)。
什么是 styled-components?
Styled Components 是一种为 React 组件编写 CSS 的全新方法。你可以简单地使用样式创建组件。
export const Main = () => {
return <HelloWorld>Hello World</HelloWorld>;
};
const HelloWorld = styled.h1`
font-weight: 700;
line-height: 1.618;
`;
这是一种非常非常便捷的 CSS 编写方式。所有 CSS 都与主逻辑位于同一个文件中。这堪称终极的“组件共置”。另外,如果你对小组件很痴迷,这会强制你编写小组件,因为由于单个文件中包含了 HTML + CSS + TS 这三种技术(没错,我就是那些深谙 TypeScript 的人😋),组件会迅速膨胀。所以你会觉得有义务将组件拆分成更小的部分,这最终是件好事。模块化至关重要。
就像 Svelte 和 Vue 的 SFC 一样。他们正确地解决了这个问题,而这让我对 React 感到不满。
不管怎样,抛开吐槽不说,这种编写样式的方式真的很棒,我再怎么强调也不为过。需要基于 props 的动态样式吗?不用担心,只需将 props 传递给你的样式组件,然后在其中使用它即可。
export const Main = () => {
return <HelloWorld weight={600}>Hello World</HelloWorld>;
};
const HelloWorld = styled.h1<{ weight: number }>`
font-weight: ${({ weight }) => weight};
line-height: 1.618;
`;
如果你不熟悉或者讨厌 TypeScript 代码,请见谅。这没办法。它现在已经成为我的一部分了😇。
是的,它会自动进行作用域划分和添加供应商前缀。供应商前缀是在运行时生成的,也就是说,它会判断浏览器是否需要供应商前缀,然后生成带有供应商前缀的样式。这就像一个在浏览器中运行的 PostCSS 和 Autoprefixer 的运行时版本。
它让事情变得非常非常容易。但如果你不完全理解 React 及其渲染过程的工作原理,就会开始出错。
什么是 CSS 模块?
CSS 模块是一种稍微不那么激进的 CSS 编写方式。它本质上是独立的 CSS 文件,但只是模块化的。语法基本保持不变,但作用域仅限于使用它的组件(通过修改类名)。其一般模式如下:
|-HelloWorld
|-HelloWorld.tsx
|-HelloWorld.module.css
注意,我
.css
在末尾使用了。也可以是.scss
,或者.less
,或者.styl
,随便你怎么称呼。我个人使用 SCSS 模块。
请注意,我们的 CSS 模块本身的名称就表明它是一个模块。*.module.*
它是一个约定优于配置方法,在未来的捆绑器中非常流行,例如ESBuild、Vite、Snowpack等。
要使用它们,您需要在 JS 中导入 css 文件,并像这样引用它。
import css from './HelloWorld.module.css';
export const Main = () => {
return <h1 className={css.helloWorld}>Hello World</h1>;
};
同时我们的 CSS 文件:
/* HelloWorld.module.css */
.helloWorld {
font-weight: 700;
line-height: 1.618;
}
生成的 CSS 类似于以下内容:
/* HelloWorld.module.css */
.__B56BLAH_helloWorld_4269BRUHBRUH {
font-weight: 700;
line-height: 1.618;
}
className 被破坏了,并且值在css.helloWorld
我们的组件中被替换了。
好吧,我发挥了一些艺术自由,加了一些 Elon Musk 风格的怪东西。实际的乱码输出会小得多,也更合理 😁。
CSS 模块在这方面非常方便。此外,你还可以添加一些工具,例如autoprefixer
添加供应商前缀,或者将代码编译回旧版 CSS 以实现浏览器兼容性。
有问题的应用程序
介绍结束了,让我们来看看我从 Styled Components 迁移到 CSS Modules 的应用。接下来,让我向你介绍我的宝贝macos.now.sh,它是 macOS Big Sur 的克隆版,用 Preact、TypeScript 编写,并使用 Vite 作为打包器。快来体验一下吧,相信你会喜欢的(提示:只需将鼠标悬停在底部的应用 Dock 上即可)。
无论如何,整个应用程序都是用 Styled Components 编写的,直到我将其从 30 多个组件中剔除,转而使用 CSS Modules。
为什么?🧐
简单的答案👇
开玩笑啦😅。这里有完整的技术解释👇
CSS 未最小化
看看这张图片👇
这是应用的主要生产环境包。如你所见,有些地方进行了压缩,有些地方没有。你可以看到未压缩的部分才是真正的压缩CSS
部分。这些是我以模板字面量(或者字符串字面量,我混用了😅)编写的样式。由于这些不是打包器内置 CSS 压缩工具的 CSS,所以它们保持原样,这有点让人失望。我是一个顽固的性能狂人,Web 性能第一法则是:打包并压缩你的资源。让它们尽可能小,然后再更小 ¯\_(ツ)_/¯。
说真的,你可以在这里查看这个文件:https://macos-web-fwyxhwxry-puruvj.vercel.app/assets/index.da0c587c.js
为什么不使用 babel 插件?🤨
如果您不知道,Styled Components 有一个 Babel 插件就是用于此目的,它可以缩小模板文字内的 CSS,而且相当不错。
但它对我不起作用。
不,确切地说,它对我来说根本不起作用。我设置了 Babel 插件,做了正确的配置,安装了插件,但它就是不工作。Vite 的插件运行出了问题。插件可以正常工作,因为构建时间比以前增加了很多,但输出仍然没有被压缩。create-react-app
为了检查这个问题,我创建了一个复本,同样的插件运行得很好。
但无论如何,即使这个问题解决了,房间里还有一头更大的大象
JS 注入 CSS
所有这些 CSS 仍然存在于 JavaScript 中,并且仅在浏览器评估 JS 时才应用,我确信您知道这一点,JavaScript 很重!!!解析它需要相当多的 CPU 资源,而且主线程负载很重。我们的 HTML 用 JS 渲染已经超出了极限,但 CSS 也用 JS 渲染呢?这对浏览器来说负担太重了。
浏览器在解析 JS 以及渲染 HTML 和 CSS 方面已经变得非常高效,而且所有这些都是并行的。但是,如果 JavaScript 负责所有工作,那么浏览器的效率仍然不够高(这是有原因的)。
如果你追求极致性能,CSS 最好放在单独的文件中,或者内联到 style 标签中。没有比这更好的了。
性能变得重要
当我大约六个月前(2020 年 11 月)开始这个项目时,我给自己定了个小目标:不要担心性能。当然,当时的性能仅仅意味着更小的包大小,而不是运行时性能,因为我之前真的从未遇到过任何运行时性能问题。但这个项目的不同之处在于它包含了很多东西。有大量的requestAnimationFrame
代码、大量的组件、大量的全局状态,以及其他一些不相关的内容。而且所有这些都会同时显示在屏幕上。你无法真正延迟加载很多东西,因为几乎所有东西都是即时加载的。
所有这些都拖累了应用的运行时性能。Dock 动画很卡顿,菜单打开需要一段时间,主题切换也明显卡顿。所以我最终不得不考虑运行时性能。而最显而易见的选择就是抛弃那些花哨的东西,回归本真。
它有用吗?
绝对有效!性能提升太快了,无论是运行时间还是包大小。
这是压缩后的 CSS 文件输出。它通过 Autoprefixer 处理供应商样式,Vite 会自动将其合并到一个高度压缩的 CSS 文件中。您可以自己看看 👇
这是index.js
👇
完全精简,JS 位于 js 文件中,CSS 位于其自己的文件中,并且所有代码均由浏览器并行处理,样式不会像样式组件那样每次 props 更改时都生成。只会应用类,并且相应的样式已存在于 CSS 文件中。如同以往一样,简单快捷。
减少捆绑包大小
整个操作从我的 bundles 中减少了 60KB,这可是个大问题。我删除了styled-components
、react-is
(样式组件出于某种原因需要它)styled-reset
和color2k
(用于颜色操作)。
如果你已经编码一段时间了,你就会知道删除旧东西是多么令人满足。😌
花了多少钱?
是的。我丢了一些东西:一个很棒的 API 设计。
在 Styled Components 中编写样式非常愉快。它的 API 设计非常棒,就代码编写而言,我更喜欢它而不是 CSS 模块。
如果您未使用某个样式,则表示您未使用某个组件,因此 VSCode 会将该组件标记为未使用,以便您轻松将其移除。告别无效样式!
另外,比较一下 Styled Components 中的以下组件:
interface ActionCenterSurfaceProps {
grid: [[number, number], [number, number]];
children: ComponentChildren;
}
export const ActionCenterSurface = ({ grid, children }: ActionCenterSurfaceProps) => {
const [[columnStart, columnSpan], [rowStart, rowSpan]] = grid;
const [theme] = useTheme();
return (
<Container
columnSpan={columnSpan}
columnStart={columnStart}
rowSpan={rowSpan}
rowStart={rowStart}
theme={theme}
>
{children}
</Container>
);
};
type ContainerProps = {
columnStart: number;
columnSpan: number;
rowStart: number;
rowSpan: number;
theme: TTheme;
};
const Container = styled.section<ContainerProps>`
display: grid;
grid-auto-rows: 1fr;
gap: 0.25rem;
position: relative;
padding: 0.5rem;
border-radius: 0.75rem;
background-color: hsla(${theme.colors.light.hsl}, 0.5);
${({ columnStart, columnSpan, rowSpan, rowStart, theme: localTheme }) => css`
grid-column: ${columnStart} / span ${columnSpan};
grid-row: ${rowStart} / span ${rowSpan};
box-shadow: hsla(0, 0%, 0%, 0.3) 0px 1px 4px -1px, 0 0 0 ${localTheme === 'dark' ? 0.4 : 0}px hsla(
${theme.colors.dark.hsl},
0.3
);
`};
`;
这是我之前在 Styled Components 中的一个组件。如你所见,它接受数字类型的值。如果是布尔值的话,创建类并应用样式就很容易了。但在这里,值可以是任意值。
现在看看新的 CSS 模块版本:
成分:
interface ActionCenterSurfaceProps {
grid: [[columnStart: number, columnSpan: number], [rowStart: number, rowSpan: number]];
children: ComponentChildren;
}
export const ActionCenterSurface = ({ grid, children }: ActionCenterSurfaceProps) => {
const [[columnStart, columnSpan], [rowStart, rowSpan]] = grid;
const [theme] = useTheme();
return (
<section
className={css.container}
style={
{
'--column-start': columnStart,
'--column-span': columnSpan,
'--row-start': rowStart,
'--row-span': rowSpan,
'--border-size': `${theme === 'dark' ? 0.4 : 0}px`,
} as React.CSSProperties
}
>
{children}
</section>
);
};
该组件的 CSS 如下:
.container {
display: grid;
grid-auto-rows: 1fr;
gap: 0.25rem;
position: relative;
padding: 0.5rem;
border-radius: 0.75rem;
box-shadow: hsla(0, 0%, 0%, 0.3) 0px 1px 4px -1px, 0 0 0 var(--border-size) hsla(
var(--app-color-dark-hsl),
0.3
);
background-color: hsla(var(--app-color-light-hsl), 0.5);
grid-column: var(--column-start) / span var(--column-span);
grid-row: var(--row-start) / span var(--row-span);
}
如您所见,prop 值通过 CSS 变量传递给 CSS。这种方法也很好,但我认为 Styled Components 方法更简洁。
将来,我可能会尝试像Linaria这样的库,它在编码过程中具有与完全相同的 API styled-components
,但在构建时运行时会被完全删除,并且 CSS 会被提取到单独的 CSS 文件中,这真是太棒了!!!🤓
好啦,今天就到这里啦。
签名!👋
文章来源:https://dev.to/puruvj/why-i-moved-from-styled-components-to-s-css-modules-2ikc