组件样式
React 本身没有样式系统。你写普通的 CSS,JSX 提供两种方式把它应用到元素上:类名(适用于几乎所有场景)和 style 属性(用于样式表无法预知的那些值)。本章会介绍这两种方式,还有介于两者之间的模式:根据属性或状态构建类名,让元素随着数据变化而自动重新设置样式。
JSX 中的类名
class 在 JavaScript 中是保留字,所以 JSX 把这个属性叫做 className。属性值是一个普通字符串,React 会把它设置为 DOM 中元素的 class 属性,这意味着另一边的 CSS 写法完全没有变化。
function Avatar() {
return <img className="avatar avatar--large" src={photo} alt="北京" />
}一个字符串里写两个类名,用空格分开,就像 HTML 一样。JSX 中的驼峰命名规则只应用于标签的属性名;类名本身是你的自由,所以串串命名、BEM 或其他你样式表已经在用的约定都能直接迁移过来。
导入样式表
组件文件在顶部用一条裸导入语句来加载它需要的 CSS:
import './App.css'
export default function App() {
return <h1 className="title">React 事实</h1>
}这个导入语句左边没有变量,组件也不会去读这个文件。它是给构建工具的指令:这个模块依赖那个 CSS,所以要把它包括进来。Vite 会在开发时把规则注入页面,在生产构建中生成一个真正的 .css 文件。配置 React 项目 里介绍了处理图片的同样机制。
导入语句放在哪里是一个组织问题。在 Button.jsx 顶部导入 Button.css 可以把样式放在使用它的组件旁边。规则本身仍然是全局的:.title 选择器会匹配页面任何地方带有那个类的元素,不管是哪个文件导入的。BEM 这样的命名约定存在的目的就是让这个共享命名空间可控,CSS Modules 和 CSS-in-JS 库一旦应用超过了单靠命名规范能管理的程度,就会为你限定规则的作用域。
版本说明
React 19 增加了从组件内部渲染 <link rel="stylesheet"> 和 <style> 的支持,还有一个 precedence 属性让 React 把它们提升到文档 head 中并按可预测的顺序排列。早期版本需要用 react-helmet 这样的库。上面的构建工具导入方式在所有版本中的行为都一样。
基于数据的类名
className 在花括号里面,就像任何其他 JSX 表达式一样,所以字符串可以被计算出来。以一个掷骰子游戏为例,每个骰子可以在掷骰之间被"冻结"。骰子是否被冻结的状态存在状态里面,通过属性传到这里来:
function Die({ value, isHeld }) {
return (
<button className={`die ${isHeld ? 'die--held' : ''}`}>
{value}
</button>
)
}每个骰子都有 die 类。被冻结的骰子还会额外获得 die--held。样式表中存放真实的颜色和边框,组件的唯一职责就是决定哪个类应该被应用。当 isHeld 改变时,React 更新 class 属性,浏览器重新绘制。组件内部没有任何代码知道"冻结"长什么样,所以这个外观可以改变而这个文件完全不需要动。
有一个小问题:当 isHeld 是 false 时,模板字符串会生成 "die " 带有尾部空格。浏览器会忽略它,不过在开发者工具里看起来不太整洁。数组的方式可以更干净地处理这种情况,而且可以扩展到多个条件:
const classes = ['die', isHeld && 'die--held'].filter(Boolean).join(' ')
return <button className={classes}>{value}</button>当条件失败时 isHeld && 'die--held' 计算为 false,filter(Boolean) 会把它删除掉,join(' ') 在剩下的任何东西之间放一个空格。
style 属性
style 属性接收一个 JavaScript 对象。属性名是 CSS 属性名的驼峰版本,两个花括号是常规的表达式槽配合对象字面量:
<div style={{ backgroundColor: 'darkslateblue', paddingTop: 12 }}>
内联样式
</div>background-color 变成 backgroundColor,padding-top 变成 paddingTop。值通常是字符串。传一个裸数字,React 会为接受长度值的属性自动追加 px,所以 paddingTop: 12 会渲染成 padding-top: 12px。CSS 把某些属性当作无单位的,那些属性会保持原始数字:lineHeight、opacity、flexGrow、zIndex 和 fontWeight 都会直接传过去。任何其他单位都要写成字符串,比如 width: '60%' 或 margin: '2rem'。
大多数样式应该放在样式表中,一个类可以包含十几条声明,被十几个元素共享。style 属性在值只在运行时才知道、而 CSS 无法预知的情况下才值得一用:
function ProgressBar({ percent }) {
return (
<div className="progress">
<div className="progress__fill" style={{ width: `${percent}%` }} />
</div>
)
}填充条的颜色、高度、过渡效果和圆角都放在 .progress__fill 中。样式表无法知道的唯一值是宽度,因为它来自数据,所以这一个值走内联。背景图片的 URL 来自 API 也是同样的模式:
<div className="hero" style={{ backgroundImage: `url(${photo.url})` }} />大小、定位和遮罩都在 .hero 中,只有 URL 通过属性传进来。
内联样式也有硬性的限制。一个 style 对象描述一个元素的声明,所以没有办法在其中表达悬停状态、媒体查询或关键帧动画。这些不管其他样式有多动态,都必须放在 CSS 中。
className 附加,因为 class 在 JavaScript 中是保留字。在文件顶部导入样式表,把规则放在里面,让组件通过从属性或状态构建字符串来决定哪个类应用。把 style 属性留给你的 CSS 文件无法预知的值,比如来自数据的宽度。 接下来:属性,组件开始从父组件获取数据的地方。

