antd Spin 语义化样式定制指南:使用 classNames 与 styles 精确控制加载中状态的每个部分
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
Spin(加载中)是 antd 反馈类组件中最常被二次定制的组件之一——业务侧往往需要微调指示器颜色、描述文案的间距、嵌套内容在加载时的遮罩过渡等。antd v6 起,Spin 通过classNames与styles两个属性开放了基于**语义化结构(Semantic DOM)**的样式入口:你可以传入普通对象,也可以传入({ props }) => ...形式的函数,从而在运行时根据组件实际状态(如尺寸)动态下发样式。本文将以仓库中的 style-class 演示 与对应 说明文档 为核心,结合 Spin 源码,讲解这两套 API 的完整用法、语义节点清单以及底层合并原理。
为什么 Spin 需要语义化样式 API
Spin 的 DOM 结构会随使用形态而变化:单独使用时渲染"指示器 + 可选描述";作为包裹元素(有 children)或使用fullscreen时,则会多出承载加载区域的section与承载内容层的container。若只用传统的className/style,只能改到根元素,无法精准命中某一层。
因此 v6 为 Spin 引入了对齐 antd"语义化 DOM"体系的classNames与styles(版本标注见 Spin API 文档):
classNames:以对象或函数形式为各语义节点提供自定义 class;styles:以对象或函数形式为各语义节点提供行内CSSProperties;- 两者自
6.0.0起可用,传入对象或函数均可,函数收到的参数为{ props }。
语义化结构一览:Spin 的五个节点
依据 源码类型定义 与 语义预览演示,Spin 的语义节点包括五个,各自的职责与最小可用版本如下:
| 语义节点 | 对应 DOM/职责 | 可用版本 |
|---|---|---|
root | 根元素,负责绝对定位、显示控制、颜色、字号、对齐、透明度与过渡动画 | 6.0.0 |
section | 加载元素所在区域(嵌套/fullscreen 形态下的加载层),负责相对定位、flex 布局与对齐 | 6.3.0 |
indicator | 指示器元素,负责宽高、字号、inline-block、过渡动画与 line-height | 6.0.0 |
description | 描述文案元素 | 6.3.0 |
container | 承载被包裹子元素的内容容器,负责透明度与过渡动画 | 6.3.0 |
其中tip、mask两个旧字段已废弃:tip请改用description,mask请改用root(源码中通过devUseWarning在开发环境给出 deprecation 提示)。
用法一:以对象形式传入 classNames 与 styles
对象形式最直接:直接给出节点名到样式值的映射。对于styles,值就是标准React.CSSProperties;对于classNames,值是样式类名。
仓库 style-class.tsx 中演示了三种叠加用法:
import React from 'react'; import { Flex, Spin } from 'antd'; import type { GetProp, SpinProps } from 'antd'; import { createStaticStyles } from 'antd-style'; // 1) 通过 antd-style 生成静态类,再注入 classNames.root const classNames = createStaticStyles(({ css }) => ({ root: css` padding: 8px; `, })); // 2) styles 的对象形式:直接命中 indicator 节点 const stylesObject: SpinProps['styles'] = { indicator: { color: '#00d4ff', }, }; const App: React.FC = () => { const sharedProps: SpinProps = { spinning: true, percent: 0, classNames: { root: classNames.root }, }; return ( <Flex align="center" gap="medium"> <Spin {...sharedProps} styles={stylesObject} /> </Flex> ); };示例要点:
classNames与styles可以同时使用:一个负责挂 class(便于写 hover、动画等 CSS 能力),一个负责行内样式;- 这里的静态类由文档站开发依赖
antd-style(^4.1.0,见 package.json)生成,实际业务中你完全可以直接使用自己的 CSS Module、styled 产物或任何普通字符串类名; indicator样式直接作用于旋转图标所在元素,因此仅用一行color即可改变指示器颜色。
用法二:以函数形式按组件状态动态返回样式
classNames/styles的函数签名统一为:
type StylesFn = (info: { props: SpinProps }) => Record<SemanticNode, React.CSSProperties>;函数体接收{ props },其props是组件经过合并后的最终属性。从 Spin 实现 看,它至少包含当前生效的size、spinning、fullscreen、percent以及合并后的description。这意味着你可以在函数里做基于状态的条件样式。
演示中的stylesFn根据尺寸切换指示器颜色:
const stylesFn: SpinProps['styles'] = ({ props }): GetProp<SpinProps, 'styles', 'Return'> => { if (props.size === 'small') { return { indicator: { color: '#722ed1', }, }; } return {}; }; // 使用:同一函数,因 size="small" 走紫色分支 <Spin {...sharedProps} styles={stylesFn} size="small" />GetProp<SpinProps, 'styles', 'Return'>是 antd 导出的类型工具,可取出styles的返回值类型,让函数返回值获得完整类型检查。返回{}表示该状态不下发任何样式,这是函数形式的常见写法。
完整可运行示例
综合对象与函数两种形态,一个自包含的演示如下(语义结构与 style-class.tsx 一致):
import React from 'react'; import { Flex, Spin } from 'antd'; import type { SpinProps } from 'antd'; const stylesBySize: SpinProps['styles'] = ({ props }) => { const colorMap: Record<string, string> = { small: '#722ed1', medium: '#1677ff', large: '#52c41a', }; return { indicator: { color: colorMap[props.size ?? 'medium'] } }; }; const App: React.FC = () => ( <Flex align="center" gap="middle"> <Spin size="small" styles={stylesBySize} /> <Spin size="medium" styles={stylesBySize} /> <Spin size="large" styles={stylesBySize} /> </Flex> ); export default App;提示:
size默认值为medium,历史值default已被废弃并将在 v7 移除(见 SpinProps 定义)。
源码原理:函数如何被解析、class 如何合并
理解底层实现能帮你判断函数调用时机与 class 拼接行为。相关逻辑集中在两个文件:
1. 函数/对象解析与多来源合并(useMergeSemantic)
Spin 渲染前会先构造mergedProps,随后调用useMergeSemantic(index.tsx):
const [mergedClassNames, mergedStyles] = useMergeSemantic( [contextClassNames, classNames], // 全局配置层 + 组件层 classNames [contextStyles, contextStyleRoot, styles], // 全局配置层 + 组件层 styles { props: mergedProps }, );useMergeSemantic内部对每个来源依次执行resolveStyleOrClass:
export const resolveStyleOrClass = (value, info) => isFunction(value) ? value(info) : value;即:每次渲染都会判断值是否为函数,是则用当前mergedProps调用它——这正是函数形式能感知props.size的原因;然后 class 通过clsx逐层拼接(contextClassNames在前、组件classNames在后),style 通过浅合并逐层覆盖。因此 Spin 的全局配置(ConfigProvider 的classNames/styles)与实例上的值会自然叠加。
2. 合并结果如何落到语义节点(Spin 渲染层)
从渲染 JSX 可看到每个节点对应的目标:
- 最外层
<div>接收mergedClassNames.root与mergedStyles.root; - 有 children 或
fullscreen时,加载指示区域放入独立的${prefixCls}-section容器(挂section语义),子内容放入${prefixCls}-container容器(挂container语义); - 非嵌套形态下根节点会同时并入
section的 class 与样式,因此单用时无需担心section配置"丢失"; - 描述文案
<div>同时兼容旧字段tip与新字段description的 class/style(index.tsx),源码用合并写法保证两代 API 过渡期行为一致。
3. 开发期废弃提示
在非生产环境,Spin 会对以下写法输出devUseWarning警告(index.tsx):
size="default"→ 改用size="medium";tipprop → 改用description;wrapperClassName→ 改用classNames.root;classNames.tip/styles.tip→ 改用description对应字段;classNames.mask/styles.mask→ 改用root对应字段。
实践建议与常见坑
- 函数形式避免内联副作用:函数每次渲染都会执行,内部只应做纯计算与样式返回,不要在此发起网络请求或写 DOM。
- 全屏/嵌套与单独形态差异:
section、container只在嵌套或fullscreen形态出现;若你的样式仅针对单加载形态,请优先落在root/indicator上以保证两种形态都生效。 - 样式优先级:行内样式按
contextStyles→contextStyle(root)→styles顺序合并,实例上的styles优先生效;class 则是全局层与实例层经clsx拼接后共同作用于元素,自定义 class 的具体视觉表现取决于其 CSS 特异性与书写顺序。 - 与 ConfigProvider 协同:想统一全站 Spin 的指示器颜色,可在 ConfigProvider 的组件级
classNames/styles(useComponentConfig('spin')读取,见 index.tsx)中配置;实例属性会在此基础上进一步覆盖。
小结
classNames/styles让 Spin 的自定义从"整体一个根 class"进化到"按root/section/indicator/description/container精准施力"。对象形式适合静态定制,函数形式适合根据props.size、spinning等状态动态响应。配合 ConfigProvider 的全局注入与源码级的多源合并机制,你既能得到统一默认外观,也能在单个实例上无损覆盖——这正是 antd v6 语义化样式体系的通用心智模型,也适用于仓库中其他已支持 Semantic DOM 的组件。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考