-
Notifications
You must be signed in to change notification settings - Fork 0
Description
仓库, 里面用到的 genStyleUtils 来自 cssinjs-utils
https://github.com/ant-design/cssinjs-utils
背景知识
在阅读代码之前,你需要理解 Ant Design v5 相比 v4 的几个关键架构转变:
- CSS-in-JS (Runtime): 不再使用 Less 编译,而是在运行时动态生成 CSS。这使得动态切换主题(Dark Mode、Compact Mode)无需刷新页面。
https://ant.design/docs/react/customize-theme-cn#%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5
- Design Token System (设计变量系统)
- Seed Token: 基础变量(如 blue-6)
- Map Token: 映射变量(如 colorPrimary)
- Alias Token: 别名变量(如 colorLink)
- 按需加载: 只有当组件渲染时,对应的
<style>标签才会被插入到<head>中。
genStyleUtils
- 输入: 全局的配置上下文(Hooks、工具函数)。
- 输出: 一组用于生成组件样式的工厂函数 (genStyleHooks, genComponentStyleHook)。
它的主要职责是:
- 依赖注入: 将获取 Token、前缀、CSP 配置的能力注入进来,解耦了底层逻辑。
- Token 处理: 自动合并 Global Token 和 Component Token,并处理 CSS 变量(CSS Variables)。
- 样式注册: 封装 useStyleRegister,处理 Hash 计算(用于样式隔离)和 SSR 支持。
- 统一标准化: 确保所有组件(Button, Table 等)使用相同的逻辑生成样式,保证一致性。
config 配置项详解
| 配置项 | 类型 | 作用详解 |
| usePrefix | Hook | 获取类名前缀。
默认是 ant。如果用户配置了 ConfigProvider 修改前缀(例如改为 my-app),这个 Hook 会确保生成的样式类名变为 .my-app-btn。 |
| useToken | Hook | 获取当前的主题 Token。
这是最关键的部分。它从 Context 中拿到当前的 Design Token(颜色、圆角、间距等),并计算出 hashId(用于样式隔离的唯一标识)。|
| useCSP | Hook | 内容安全策略 (Content Security Policy)。
用于获取 nonce 属性。在严格的安全环境下,动态插入的 <style> 标签必须包含合法的 nonce 才能执行。 |
| getResetStyles | Function | 生成重置样式 (Reset CSS)。
类似于 normalize.css。当组件首次加载时,确保基础标签(如 body, p, ul)的样式被统一重置,消除浏览器差异。|
| getCommonStyle | Function | 生成通用样式。
生成所有组件共用的样式,例如基础字体 (font-family)、盒模型 (box-sizing) 等。通常会混入到每个组件的样式中。|
| getCompUnitless | Function | 定义无单位 Token。
告诉系统哪些组件 Token 不需要自动添加 px 后缀。例如 zIndex, opacity, fontWeight 是数字,不应该变成 100px。 |
| layer | Object | CSS Layer (级联层)。
用于控制样式优先级。Ant Design 默认将样式放入 antd 层,防止被用户自定义的全局 CSS 覆盖。 |
返回值解析 (生成的工具)
genStyleHooks` (最常用)
这是给组件(如 Button)使用的最终入口。
- 用法:
export default genStyleHooks('Button', styleFn, defaultToken); - 作用: 它返回一个自定义 Hook(例如 useStyle)。当你在组件里写
const [wrapSSR, hashId] = useStyle(prefixCls)时,它会自动计算样式、生成 CSS 变量,并插入 DOM。
genComponentStyleHook
这是 genStyleHooks 的底层实现。
- 作用: 直接调用
@ant-design/cssinjs的useStyleRegister。 - 逻辑:
- 获取 Token。
- 计算 Hash。
- 合并 Component Token。
- 执行组件的样式生成函数 (styleFn)。
- 将生成的 CSS 对象转为 CSS 字符串并缓存。
genSubStyleComponent
用于生成子组件样式。
- 场景: 某些复杂的组件(如 Table 的筛选下拉框、DatePicker 的弹窗)可能需要独立的样式逻辑,或者为了拆分代码体积。
- 作用: 创建一个不渲染 DOM 节点,只负责注入样式的 React 组件。
从用户配置到最终 CSS 的完整流程
flowchart TD
%% 1. 配置阶段
subgraph Config [1. 配置阶段]
direction TB
UserConfig["ConfigProvider theme={...}"]
UserValue["activeShadow: '0 0 0 4px red'"]
UserConfig --> UserValue
end
%% 2. 运行时合并阶段
subgraph Runtime [2. 运行时合并阶段]
direction TB
GlobalToken["Global Token (Blue Theme)"]
DefaultLogic["prepareComponentToken()"]
DefaultValue["默认值: '0 0 0 2px blue'"]
MergeEngine{"样式引擎合并"}
GlobalToken --> DefaultLogic
DefaultLogic --> DefaultValue
UserValue --> MergeEngine
DefaultValue --> MergeEngine
FinalToken["最终 Token: '0 0 0 4px red'"]
MergeEngine -->|覆盖默认值| FinalToken
end
%% 3. 样式生成阶段
subgraph Generation [3. 样式生成阶段]
direction TB
GenStyleFn["genSelectInputStyle(token)"]
CSSObject["CSS Object: { boxShadow: '...red' }"]
FinalToken --> GenStyleFn
GenStyleFn --> CSSObject
end
%% 4. CSS 注入阶段
subgraph Injection [4. CSS 注入阶段 CSS Injection]
direction TB
Engine["CSS-in-JS Engine"]
DOMStyle["<style> .ant-select-focused { box-shadow: ...red } </style>"]
CSSObject --> Engine
Engine -->|插入 DOM| DOMStyle
end
Config --> Runtime
Runtime --> Generation
Generation --> Injection
以 activeShadow 为例,追踪它从用户配置到最终 CSS 的完整旅程。
1. 配置阶段
地点: components/select/demo/basic.tsx
用户通过 ConfigProvider 注入自定义值。
<ConfigProvider
theme={{
components: {
Select: {
// 1. 用户定义:我要红色的阴影
activeShadow: '0 0 0 4px red',
}
}
}}
>
<Select />
</ConfigProvider>2. 运行时合并阶段
地点: node_modules/@ant-design/cssinjs-utils/lib/util/genStyleUtils.d.ts (实际运行在 useStyleRegister 内部)
当 <Select /> 组件渲染时,useStyle Hook 被触发。Ant Design 的样式引擎开始工作:
- 获取全局 Token: 从 Context 中拿到基础 Token(如
colorPrimary等)。 - 计算默认组件 Token: 调用
prepareComponentToken(globalToken)。- 此时计算出的默认值是:
0 0 0 2px blue(假设是默认蓝色主题)。
- 此时计算出的默认值是:
- 合并用户配置: 样式引擎发现
ConfigProvider中有Select.activeShadow。- 覆盖发生: 用户配置的
'0 0 0 4px red'覆盖 了默认的'0 0 0 2px blue'。
- 覆盖发生: 用户配置的
- 生成最终 Token: 此时
token.activeShadow的值变成了'0 0 0 4px red'
3. 样式生成阶段 (Style Generation)
地点: components/select/style/select-input.ts
样式生成函数 genSelectInputStyle 接收到了合并后的 Token。
// token.activeShadow 此时已经是 '0 0 0 4px red' 了
genSelectInputVariantStyle(
token,
'outlined',
{
// ...
// 4. 传递 Token:将 '0 0 0 4px red' 传给 colors.shadow
shadow: token.activeShadow,
}
)接着进入 genSelectInputVariableStyle:
const genSelectInputVariableStyle = (token, colors) => {
return {
// ...
[`&${componentCls}-focused`]: {
// 5. 生成 CSS 对象
// colors.shadow 是 '0 0 0 4px red'
boxShadow: colors.shadow,
},
};
};4. CSS 注入阶段 (CSS Injection)
地点: 浏览器 DOM
最终,Ant Design 的 CSS-in-JS 引擎将上述 CSS 对象转换为 CSS 字符串,并插入到 <style> 标签中。
/* 6. 最终生成的 CSS */
.ant-select-focused {
box-shadow: 0 0 0 4px red;
}