当前位置: 首页 > news >正文

CSS in JS 的演进:Styled Components, Emotion 等的对比与选择

在 React 或其他组件化框架的生态系统中,CSS 的管理方式一直是开发者关注的焦点。传统的 CSS 文件管理方式在复杂的大型应用中,往往会遇到全局作用域的冲突、命名困难、样式复用不灵活等问题。为了解决这些痛点,CSS-in-JS 应运而生,它允许开发者直接在 JavaScript 代码中编写 CSS,并将其与组件逻辑紧密结合,带来了前所未有的样式管理体验。

本文将深入探讨 CSS-in-JS 的概念、发展历程,重点对比分析 Styled Components 和 Emotion 等主流库,并提供选择库的考量因素。

第一章:CSS in JS 的概念、优势与挑战

1.1 什么是 CSS in JS?

CSS-in-JS 是一种将 CSS 样式编写在 JavaScript 文件中的模式。通常,它通过 JavaScript 函数或组件来生成 CSS 样式,并将这些样式动态地应用到 DOM 元素上。

1.2 CSS in JS 的优势

样式与组件的解耦与封装: CSS 样式被封装在组件内部,避免了全局 CSS 带来的命名冲突和样式污染。

强大的动态样式能力: 可以轻松地基于组件的 state、props 或其他 JS 变量来动态生成样式,实现真正的“动态样式”。

更强的逻辑组合: CSS 逻辑可以与其他 JavaScript 逻辑(如事件处理、条件渲染)结合,使得样式更加智能。

提高开发效率: 无需在 .js 和 .css 文件之间频繁切换,可以将相关逻辑集中管理。

自动前缀处理: 大多数 CSS-in-JS 库内置了对浏览器前缀的自动处理。

主题化 (Theming): 方便地实现全局主题切换和局部样式覆盖。

移除未使用的 CSS: 通过构建工具或库本身的特性,可以在一定程度上避免生成死 CSS 代码。

1.3 CSS in JS 的挑战

性能开销: 一些 CSS-in-JS 方案需要在运行时动态生成 CSS,这可能带来额外的 JavaScript 解析和 DOM 操作开销,尤其是在服务器端渲染 (SSR) 时。

学习曲线: 需要理解其特定的 API 和工作原理。

浏览器兼容性(早期): 早期的一些实现可能存在一些兼容性问题,但随着发展,主流库的兼容性已得到极大改善。

字符串模板的局限性: 虽然很多库支持 JavaScript obj/函数,但有时仍需处理 CSS 字符串模板。

打包体积: 引入库本身会增加项目的打包体积。

第二章:CSS in JS 的演进与主流库

CSS-in-JS 的概念最早可以追溯到一些早期尝试,但真正流行起来并受到广泛关注,离不开 React 社区的推动。

2.1 早期探索与其他概念

内联样式(Inline Styles): React 最基础的样式写法,直接在 JSX 中使用 JS 对象。

<JSX>

<MyComponent style={{ color: 'blue', fontSize: 16 }} />

优点: 样式与组件强绑定,无命名冲突。

缺点: 不支持伪类、伪元素、媒体查询,无法实现复杂样式,缺乏复用性。

CSS Modules: 虽然不是严格意义上的 CSS-in-JS,但 CSS Modules 是解决 CSS 命名冲突的有效方案。它通过将 CSS 类名转换为具有唯一性的哈希值,实现 CSS 的局部作用域。

<JSX>

// MyComponent.module.css

.title {

color: red;

}

// MyComponent.js

import styles from './MyComponent.module.css';

function MyComponent() {

return <div className={styles.title}>Hello</div>;

}

优点: Scoped CSS,避免冲突,支持原生 CSS 语法。

缺点: 无法直接通过 JS 动态控制,动态修改通过 JS 变量和 className 绑定,不够灵活。

2.2 主流 CSS in JS 库

Styled Components 和 Emotion 是目前 React 生态中最流行的两个 CSS-in-JS 库,它们都提供了非常强大的功能。

2.2.1 Styled Components

Styled Components 的核心思想是创建具有特定样式的 React 组件。它使用 ES6 的模板字符串(Tagged Templates)来定义 CSS 规则。

核心 API:

styled 对象: 用于创建带有特定样式的组件。

<JAVASCRIPT>

import styled from 'styled-components';

// 创建一个带有特定样式的 div 组件

const Title = styled.div`

color: red;

font-size: 24px;

margin-bottom: 10px;

`;

attrs(): 用于为组件添加额外的属性(props)。

Props 传递: 模板字符串中可以直接使用 props 来生成动态样式。

<JAVASCRIPT>

const DynamicDiv = styled.div`

color: ${props => props.primary ? 'blue' : 'black'};

border: 1px solid ${props => props.theme.borderColor}; /* 演示主题化 */

`;

样式继承与扩展: 通过 styled(Component) 可以继承现有组件的样式。

<JAVASCRIPT>

const AnotherTitle = styled(Title)`

font-weight: bold;

`;

createGlobalStyle: 用于定义全局样式,如 body 样式、字体定义等。

ThemeProvider: 用于提供主题数据,方便在多个组件中共享。

优点:

API 优雅直观: 模板字符串的语法与普通 CSS 非常相似,易于理解和上手。

组件化思想: 样式与组件的结合非常紧密,符合 React 的组件化理念。

强大的社区支持和生态: 拥有活跃的社区和丰富的生态工具。

自动生成类名: 自动生成哈希类名,避免冲突。

缺点:

Runtime 占有率: 在客户端渲染模式下,需要 JavaScript 来生成和注入样式,这会增加运行时开销。

SSR 复杂性: SSR 时需要额外配置,以确保样式正确渲染。

Bundle Size: 库本身有一定大小。

2.2.2 Emotion

Emotion 是另一个非常流行且功能强大的 CSS-in-JS 库。它提供了多种 API,可以满足不同场景下的需求。

核心 API:

css prop (jsx-style-props): 最流行的使用方式,直接通过 css prop 接收 CSS 字符串创建的样式对象。

<JSX>

import { css } from '@emotion/react';

function MyComponent() {

const dynamicStyle = {

color: 'green',

fontSize: '20px',

};

return (

<div

css={css`

background-color: yellow;

padding: 10px;

${dynamicStyle}; /* 插入 JS 对象 */

color: ${props => props.theme.textColor}; /* 演示主题化 */

`}

>

Hello from Emotion

</div>

);

}

styled API: 也提供了类似于 styled-components 的 API。

<JAVASCRIPT>

import styled from '@emotion/styled';

const Button = styled.button`

padding: 10px 20px;

background-color: ${props => props.primary ? 'dodgerblue' : 'gray'};

`;

css 函数 API: 用于创建可复用的 CSS 样式对象。

<JAVASCRIPT>

import { css } from '@emotion/react';

const baseStyles = css`

border: 1px solid red;

`;

const highlightedStyles = css`

background-color: lightblue;

`;

function AnotherComponent() {

return <div css={[baseStyles, highlightedStyles]}>Styled</div>;

}

GlobalStyles: 类似 createGlobalStyle,用于全局样式。

ThemeProvider: 提供主题。

优点:

灵活性高: 提供了多种 API (css prop, styled API),可以适应不同的开发习惯。

更高的性能: css prop 的实现通常比 styled-components 在运行时拥有更少的开销,尤其是在 SSR 场景下。

支持 Preload / Critical CSS: 能够更方便地提取关键 CSS。

更友好的 TypeScript 支持: 对 TS 的支持通常被认为更优秀。

缺点:

API 模式较多: 可能需要花一些时间来理解其不同的 API。

Styled API 的局限性: 虽然提供了 styled API,但在一些高级特性上可能不如 styled-components 成熟。

2.4 其他 CSS in JS 方案

Jss: 一个更底层的 CSS-in-JS 库,功能强大,可以构建自己的 UI 库。Styled Components 和 Emotion 内部都使用了 Jss。

Linaria: 一个“零运行时”的 CSS-in-JS 库,它会在构建时将 CSS 提取到单独的文件中,并将类名注入到 JS 中,几乎没有运行时开销。

Stitches: 一个现代化的 CSS-in-JS 库,由原 Styled Components 作者之一创建,强调性能、可访问性和开发者体验,支持 Type-safe,拥有强大的主题和规则 API。

第三章:Styled Components vs Emotion 对比

特性

Styled Components

Emotion

核心理念

创建带样式的 React 组件 (`styled.div``...)

灵活的 CSS-in-JS 方案,支持 css prop (<div css={css``}/>) 和 styled API。

API 简洁性

非常直观,与原生 CSS 相似,易于上手。

css prop 相对直接,styled API 也很直观,提供了多种选择。

动态样式

通过 props 在模板字符串中渲染 JS 表达式。

通过 props 在 css prop 的 JS 字符串或对象中渲染 JS 表达式。

样式继承

styled(Component) 语法流畅。

styled API 支持继承,css prop 可以通过组合 CSS 对象实现类似功能。

性能 (Runtime)

在客户端渲染时有一定运行时开销。SSR 时需要 ServerStyleSheet。

css prop 通常有更低的运行时开销,SSR 支持也很好。

性能 (Bundle Size)

库自身有一定体积。

库自身有一定体积,但可以通过 @emotion/react 和 @emotion/babel-plugin 优化。

SSR 支持

需要 ServerStyleSheet 来收集样式。

内置了更好的 SSR 支持,通过 renderToString,renderStatic 等 API。

打包优化

可以通过 Babel 插件(如 babel-plugin-styled-components)在构建时提取样式,减少运行时开销。

可以结合 @emotion/babel-plugin 在构建时生成更优化的 CSS,甚至零运行时。

主题化

ThemeProvider API 成熟稳定。

ThemeProvider API 也非常强大。

TypeScript

支持,但有时需要额外的类型定义。

对 TS 支持被认为更优秀,常与 @emotion/react 搭配使用。

社区活跃度

非常活跃,生态成熟。

非常活跃,许多知名项目使用。

选择场景

喜欢直观的 CSS 语法,习惯组件化开发,团队熟悉。

需要极致性能,注重 SSR,喜欢 css prop 的写法,需要更好的 TS 集成。

2.4 Emotion 的打包优化: @emotion/babel-plugin

Emotion 提供了一个 Babel 插件:@emotion/babel-plugin。这个插件可以在构建时将 CSS-in-JS 的代码转换成更优化的形式,甚至实现“零运行时”的效果。

安装:

<BASH>

npm install --save-dev @emotion/babel-plugin

配置 .babelrc 或 babel.config.js:

<JSON>

{

"presets": [

"react-app" // or your preferred presets

],

"plugins": [

["@emotion", { "sourceMap": true, "autoLabel": "always" }] // autoLabel helpful for debugging

]

}

sourceMap: 是否生成 Source Map。

autoLabel: 为生成的 CSS 类名添加标签(例如 css-a1b2c3d4),方便调试。

labelFormat: 自定义标签格式。

当使用这个插件后,Emotion 的 css prop 实际上可以被转换成预编译的 CSS 类,大大减少了运行时开销。

第四章:如何选择合适的 CSS in JS 库

选择哪个 CSS-in-JS 库,取决于你的项目需求、团队偏好和性能要求。

4.1 考量因素

团队熟悉度和偏好:

如果团队熟悉 Styled Components 的模板字符串语法,并且觉得它直观易懂,那么继续使用 Styled Components 是一个不错的选择。

如果团队更喜欢 JavaScript 对象的写法,或者想尝试 css prop 的方式,Emotion 可能是更好的选择。

性能要求:

对于对运行时性能要求极高的应用,尤其是 SSR 场景,Emotion(配合 Babel 插件)或 Linaria、Stitches 可能更有优势。

Styled Components 的运行时开销虽然存在,但对于大多数应用来说,通常不是瓶颈,尤其是在使用了 Babel 插件优化后。

项目规模和复杂性:

在小型项目或原型开发中,两者的差异可能不明显。

在大型、复杂的项目中,其模式、工具支持、社区生态的成熟度会显得更为重要。

TypeScript 集成:

如果你使用 TypeScript,Emotion 的 TS 支持通常被认为更佳,配置也更顺畅。

生态系统和工具链:

两者的社区都非常活跃,拥有丰富的第三方主题、组件库和工具。

检查你正在使用的 UI 库或框架是否对某些 CSS-in-JS 库有更好的集成。

零运行时需求:

如果你的项目对运行时零开销有非常高的要求,Linaria 可能是你的首选,因为它在构建时提取 CSS。

4.2 实际迁移和集成

从零开始: 如果新项目,根据项目需求和团队偏好,选择其中一个主流库(Styled Components 或 Emotion)并开始使用。

与现有 CSS 集成:

CSS Modules + CSS-in-JS: 可以逐步将组件的样式迁移到 CSS-in-JS,实现新组件使用 CSS-in-JS,旧组件保持 CSS Modules。

全局 CSS + CSS-in-JS: 可以使用 createGlobalStyle 或 GlobalStyles 来管理全局样式(如重置 CSS、字体样式),而组件内部则使用 CSS-in-JS。

性能监控: 无论选择哪个库,都应该使用性能监控工具(如 React DevTools Profiler, Lighthouse)来分析其对应用性能的影响。

第五章:高级主题与最佳实践

5.1 主题化 (Theming)

主题化是 CSS-in-JS 的一大亮点。通过 ThemeProvider,可以将颜色、字号、间距等主题变量注入到组件的样式中。

Styled Components 主题化示例:

<JSX>

// src/theme.js

export const theme = {

colors: {

primary: 'blue',

secondary: 'gray',

},

fontSizes: {

small: '14px',

medium: '16px',

},

};

// src/App.js

import { ThemeProvider } from 'styled-components';

import { theme } from './theme';

import MyComponent from './MyComponent';

function App() {

return (

<ThemeProvider theme={theme}>

<MyComponent />

</ThemeProvider>

);

}

// src/MyComponent.js

import styled from 'styled-components';

const Box = styled.div`

background-color: ${props => props.theme.colors.primary};

color: white;

font-size: ${props => props.theme.fontSizes.medium};

padding: 10px;

`;

Emotion 主题化示例:

<JSX>

// src/theme.js (与 Styled Components 类似)

export const theme = { /* ... */ };

// src/App.js

import { ThemeProvider } from '@emotion/react'; // Emotion 的 Provider

import { theme } from './theme';

import MyComponent from './MyComponent';

function App() {

return (

<ThemeProvider theme={theme}>

<MyComponent />

</ThemeProvider>

);

}

// src/MyComponent.js

import { css } from '@emotion/react';

const boxStyles = (theme) => css`

background-color: ${theme.colors.primary};

color: white;

font-size: ${theme.fontSizes.medium};

padding: 10px;

`;

function MyComponent() {

return (

<div css={boxStyles}> {/* Emotion 直接访问 theme */}

Hello

</div>

);

}

5.2 样式继承与组合

Styled Components: styled(Component) 语法非常清晰。

Emotion:

styled API 同样支持继承。

css prop 可以通过数组组合多个 CSS 对象,实现样式的复用和组合。

<JSX>

import { css } from '@emotion/react';

const baseButtonStyles = css`

padding: 8px 15px;

border-radius: 4px;

`;

const primaryButton = css`

background-color: blue;

color: white;

`;

const CTAButton = ({ children }) => (

<button css={[baseButtonStyles, primaryButton]}>

{children}

</button>

);

5.3 移除未使用的 CSS

Unused CSS Selectors: 在构建时,如果使用 babel-plugin-styled-components 或 @emotion/babel-plugin,它们可以在一定程度上移除未被渲染的样式。

PurgeCSS: 一个独立的工具,可以配合 Webpack 使用,扫描你的代码,移除未使用的 CSS 规则。

5.4 性能优化技巧总结

使用 Babel 插件: babel-plugin-styled-components 或 @emotion/babel-plugin 可以在构建时提取样式,减少运行时开销。

SSR 配置: 正确配置 SSR 相关的样式收集和渲染。

代码分割: 对组件库或不常用的功能进行代码分割,按需加载。

关注 css prop 的写法: 在 Emotion 中,尽量将动态值直接写在 JS 字符串中,避免过度使用函数。

利用 css prop 的组合能力: 将常用的样式片段定义为可复用的 CSS 对象。

关注库的最新版本: CSS-in-JS 库一直在迭代优化,保持更新可以获得更好的性能和新特性。

结论

CSS-in-JS 已经从早期的新颖概念,发展成为现代前端样式管理的重要范式。Styled Components 和 Emotion 是其中的佼佼者,它们各自拥有独特的优势和 API 设计。

Styled Components 以其直观的模板字符串语法和强大的组件化理念赢得了开发者喜爱。

Emotion 则以其灵活性、性能优势和优秀的 TS 集成提供了更广泛的选择。

选择哪个库,没有绝对的对错,关键在于了解它们的设计哲学,结合项目的具体需求、团队的熟悉程度以及性能指标来做出最适合的决策。理解 CSS-in-JS 的核心原理和最佳实践,将帮助你构建出更具可维护性、可扩展性和高性能的组件化应用。


文章转载自:

http://cc49LUOw.jcxgr.cn
http://hpwv9crF.jcxgr.cn
http://npjINZk6.jcxgr.cn
http://NiLG9C8z.jcxgr.cn
http://ZMA52y8k.jcxgr.cn
http://QfXVXVO8.jcxgr.cn
http://ew8maDSZ.jcxgr.cn
http://oKSt0z6q.jcxgr.cn
http://bwr7MVzV.jcxgr.cn
http://smP624K6.jcxgr.cn
http://Tw8kF9Hl.jcxgr.cn
http://LBRJgkTY.jcxgr.cn
http://In7AL17J.jcxgr.cn
http://PhvdQpC4.jcxgr.cn
http://H2NdswuO.jcxgr.cn
http://d6RInNNj.jcxgr.cn
http://oYabvOLp.jcxgr.cn
http://fOi5YbXA.jcxgr.cn
http://5tP3fukI.jcxgr.cn
http://9B40Tq3g.jcxgr.cn
http://7JjCULAw.jcxgr.cn
http://Wyrp0RRO.jcxgr.cn
http://GEmj6lrx.jcxgr.cn
http://4oBw8oqp.jcxgr.cn
http://lgNiLvGc.jcxgr.cn
http://sQqE87HX.jcxgr.cn
http://tiJqpkkx.jcxgr.cn
http://Tc9YqEmY.jcxgr.cn
http://OHlr1g4S.jcxgr.cn
http://DPYuTsVj.jcxgr.cn
http://www.dtcms.com/a/374883.html

相关文章:

  • mybatis-plus多租户兼容多字段租户标识
  • Flutter跨平台工程实践与原理透视:从渲染引擎到高质产物
  • 华为云盘同步、备份和自动上传功能三者如何区分
  • 设计模式第一章(建造者模式)
  • Vue3入门到实战,最新版vue3+TypeScript前端开发教程,笔记02
  • 【Vue】Vue2 与 Vue3 内置组件对比
  • XSS 跨站脚本攻击剖析与防御 - 第一章:XSS 初探
  • vue 去掉el-dropdown 悬浮时出现的边框
  • 常见的排序算法总结
  • [优化算法]神经网络结构搜索(一)
  • php 使用html 生成pdf word wkhtmltopdf 系列2
  • 大数据毕业设计选题推荐-基于大数据的海洋塑料污染数据分析与可视化系统-Hadoop-Spark-数据可视化-BigData
  • 【计算机网络 | 第11篇】宽带接入技术及其发展历程
  • 探索Java并发编程--从基础到高级实践技巧
  • Made in Green环保健康产品认证怎么做?
  • yum list 和 repoquery的区别
  • 解决HTML/JS开发中的常见问题与实用资源
  • Angular 面试题及详细答案
  • AI与AR融合:重塑石化与能源巡检的未来
  • 增强现实光学系统_FDTD_zemax_speos_学习(1)
  • 开学季干货——知识梳理与经验分享
  • Alex Codes团队并入OpenAI Codex:苹果生态或迎来AI编程新篇章
  • The learning process of Decision Tree Model|决策树模型学习过程
  • 六、与学习相关的技巧(下)
  • 《低功耗音频:重塑听觉体验与物联网边界的蓝牙革命》
  • 20250909的学习笔记
  • 金融量化指标--5Sortino索提诺比率
  • 消息三剑客华山论剑:Kafka vs RabbitMQ vs RocketMQ
  • 均值/方差/标注查介绍
  • 深入解析Guava RateLimiter限流机制