为什么我从 Styled Components 转到了 (S)CSS Modules?什么是 Styled Components?什么是 CSS Modules?我讨论的应用是为什么?🧐 它好用吗?价格是多少?

2025-05-27

为什么我从 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;
`;
Enter fullscreen mode Exit fullscreen mode

这是一种非常非常便捷的 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;
`;
Enter fullscreen mode Exit fullscreen mode

如果你不熟悉或者讨厌 TypeScript 代码,请见谅。这没办法。它现在已经成为我的一部分了😇。

是的,它会自动进行作用域划分和添加供应商前缀。供应商前缀是在运行时生成的,也就是说,它会判断浏览器是否需要供应商前缀,然后生成带有供应商前缀的样式。这就像一个在浏览器中运行的 PostCSS 和 Autoprefixer 的运行时版本。

它让事情变得非常非常容易。但如果你不完全理解 React 及其渲染过程的工作原理,就会开始出错。

这里有龙

什么是 CSS 模块?

CSS 模块是一种稍微不那么激进的 CSS 编写方式。它本质上是独立的 CSS 文件,但只是模块化的。语法基本保持不变,但作用域仅限于使用它的组件(通过修改类名)。其一般模式如下:

|-HelloWorld
  |-HelloWorld.tsx
  |-HelloWorld.module.css
Enter fullscreen mode Exit fullscreen mode

注意,我.css在末尾使用了。也可以是.scss,或者.less,或者.styl,随便你怎么称呼。我个人使用 SCSS 模块。

请注意,我们的 CSS 模块本身的名称就表明它是一个模块。*.module.*它是一个约定优于配置方法,在未来的捆绑器中非常流行,例如ESBuildViteSnowpack等。

要使用它们,您需要在 JS 中导入 css 文件,并像这样引用它。

import css from './HelloWorld.module.css';

export const Main = () => {
  return <h1 className={css.helloWorld}>Hello World</h1>;
};
Enter fullscreen mode Exit fullscreen mode

同时我们的 CSS 文件:

/* HelloWorld.module.css */

.helloWorld {
  font-weight: 700;
  line-height: 1.618;
}
Enter fullscreen mode Exit fullscreen mode

生成的 CSS 类似于以下内容:

/* HelloWorld.module.css */

.__B56BLAH_helloWorld_4269BRUHBRUH {
  font-weight: 700;
  line-height: 1.618;
}
Enter fullscreen mode Exit fullscreen mode

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 压缩工具的 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 文件中。您可以自己看看 👇

优化的 CSS 模块文件

这是index.js👇

优化 JavaScript

完全精简,JS 位于 js 文件中,CSS 位于其自己的文件中,并且所有代码均由浏览器并行处理,样式不会像样式组件那样每次 props 更改时都生成。只会应用类,并且相应的样式已存在于 CSS 文件中。如同以往一样,简单快捷。

完美

减少捆绑包大小

整个操作从我的 bundles 中减少了 60KB,这可是个大问题。我删除了styled-componentsreact-is(样式组件出于某种原因需要它)styled-resetcolor2k(用于颜色操作)。

如果你已经编码一段时间了,你就会知道删除旧东西是多么令人满足。😌

花了多少钱?

卡魔拉:花了多少钱?;灭霸:一切

是的。我丢了一些东西:一个很棒的 AP​​I 设计。

在 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
        );
  `};
`;
Enter fullscreen mode Exit fullscreen mode

这是我之前在 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>
  );
};
Enter fullscreen mode Exit fullscreen mode

该组件的 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);
}
Enter fullscreen mode Exit fullscreen mode

如您所见,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
PREV
终极 JavaScript 项目库:500 多个面向开发人员的创意 🚀
NEXT
您如何撰写提交信息?