一、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. 运行时样式生成流程
- 解析模板字符串:将 CSS 代码转换为 AST(抽象语法树)
- 处理动态值:替换
${props => ...}为具体值 - 生成哈希类名:基于组件位置和样式内容生成唯一标识
- 注入样式表:将处理后的 CSS 插入页面头部
五、三种问题解决方案对比表
| 问题类型 | 传统方案痛点 | CSS-in-JS 解决方案 | 关键技术点 |
|---|---|---|---|
| 全局污染 | 类名冲突导致样式覆盖 | 自动生成唯一类名 | 哈希算法 + 样式隔离 |
| 动态样式 | 需手动管理多套类名 | 样式与 JavaScript 变量直接绑定 | 模板字符串插值 + 动态渲染 |
| 组件化 | 样式与组件文件分离 | 样式与组件共存于同一文件 | 组件级样式作用域 + 按需加载 |