
ant-design Calendar 日历组件实战从 cellRender 渲染管线到语义化 DOM 的完整解析【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-designCalendar 是 ant-design「数据展示」分组中用于按日历形式呈现数据的容器组件适用于日程、课表、价格日历、农历展示等场景。本文基于 ant-design 仓库中 Calendar 官方文档 的完整 API 与全部官方示例结合 核心实现文件 generateCalendar.tsx 和 头部实现文件 Header.tsx 的源码讲清楚每个参数的实际行为、渲染优先级与事件触发时机帮助你掌握从基础用法到自定义头部、农历渲染、语义化类名定制的完整能力。何时使用当数据是日期或按照日期划分时例如日程、课表、价格日历等或者需要展示农历等适合使用 Calendar。组件目前支持年/月两种面板模式切换。官方示例覆盖了以下典型场景基本默认日历demo/basic.tsx通知事项日历在日期单元格内展示事件列表demo/notice-calendar.tsx跨日期事件事件横跨多个日期demo/event-range.tsx卡片模式非全屏的紧凑日历demo/card.tsx选择功能响应日期选择demo/select.tsx农历日历叠加农历、节气、节假日demo/lunar.tsx周数5.23.0 起显示周数列demo/week.tsx自定义头部demo/customize-header.tsx自定义语义结构的样式和类6.0.0 起demo/style-class.tsx组件 Tokendemo/component-token.tsx快速上手与受控机制官方文档给出的最小用法// 默认语言为 en-US所以如果需要使用其他语言推荐在入口文件全局设置 locale // import dayjs from dayjs; // import dayjs/locale/zh-cn; // dayjs.locale(zh-cn); Calendar cellRender{cellRender} onPanelChange{onPanelChange} onSelect{onSelect} /注意官方特别强调Calendar 部分 locale 是从value中读取的所以必须先正确设置 dayjs 的 locale否则头部显示的年月文案可能与locale配置不一致。从源码结构看Calendar 是基于rc-component/picker的PickerPanel封装的generateCalendar.tsx 通过generateConfig默认使用rc-component/picker/generate/dayjs的 dayjs 配置见 index.tsx生成组件内部渲染一个hideHeader的RCPickerPanel再自行渲染头部与网格。值与模式都是受控/非受控混合状态// 简化自 components/calendar/generateCalendar.tsx const [mergedValue, setMergedValue] useControlledState( () defaultValue || generateConfig.getNow(), // 初始值defaultValue 或当前时间 value, ); const [mergedMode, setMergedMode] useControlledStateCalendarMode(month, mode);也就是说value存在时完全受控不传value时组件内部用defaultValue或今天自管理状态。日历根节点会根据fullscreen添加-full/-mini类名根据direction rtl添加-rtl类名。mode初始模式month|year默认month与内部面板模式并非一一对应源码中做了映射const panelMode React.useMemomonth | date( () (mergedMode year ? month : date), [mergedMode], );即「年」模式实际渲染 12 个月份的网格rc-picker 的 month 面板「月」模式渲染日期网格。单元格渲染cellRender 与 fullCellRender 的优先级这是 Calendar 最核心的定制能力。两者回调签名相同function( current: Dayjs, info: { prefixCls: string; originNode: React.ReactElement; today: Dayjs; range?: start | end; type: PanelMode; // date | month 等 locale?: Locale; subType?: hour | minute | second | meridiem; } ): React.ReactNode区别在于渲染粒度。从 generateCalendar.tsx 的实现看日期单元格走dateRender、月份单元格走monthRender两者的优先级完全一致fullCellRender优先返回值直接替换整个单元格包括日期数字本身其次是已废弃的dateFullCellRender/monthFullCellRender源码中有deprecated Please use fullCellRender instead标注开发模式下使用会触发 deprecation warning都不提供时默认渲染一个日期数字区-date-value 内容区-date-content此时cellRender只替换内容区默认还会根据是否与今天相同来添加-date-today高亮类名。开发模式下源码会对 4 个旧 API 统一发出废弃警告[ [dateFullCellRender, fullCellRender], [dateCellRender, cellRender], [monthFullCellRender, fullCellRender], [monthCellRender, cellRender], ].forEach(([deprecatedName, newName]) { warning.deprecated(!(deprecatedName in props), deprecatedName, newName); });所以 5.4.0 之后的项目应统一迁移到cellRender/fullCellRender。通知事项日历示例官方「通知事项日历」示例展示了cellRender的标准写法——按info.type区分日期格与月份格未覆盖的分支返回info.originNode以保持默认样式完整代码见 demo/notice-calendar.tsxconst cellRender: CalendarPropsDayjs[cellRender] (current, info) { if (info.type date) { return dateCellRender(current); // 渲染 Badge 事件列表 } if (info.type month) { return monthCellRender(current); // 渲染该月统计数字 } return info.originNode; }; return Calendar cellRender{cellRender} /;农历日历fullCellRender cloneElement 的进阶用法「农历日历」示例demo/lunar.tsx是fullCellRender的典型应用它借助第三方库lunar-typescript的Lunar、HolidayUtil计算农历、节气与节假日再通过React.cloneElement(info.originNode, { ... })在保留默认单元格 DOM 结构的前提下替换类名与子节点从而同时拿到默认布局和自定义内容const cellRender: CalendarPropsDayjs[fullCellRender] (date, info) { const d Lunar.fromDate(date.toDate()); const lunar d.getDayInChinese(); const solarTerm d.getJieQi(); if (info.type date) { return React.cloneElement(info.originNode, { className: clsx(styles.dateCell, { [styles.current]: selectDate.isSame(date, date), [styles.today]: date.isSame(dayjs(), date), }), children: ( div className{styles.text} span{date.get(date)}/span div className{styles.lunar}{displayHoliday || solarTerm || lunar}/div /div ), }); } // info.type month 时渲染「1月正月」这样的农历月份标签 };该示例还搭配了fullscreen{false}卡片模式和headerRender自定义头部年/月选择器展示农历干支与生肖是三者联动的完整参考。选择功能与事件体系onSelect 的 source 来源Calendar 有三个事件onChange(date)、onPanelChange(date, mode)、onSelect(date, info)。它们各自的触发时机在 generateCalendar.tsx 中定义得非常清晰const triggerChange (date: DateType) { setMergedValue(date); if (!isSameDate(date, mergedValue, generateConfig)) { // 月面板切换月份、或年面板切换年份时才会触发 onPanelChange if ( (panelMode date !isSameMonth(date, mergedValue, generateConfig)) || (panelMode month !isSameYear(date, mergedValue, generateConfig)) ) { triggerPanelChange(date, mergedMode); } onChange?.(date); } }; const onInternalSelect (date: DateType, source: SelectInfo[source]) { triggerChange(date); onSelect?.(date, { source }); };由此可以得到三个实用的行为结论onPanelChange不是每次点击都触发只有当新日期与当前日期「跨月月面板或跨年年面板」时才会触发同一天重复点击也不会触发onChangeonSelect携带来源信息source的四种取值对应不同的点击位置date月面板中点击具体日期格month年面板中点击某个月份year/month点击默认头部里的年份 / 月份下拉选择器见 Header.tsx 中YearSelect、MonthSelect分别以year、month调用onChangecustomize通过headerRender自定义头部里调用onChange触发的选择。这正是官方 FAQ「如何仅获取来自面板点击的日期」的答案Calendar onSelect{(date, { source }) { if (source date) { console.log(Panel Select:, source); } }} /如果你希望「选择」只响应面板点击而忽略头部选择器的联动过滤source即可。默认头部结构与 headerRender 自定义不传headerRender时头部由 Header.tsx 的CalendarHeader渲染包含三部分源码级细节年份选择器YearSelect基于 antdSelect默认展示当前年往前 10 年、共 20 年常量YEAR_SELECT_OFFSET 10、YEAR_SELECT_TOTAL 20若设置了validRange选项会被限制在范围的起止年份内且跨年切换时会把月份钳制到允许范围内避免选中范围外的月份月份选择器MonthSelect仅当mode month时渲染12 个月份受validRange同年范围约束模式切换器ModeSwitch基于Radio.Group的「月 / 年」切换文案取自locale.month/locale.year。传入headerRender则整体替换头部回调参数为function( object: { value: Dayjs; // 当前受控/内部日期 type: year | month; // 当前模式 onChange: (date: Dayjs) void; // 触发选择source 为 customize onTypeChange: (type: year | month) void; // 切换模式 } ) React.ReactNode完整示例见 demo/customize-header.tsxdemo/lunar.tsx 中则展示了如何用Row/ColSelectRadio.Group重排头部布局。日期约束disabledDate 与 validRange两个约束属性在源码中合并为同一个判定函数generateCalendar.tsxconst mergedDisabledDate React.useCallback( (date: DateType) { const notInRange validRange ? generateConfig.isAfter(validRange[0], date) || generateConfig.isAfter(date, validRange[1]) : false; return notInRange || !!disabledDate?.(date); }, [disabledDate, validRange], );即validRange可显示日期区间[Dayjs, Dayjs]超出即禁用且与用户自定义的disabledDate(currentDate) boolean是「或」关系。文档同时提醒使用disabledDate时不要直接修改传入的currentDate对象。如前所述validRange的影响不止于禁用单元格默认头部的年份下拉选项、月份下拉选项以及跨年/跨月时的日期钳制逻辑都会根据validRange收窄见 Header.tsx 中YearSelect与MonthSelect的实现适合做「只能预订未来 N 个月」这类业务约束。展示形态fullscreen 与 showWeekfullscreen默认truefalse时切换为「卡片模式」根节点类名从-full变为-mini头部控件自动缩小为sizesmall。官方卡片示例demo/card.tsx用theme.useToken()的colorBorderSecondary、borderRadiusLG给外层加了边框圆角const { token } theme.useToken(); const wrapperStyle: React.CSSProperties { width: 300, border: ${token.lineWidth}px ${token.lineType} ${token.colorBorderSecondary}, borderRadius: token.borderRadiusLG, }; return ( div style{wrapperStyle} Calendar fullscreen{false} onPanelChange{onPanelChange} / /div );showWeek5.23.0 起默认false显示周数列。源码中该属性直接透传给RCPickerPanel。示例 demo/week.tsx 同时演示了全屏与非全屏两种形态Calendar fullscreen showWeek / Calendar fullscreen{false} showWeek /value / defaultValue 与 localevalue展示日期Dayjs受控defaultValue非受控初始日期默认今天locale国际化配置对象默认值来自 components/calendar/locale/en_US.ts并会与全局 ConfigProvider 的Calendar.locale合并源码merge(contextLocale, props.locale || {})。日期类组件的完整国际化配置方式可参考 DatePicker 文档 中的「国际化配置」章节。再次强调文档中的注意事项因为部分 locale 文案直接从valuedayjs 实例读取使用非英文 locale 时务必先dayjs.locale(zh-cn)入口文件全局设置这是 FAQ「为什么时间类组件的国际化 locale 设置不生效」问题的根源之一。语义化 DOMclassNames 与 styles6.0.06.0.0 起Calendar 支持通过classNames/styles属性支持对象或(info: { props }) Record...函数定制内部各语义化结构的类名和行内样式类型定义见 generateCalendar.tsx 中的CalendarSemanticType语义结构及说明参考 demo/_semantic.tsx如下语义节点说明root根元素背景色、边框、圆角等基础样式和整体布局结构header头部元素年份选择器、月份选择器、模式切换器的布局和样式控制body主体元素日历表格的内边距、布局控制用于容纳日历网格content内容元素日历表格的宽度、高度等尺寸控制和表格样式item条目元素日历单元格的背景色、边框、悬停态、选中态等交互样式itemContent条目内容元素单元格内自定义内容区域的高度、溢出等样式控制从源码实现看generateCalendar.tsxroot/header会被拆出应用到外层容器与头部节点其余语义类合并进RCPickerPanel其中itemContent还额外注入到默认单元格的内容区-date-content因此即使不提供cellRenderstyles.itemContent也能影响默认单元格内容区的样式。这些语义配置会先经过useMergeSemantic与 ConfigProvider 的calendar全局配置contextClassNames/contextStyles合并再叠加组件自身的className/style优先级为ConfigProvider 全局 → 组件属性。完整 API 一览参数说明类型默认值版本cellRender自定义单元格的内容内容区function(current: Dayjs, info) ReactNode-5.4.0classNames自定义各语义化结构的 class支持对象或函数RecordSemanticDOM, string \| (info) RecordSemanticDOM, string-6.0.0dateFullCellRender已废弃 5.4.0 请用fullCellRenderfunction(date: Dayjs): ReactNode- 5.4.0fullCellRender自定义整个单元格的内容function(current: Dayjs, info) ReactNode-5.4.0defaultValue默认展示的日期Dayjs--disabledDate不可选择的日期注意不要直接修改参数(currentDate: Dayjs) boolean--fullscreen是否全屏显示booleantrue-showWeek是否显示周数列booleanfalse5.23.0styles自定义各语义化结构的行内 style支持对象或函数RecordSemanticDOM, CSSProperties \| (info) RecordSemanticDOM, CSSProperties-6.0.0headerRender自定义头部内容function(object: { value: Dayjs, type: year \| month, onChange: f(), onTypeChange: f() })--locale国际化配置objecten_US 默认配置-mode初始模式month|yearmonth-validRange设置可以显示的日期[Dayjs, Dayjs]--value展示日期受控Dayjs--onChange日期变化回调function(date: Dayjs)--onPanelChange日期面板变化回调function(date: Dayjs, mode: string)--onSelect选择日期回调包含来源信息function(date: Dayjs, info: { source: year \| month \| date \| customize })info自 5.6.0-通用属性如className、style、rootClassName请参考 ant-design 的 通用属性文档。FAQ如何在 Calendar 中使用自定义日期库参考 ant-design 的 使用自定义日期库 文档中 Calendar 相关章节源码上 index.tsx 暴露了Calendar.generateCalendar(generateConfig)可以传入非 dayjs 的GenerateConfig生成组件。如何给日期类组件配置国际化参考 DatePicker 文档 的「国际化配置」章节在 ConfigProvider 中设置locale同时确保dayjs.locale()与之一致。为什么时间类组件的国际化 locale 设置不生效常见原因是未同步设置 dayjs 的全局 locale。Calendar 的部分文案如value相关的格式化结果直接从 dayjs 实例读取因此ConfigProvider.locale生效不等于 dayjs 文案生效两者都要配置。如何仅获取来自面板点击的日期使用onSelect的info.source过滤见上文「选择功能与事件体系」一节的示例。主题变量Design TokenCalendar 支持在 ConfigProvider 中通过theme.components.Calendar配置组件级 Token组件 Token具体 Token 列表可在官方文档页面「主题变量Design Token」表格中查看示例见 demo/component-token.tsx组件样式实现位于 components/calendar/style/index.ts。小结Calendar 的定制能力可以归纳为四条主线单元格渲染cellRender改内容区、fullCellRender换整个格子、旧 API 一律迁移、头部定制headerRender或依赖validRange收窄默认选择器、日期约束validRange与disabledDate叠加禁用、语义化定制6.0.0 的classNames/styles覆盖 root/header/body/content/item/itemContent 六类节点。配合onSelect的source来源信息与 dayjs 全局 locale 的正确初始化即可覆盖日程、价格日历、农历等绝大多数日期展示业务。【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/GitHub_Trending/an/ant-design创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考