我理解的 CSS-in-JS

280 阅读2分钟

一、CSS-in-JS

  • 代表:styled-components、Emotion。
  • 特点:将 CSS 写入 JavaScript,支持动态样式和组件化。

二、逐行拆解 styled-components 代码

第一行代码:import styled from 'styled-components';
  • styled-components 是什么?
    想象它是一个「魔法缝纫机」:

    • 输入:你告诉它要做一件衣服(CSS 样式)
    • 输出:它把衣服和模特(React 组件)缝合成一体
  • styled 是什么?
    这是「缝纫机的操作面板」,上面有各种纽扣对应 HTML 标签:

    styled.button  // 对应 <button>
    styled.div     // 对应 <div>
    styled.input   // 对应 <input>
    

    这些按钮让你选择要给哪个 HTML 标签“穿衣服”。

第二行代码:const StyledButton = styled.button...
  • styled.button 是什么操作?
    相当于告诉缝纫机:“我要给 <button> 标签设计衣服”。
    此时会生成一个空白的按钮模型,等待你添加样式。

  • 为什么后面直接拼接 `...`
    这是 JavaScript 的标签模板语法(可理解为“用反引号包裹的 CSS 代码块”)。
    类似于:

    // 伪代码解释
    styled.button( `background: blue;` )
    

    实际是调用 styled.button() 方法,并将模板字符串作为参数传递。

Q1:模板字符串里能写什么?

基础写法:纯 CSS 属性

const StyledButton = styled.button`
  background: blue;      // 背景色
  color: white;         // 文字颜色
  padding: 10px 20px;   // 内边距
`;

效果等同于

<button style="background: blue; color: white; padding: 10px 20px;">
  Click
</button>

Q2:高级能力 1:动态样式(根据 props 变化)
const StyledButton = styled.button`
  background: ${props => props.primary ? 'blue' : 'gray'};
  /* 如果传入 primary 属性,背景变蓝 */
`;

使用示例

<StyledButton primary>Blue Button</StyledButton>
<StyledButton>Gray Button</StyledButton>

Q3:高级能力 2:嵌套语法(类似 Sass)
const StyledButton = styled.button`
  &:hover {  
    opacity: 0.8;  // 鼠标悬停时透明度变化
  }
  
  span {
    font-size: 14px;  // 按钮内的 <span> 标签
  }
`;

Q4:高级能力 3:全局主题(通过 ThemeProvider)
import { ThemeProvider } from 'styled-components';

const theme = { colors: { primary: 'blue' } };

const StyledButton = styled.button`
  background: ${props => props.theme.colors.primary};
`;

// 在根组件包裹
<ThemeProvider theme={theme}>
  <StyledButton>使用主题色</StyledButton>
</ThemeProvider>

4. 拼接内容的限制

支持内容示例限制说明
标准 CSS 属性color: red;
JavaScript 表达式${props => props.color}必须返回字符串或数值
伪类/伪元素&:hover { ... }需用 & 符号引用父元素
媒体查询@media (max-width: 768px) { ... }与传统 CSS 写法一致

三、出现原因分析

1、解决 全局污染

传统 CSS 的问题示例
/* styles.css */
.button {
  background: gray; /* 全局生效 */
}

/* Header.css */
.button {
  background: blue; /* 与全局样式冲突 */
}
// Header.jsx
import './Header.css';
import './styles.css';

function Header() {
  // 最终按钮样式会被全局样式覆盖
  return <button className="button">Submit</button>;
}
CSS-in-JS 解决方案(styled-components)
// Header.jsx
import styled from 'styled-components';

// 生成唯一类名(如 .jsx-123)
const StyledButton = styled.button`
  background: blue; /* 仅作用于当前组件 */
`;

function Header() {
  return <StyledButton>Submit</StyledButton>;
}

解决原理

  • 自动生成唯一类名(如 jsx-123
  • 样式通过 <style> 标签注入,与其他组件隔离

2、解决【动态样式困难】

传统实现方式
// Button.jsx
import './Button.css';

function Button({ primary }) {
  // 需要手动拼接类名
  const className = `button ${primary ? 'button-primary' : ''}`;
  return <button className={className}>Click</button>;
}
/* Button.css */
.button { padding: 8px; }
.button-primary { background: blue; } /* 需要预先定义 */
CSS-in-JS 解决方案
// Button.jsx
import styled from 'styled-components';

const Button = styled.button`
  padding: 8px;
  background: ${props => props.primary ? 'blue' : 'gray'};
`;

// 直接通过 prop 控制样式
<Button primary>Submit</Button>

解决原理

  • 样式与组件状态/属性直接绑定
  • 无需预先定义多个类名

3、解决【组件化需求】

传统 CSS 文件结构
src/
  components/
    Button/
      index.jsx  # 组件逻辑
      styles.css # 分散的样式文件
    Header/
      index.jsx
      styles.css
CSS-in-JS 实现组件化
// Button.jsx
import styled from 'styled-components';

const StyledButton = styled.button`
  padding: ${props => props.size === 'large' ? '16px' : '8px'};
  /* 样式与组件共存 */
`;

export default function Button(props) {
  return <StyledButton {...props} />;
}

解决原理

  • 样式与组件代码共存于同一文件
  • 组件被删除时,关联样式自动移除

四、技术配置与实现原理

1. 基础配置

npm install styled-components
// 入口文件(如 App.jsx)
import { StyleSheetManager } from 'styled-components';

function App() {
  return (
    <StyleSheetManager shouldForwardProp={(prop) => !['primary'].includes(prop)}>
      {/* 子组件 */}
    </StyleSheetManager>
  );
}

2. 核心原理示意图

  +---------------------+
  |   styled.button     |
  +---------------------+
           ↓
 生成唯一选择器(.jsx-123)
           ↓
 注入 <style> 标签到 <head>
           ↓
 渲染 <button class="jsx-123">

3. 运行时样式生成流程

  1. 解析模板字符串:将 CSS 代码转换为 AST(抽象语法树)
  2. 处理动态值:替换 ${props => ...} 为具体值
  3. 生成哈希类名:基于组件位置和样式内容生成唯一标识
  4. 注入样式表:将处理后的 CSS 插入页面头部

五、三种问题解决方案对比表

问题类型传统方案痛点CSS-in-JS 解决方案关键技术点
全局污染类名冲突导致样式覆盖自动生成唯一类名哈希算法 + 样式隔离
动态样式需手动管理多套类名样式与 JavaScript 变量直接绑定模板字符串插值 + 动态渲染
组件化样式与组件文件分离样式与组件共存于同一文件组件级样式作用域 + 按需加载