首页
/ Ant Design Divider 语义化结构定制:深入 classNames 与 styles 的 object / function 用法

Ant Design Divider 语义化结构定制:深入 classNames 与 styles 的 object / function 用法

2026-09-07 20:34:55作者:傅爽业Veleda

导读

本文围绕 Ant Design Divider 分割线组件提供的 classNamesstyles 属性,讲解如何按组件内部的语义化结构root / rail / content)精确定制分割线外观,并完整演示「对象」与「函数」两种传参形态的差别与适用场景。文章以仓库中的官方示例 style-class.tsx 为主干,结合 Divider 源码语义 DOM 演示 与通用合并工具 useMergeSemantic,带你理解这些属性在底层是如何被解析、合并与写入 DOM 的。读完你将能够在自己的业务中无痛地为 Divider 实现「按文案位置、按尺寸动态换肤」等精细样式控制。

功能说明:该能力是 Ant Design v6.0.0 引入的组件级「语义化样式定制」能力,适用于 5.x 起持续推进的 classNames / styles 方案,具体到 Divider 时版本门槛为 6.0.0(参见 Divider API 文档 中相关行的 Version 标注)。

一、先搞清楚 Divider 的语义化结构:root / rail / content

想要用好 classNamesstyles,第一步是认识 Divider 渲染出的 DOM 结构。官方在 Divider API 的 Semantic DOM 一节中给出三个语义节点,它们的实际形态取决于分割线是否带文字:

  • root:最外层根元素。源码中即渲染在 <div role="separator"> 上(见 index.tsx),承载了边框顶线、水平/垂直布局、variant(solid/dashed/dotted)、size(sm/md)等基础样式与若干状态类。
  • rail:分隔线「轨道/连接条」。带文字时,它是文字左右两侧的两段线(-rail-start-rail-end 两个 <div>,见 index.tsx);不带文字时,rail 的样式与类会直接合并到根元素上(见 index.tsx[railCls]: !children 的判断)。
  • content:文字内容元素,即包裹 children<span class="ant-divider-inner-text">(见 index.tsx)。

官方在 _semantic.tsx 中用中英文描述了三者职责:

语义节点 覆盖内容
root 根元素,含 border-top 样式、分割线样式等容器级基础样式
content 文字内容样式,含 inline-block 显示、内边距等
rail 背景连接条,含 border-top 样式等线条样式

而这些类名真实出现在 DOM 中,有快照测试可印证——在 demo-extend.test.ts.snap 中可以看到 ant-divider-rail ant-divider-rail-startant-divider-rail ant-divider-rail-end 以及 demo-divider-rail 等来自示例的自定义类名。

二、classNames 与 styles:类型签名与两种传参形态

Divider API 文档 的定义,两个属性的类型完全对称:

属性 类型 默认 版本
classNames Record<SemanticDOM, string> | (info: { props }) => Record<SemanticDOM, string> - 6.0.0
styles Record<SemanticDOM, CSSProperties> | (info: { props }) => Record<SemanticDOM, CSSProperties> - 6.0.0

两者都支持两种传参形态:

  1. 对象(Object)形态:直接给出「语义节点 -> 类名 / 内联样式」的静态映射,适合固定风格的场景。
  2. 函数(Function)形态:接收 info: { props } 参数,返回对应的映射对象,可以根据 Divider 当前的真实 props(如 titlePlacementsizeorientation 等)动态决定样式,适合需要随属性变化的条件样式。

源码侧的类型约束在 index.tsx 中定义:classNames 允许 root? / rail? / content? 三个 key,styles 同样。经由 semanticType.ts 中的 GenerateSemantic<T, Props> 泛型工具展开后,对外暴露 classNamesAndFn / stylesAndFn 联合类型(见 index.tsx),这正是文档类型表里「对象或函数」的来源。

三、动手实践:拆解官方示例 style-class 的四种写法

仓库中的官方示例 style-class.tsx 是本文最核心的可运行材料,它一口气展示了四种组合:classNames 对象、classNames 函数、styles 对象、styles 函数。下面逐段拆解。

3.1 classNames 传对象:给三个语义节点各加一个类

const classNamesObject: DividerProps['classNames'] = {
  root: 'demo-divider-root',
  content: 'demo-divider-content',
  rail: 'demo-divider-rail',
};

// 使用:
<Divider classNames={classNamesObject}>classNames Object</Divider>

要点:这里的类名会被追加到对应语义节点已有的类名之后(合并而非覆盖),因此你既可以用它们配合 CSS Modules / 全局 CSS 编写样式,也不会破坏 Ant Design 自身的样式体系。带文字时 demo-divider-rail 会同时落到左右两条 -rail-start / -rail-end 上;demo-divider-content 落到文字 <span> 上;demo-divider-root 落到最外层根元素上。若想直接验证,可在 demo-extend.test.ts.snap 中看到对应输出。

3.2 classNames 传函数:依据 titlePlacement 动态换根节点类

const classNamesFn: DividerProps['classNames'] = (
  info,
): GetProp<DividerProps, 'classNames', 'Return'> => {
  if (info.props.titlePlacement === 'start') {
    return { root: 'demo-divider-root--start' };
  }
  return { root: 'demo-divider-root--default' };
};

// 使用:
<Divider titlePlacement="start" classNames={classNamesFn}>
  classNames Function
</Divider>

示例利用「标题靠左」这一状态给根元素打上不同类名。需要留意的两个源码细节:

  • 传入回调的 info.props经过归一化后的 props(源码中记为 mergedProps,见 index.tsx)。例如 titlePlacement 传入 left/right 时,源码会结合 RTL 方向把它换算为 start/end/center(见 index.tsx),因此函数里只需判断 start/end/center 即可,无需关心书写方向。
  • 类型上 info 的回调返回值会被收窄为 GetProp<DividerProps, 'classNames', 'Return'>,保证你返回的 key 只能落在 root/rail/content 语义节点上。

3.3 styles 传对象:三个节点各给内联样式

const stylesObject: DividerProps['styles'] = {
  root: { borderWidth: 2, borderStyle: 'dashed' },
  content: { fontStyle: 'italic' },
  rail: { opacity: 0.85 },
};

// 使用:
<Divider styles={stylesObject}>styles Object</Divider>

内联样式会精确地写入对应语义节点:

  • root 的样式展开到根 <div> 的内联 style
  • rail 的样式分别写入带文字时的两段 rail,或在无文字时随合并逻辑落到根元素;
  • content 的样式写入文字 <span>(还与 orientationMargin 换算出的左右 margin 并存,见 index.tsx)。

由于是内联样式,优先级天然高于外部类,适合快速覆盖、临时调试或动态数值(例如随状态变化的颜色)。

3.4 styles 传函数:依据 size 做整根分割线的条件样式

const stylesFn: DividerProps['styles'] = (info): GetProp<DividerProps, 'styles', 'Return'> => {
  if (info.props.size === 'small') {
    return { root: { opacity: 0.6, cursor: 'default' } };
  }
  return { root: { backgroundColor: '#fafafa', borderColor: '#d9d9d9' } };
};

// 使用:
<Divider size="small" styles={stylesFn}>styles Function</Divider>

此处读取的是归一化后的 info.props.size。需要说明:Divider 的 size 属性为 small | medium | large,同时会继承 ConfigProvider 的全局尺寸(源码通过 useSize(customSize) 解析,见 index.tsx),并在合并后统一进入 mergedProps.size,所以回调中你拿到的是「组件属性 + 全局配置」的最终生效值,判断起来非常可靠。

3.5 完整示例速览

import React from 'react';
import { Divider } from 'antd';
import type { DividerProps, GetProp } from 'antd';

const classNamesObject: DividerProps['classNames'] = {
  root: 'demo-divider-root',
  content: 'demo-divider-content',
  rail: 'demo-divider-rail',
};

const stylesFn: DividerProps['styles'] = (info) => {
  if (info.props.size === 'small') {
    return { root: { opacity: 0.6, cursor: 'default' } };
  }
  return { root: { backgroundColor: '#fafafa', borderColor: '#d9d9d9' } };
};

const App: React.FC = () => (
  <div>
    <Divider classNames={classNamesObject}>classNames Object</Divider>
    <Divider size="small" styles={stylesFn}>
      styles Function
    </Divider>
  </div>
);

export default App;

四、源码级原理解析:这些属性底层是怎么工作的

4.1 统一入口:useMergeSemantic

Divider 并不自己手写样式合并逻辑,而是调用通用 Hook useMergeSemantic,位于 index.tsx

const [mergedClassNames, mergedStyles] = useMergeSemantic<
  DividerSemanticAllType['classNames'],
  DividerSemanticAllType['styles'],
  DividerProps
>([contextClassNames, classNames], [contextStyles, contextStyleRoot, styles], {
  props: mergedProps,
});

这个调用的两个细节值得展开:

  • 多来源合并classNames 的合并列表同时包含 contextClassNames(来自 ConfigProvider 的组件级配置,见 index.tsx)与用户传入的 classNamesstyles 的合并列表还额外叠加上通过 useSemanticRootStyle 包装的 contextStyle。也就是说,全局配置与局部传参可以叠加生效,局部优先级更高。
  • 函数在此被解析:Hook 内部先用 resolveStyleOrClass 把函数形态的配置「按 info.props 求出结果」(见 useMergeSemantic/index.ts),再做合并,这正是「函数形态能按 props 动态出样式」的实现基石。

4.2 classNames 是拼接,styles 是浅合并

两类结果的合并策略并不相同,理解后可以避免误用:

  • 类名拼接mergeClassNames 对字符串类名使用 clsx 拼接(保留并追加),所以多个来源的类名会同时存在于 DOM 上,便于叠加多套规则;若后续希望覆盖样式,需靠 CSS 优先级(选择器权重、声明顺序)来解决。
  • 样式浅合并mergeStyles 对每个语义节点的样式对象做 { ...acc[key], ...cur[key] } 的逐 key 浅合并(见 useMergeSemantic/index.ts),后出现的来源会覆盖同名 CSS 属性。

4.3 合并结果如何写回 DOM

回到渲染层可以清楚地看到三处注入(见 index.tsx):

<div
  className={classString}        // 内含 mergedClassNames.root(以及无 children 时的 mergedClassNames.rail)
  style={{
    ...mergedStyles.root,
    ...(children ? {} : mergedStyles.rail), // 无文字时 rail 样式落在根元素
    ...style,
  }}
  role="separator"
>
  {children && !mergedVertical && (
    <>
      <div className={clsx(railCls, `${railCls}-start`, mergedClassNames.rail)} style={mergedStyles.rail} />
      <span className={clsx(`${prefixCls}-inner-text`, mergedClassNames.content)} style={{ ...innerStyle, ...mergedStyles.content }}>
        {children}
      </span>
      <div className={clsx(railCls, `${railCls}-end`, mergedClassNames.rail)} style={mergedStyles.rail} />
    </>
  )}
</div>

由此可提炼出三条实用结论:

  1. children 时分割线退化为一条线,rail 的语义样式直接作用于根元素,此时再单独写 root/rail 都是作用于同一个 DOM 节点。
  2. 带文字时 rail 样式会被同时应用到 start 与 end 两段 rail 上,无需分别指定。
  3. root 的内联样式在 DOM 中始终存在,最终的内联样式合并顺序为 mergedStyles.root(→ 无文字时的 rail)→ 用户 style,因此普通 style 属性优先级最高,可用它做最后兜底。

4.4 与默认样式、CSS-in-JS 的关系

需要澄清边界:classNames/styles 定制的是组件外层的语义容器,而分割线自身的默认视觉(如 borderBlockStart: 1px solid colorSplit、rail 的宽度分配、orientationMargin 的百分比轨道宽度)由样式文件 style/index.ts 经 CSS-in-JS 生成,其中 rail 在 titlePlacement 为 start/end 时按 calc(${orientationMargin} * 100%) 分配宽度(见 style/index.ts)。做深度换肤时,合理的做法是:

  • 只调「个别视觉属性」→ styles 内联样式;
  • 要配合语义类做主题化(暗色/品牌色)→ classNames + CSS Modules/全局样式;
  • 要改动整体组件的设计变量(间距、线宽、颜色 Token,如 textPaddingInlineverticalMarginInline 等)→ 走 Component Token 体系。

五、使用建议与避坑清单

结合实现与示例,汇总几条可直接落地的经验:

  1. 能归一就不手写方向逻辑:函数形态收到的 titlePlacement 已经是 RTL 感知后的 start/end/center,不要在业务里再自行判断 left/right 与书写方向。
  2. 对象适合静态主题、函数适合条件状态:若样式只在少数几个属性分支间切换,函数形态配合 size/titlePlacement 判断是示例给出的官方推荐姿势;但函数会在组件渲染时被调用(见 useMergeSemantic/index.ts),不要在回调中做高开销计算。
  3. 区分 classNames 与 styles 的合并语义classNames 是追加、styles 是覆盖;需要「改」某个默认内联值用后者,需要「叠加外部规则」用前者。
  4. 留意 rail 的双重形态:带文字时 rail 是两条独立节点,不带文字时 rail 收敛到根元素,样式书写时按「无字场景 root 即 rail」来设计可避免意外。
  5. 版本前提:该能力依赖 v6.0.0 及以上的 Divider(示例在 index.en-US.md 中标注 version="6.0.0"),低版本迁移时建议参考 v6 迁移文档 中对语义化结构与 classNames/styles 的说明,并将被废弃的 orientationMargin 迁移到 styles.content.margin

结语

classNamesstyles 是 Ant Design 面向精细化样式定制的统一出口。对 Divider 而言,掌握 root / rail / content 三个语义节点的真实 DOM 分布、理解对象与函数两种形态的差异,以及知晓底层 useMergeSemantic 的合并规则,就能在不动源码、不影响全局的前提下,把分割线调成任何想要的样子。若想继续深入,可以在仓库中对照阅读 style-class.tsxDivider 渲染源码useMergeSemantic 实现,三者串联起来即是这套能力从「API」到「DOM」的完整链路。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.76 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
858
1.35 K
docsdocs
暂无描述
Markdown
899
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
923
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.83 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
532
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
524
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
393