使用组件 state 来控制 CSS-in-JS 样式,理论上是完全可行的。不过,你需要避开一个常见陷阱:绝对不要在 styled 函数内部调用 React Hook。因为 styled 函数在模块初始化阶段执行,此时组件尚未挂载。如果你尝试在其中使用 useState,会立刻触发 Invalid hook call 错误。牢记这个原则,后续操作才能顺利进行。

为何无法在 styled 模板字符串中读取 useState 的值
背后的原理并不复杂:styled.button 本质上是一个工厂函数,仅在模块初始化时执行一次,返回的是 React 组件类型,而非每次渲染都会调用的函数。因此,在模板字符串中常见的 ${props => props.color} 写法中,props 来源于外部传入,与 useState 的返回值无关。
- 典型错误示例:
const Button = styled.button`${() => { const [c] = useState('red'); return `color: ${c}`; }}`——这段代码会立即抛出错误。 - 唯一正确的方式是:在组件内部预先计算好 state,然后将其作为 prop 传入 styled 组件。
- 无论涉及伪类、媒体查询还是主题变量,都必须遵循这一模式,没有任何捷径。
复杂样式该如何拆解才不会引发性能卡顿
所谓“复杂”样式,通常涉及多条件组合(如 size + variant + isDisabled + theme.mode)、单位换算(如 rem 转 px),或依赖外部状态(如全局 loading)。这类逻辑绝不应直接写入样式函数体内——这是一条硬性规则。
- 将组合判断提前汇总:例如
const btnClass = useMemo(() => getButtonClass({ size, variant, isDisabled, theme }), [size, variant, isDisabled, theme]),其目的就是将判断逻辑集中处理,避免在每个样式函数中重复计算。 - 数值单位统一在组件层处理:例如
margin: ${spacingMap[props.spacing]}px比margin: ${props.spacing}rem更可靠,因为 rem 依赖根字体大小,容易导致错误。 - 在样式函数中应避免使用对象深比较或数组遍历——一旦浅比较失效,class 将被迫重新生成,引发样式刷新抖动,成为性能的隐形杀手。
如何使多个动态样式片段协同生效
单个 styled 组件可以同时使用多个样式来源,但拼接方式的选择会直接影响可维护性和性能。
- 推荐写法:
const Comp = styled.div(props => [baseStyle, props.isActive && activeStyle, props.theme.dark && darkModeStyle])—— 利用数组形式合并多个样式片段,emotion 或 styled-components 会自动将它们整合为一个 class。 - 尽量避免嵌套对象写法:
css({ '&:hover': { color: 'red' } })比css`&:hover { color: red; }`多一次解析,简单场景下无需自寻烦恼。 - 如果某段样式需要复用(例如所有 loading 状态的 opacity 动画),应将其抽离为独立的
css常量,避免重复编写。
还有一个容易忽视的要点——引用稳定性。即便你提前计算好了 state,但如果每次渲染都传入一个新对象(例如 { config: { size: 'lg' } }),CSS-in-JS 库的浅比较机制将失效,导致不必要的样式重建。解决办法是:使用 useMemo 缓存 props 对象,或者直接改用扁平字段(如直接传入 size="lg"),这比临时创建对象更可靠。
