react-spectrum 日期与日历组件 API 设计解析:DatePicker、Calendar 的规范与 v2 到 v3 迁移
【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum
本文以 react-spectrum 仓库中的 specs/api/Calendar.md 为核心,系统讲解DatePicker、DateRangePicker、Calendar、RangeCalendar等日期/日历组件的 API 接口定义(minValue/maxValue、formatOptions、placeholderDate、isQuiet、hideCalendar等参数的含义与设计意图),完整继承文档中的 v2 → v3 属性迁移对照表,并结合当前仓库的 DatePicker 组件实现、useDatePicker Hook 与 Calendar 组件实现 源码,说明每个规范参数在实际代码中的落地位置与调用关系。读完本文,你可以准确理解该规范中每一项属性变更的原因,并在基于 react-spectrum v3 API 编写日期选择功能时正确选用Intl格式化方案与 ISO/时间戳等标准日期值类型。
规范定位:DatePicker、Calendar、TimePicker 的接口基线
specs/api/Calendar.md是仓库specs/api/目录下的一组组件 API 规范文档之一(同目录还有 Avatar.md、Button.md、Provider.md 等),其标题为 “DatePicker, Calendar, TimePicker”,定义了三类日期相关组件在 v3 架构下应采用的公共接口。规范的核心 TypeScript 接口定义如下(完整继承自原文档):
type DateValue = string | number | Date; interface DatePickerBase extends InputBase { minValue?: DateValue, maxValue?: DateValue, formatOptions?: Intl.DateTimeFormatOptions, placeholderDate?: DateValue, isQuiet?: boolean, hideCalendar?: boolean } interface DatePicker extends DatePickerBase, ValueBase<DateValue> {} type DateRange = RangeValue<DateValue>; interface DateRangePicker extends DatePickerBase, ValueBase<DateRange> {} interface CalendarBase { minValue?: DateValue, maxValue?: DateValue, isDisabled?: boolean, isReadOnly?: boolean, autoFocus?: boolean } interface Calendar extends CalendarBase, ValueBase<DateValue> {} interface RangeCalendar extends CalendarBase, ValueBase<DateRange> {}从这段定义可以读出几层设计意图:
DateValue = string | number | Date:统一的日期值类型。允许传入已解析的Date对象、ISO 格式日期字符串或 UNIX 时间戳(秒/毫秒),这与下方迁移表中valueFormat被移除的说明直接呼应——v3 不再用字符串格式描述值,而是要求调用方直接给出可解析的标准日期值。InputBase与ValueBase的继承结构:DatePicker继承InputBase(可输入字段),Calendar则直接继承CalendarBase(纯展示+选择),二者共用ValueBase<DateValue>/ValueBase<DateRange>来声明受控值(value/onChange)与默认值语义。DatePickerBase抽取为基线:DatePicker与DateRangePicker共享minValue/maxValue/formatOptions/placeholderDate/isQuiet/hideCalendar,仅值类型不同(单个DateValue对DateRange)。CalendarBase中的isDisabled/isReadOnly/autoFocus:日历本体不需要输入框的静默模式,但需要独立的禁用、只读与自动聚焦控制。
DatePicker 的 v2 → v3 迁移对照表(完整继承)
原文档 “DatePicker Changes” 一节列出了 v2 属性到 v3 的完整迁移关系,逐条继承如下:
| v2 | v3 | Notes |
|---|---|---|
<Datepicker> | <DatePicker> | |
quiet | isQuiet | |
disabled | isDisabled | |
required | isRequired | |
invalid | validationState="invalid" | |
readOnly | isReadOnly | |
selectionType="range" | <DateRangePicker> | |
placement | - | removed |
displayFormat | formatOptions | use Intl API for internationalized date formatting instead of moment. |
headerFormat | - | removed. |
valueFormat | - | removed. pass a parsed Date object, a date string in ISO format, or a UNIX timestamp. |
min | minValue | |
max | maxValue | |
placeholder | placeholderDate | added |
这些变更背后有三个贯穿性的设计决策,可以从仓库源码得到印证:
- 布尔属性统一加
is前缀:quiet→isQuiet、disabled→isDisabled、readOnly→isReadOnly、required→isRequired,这是 v3 全组件族的命名约定,目的是让 TS 类型与属性语义一目了然。 - 用
validationState取代invalid:二元布尔无法表达 “无错误 / 错误 / 警告” 等状态机,validationState="invalid"是 v3 表单校验的统一入口。在当前实现中,useDatePicker.ts 返回的validationDetails、state.isInvalid就是基于该状态机计算的,errorMessage支持函数签名props.errorMessage(state.displayValidation)(见 useDatePicker.ts L209-L215)。 - 移除
placement/headerFormat/valueFormat,改用Intl与时区无关的标准日期值:placement被移除意味着弹层方向由库内部根据可用空间自动决定(对应当前实现中的shouldFlip属性,默认true,见 DatePicker.tsx L56-L62);displayFormat换成formatOptions?: Intl.DateTimeFormatOptions,即规范注释所说的 “use Intl API for internationalized date formatting instead of moment”——格式化能力交给浏览器原生IntlAPI,随 Provider 的 locale 自动国际化;valueFormat移除后,值只接受解析好的Date、ISO 字符串或 UNIX 时间戳。
Calendar 的 v2 → v3 迁移对照表(完整继承)
原文档 “Calendar Changes” 一节列出了日历组件的迁移关系:
| v2 | v3 | Notes |
|---|---|---|
disabled | isDisabled | |
required | isRequired | |
invalid | validationState="invalid" | |
readOnly | isReadOnly | |
selectionType="range" | <RangeCalendar> | |
headerFormat | - | removed. |
valueFormat | - | removed. pass a parsed Date object, a date string in ISO format, or a UNIX timestamp. |
startDay | - | removed. Start day will be determined based on the locale of thedateFormatter. |
min | minValue | |
max | maxValue |
其中两条值得重点说明:
selectionType="range"拆分为独立组件:v2 用一个枚举属性区分单选/区间,v3 直接拆成<Calendar>与<RangeCalendar>两个组件(对应接口Calendar extends CalendarBase, ValueBase<DateValue>与RangeCalendar extends CalendarBase, ValueBase<DateRange>)。当前仓库中 packages/@react-spectrum/calendar/src/index.ts 正是按此结构对外导出Calendar与RangeCalendar两个组件及SpectrumCalendarProps/SpectrumRangeCalendarProps类型。startDay移除,由 locale 决定每周起始日:规范说明 “Start day will be determined based on the locale of thedateFormatter”。在实现层面,Calendar.tsx 通过useLocale()获取 locale 并传入useCalendarState,由底层日历计算库按 locale 推导星期起始日,因此 v3 中不再需要(也不允许)手动覆盖。
从源码看规范参数的落地:minValue/maxValue/placeholderDate的调用链
规范中的参数并非纸面约定,仓库源码展示了它们如何贯穿 “Spectrum 组件 → react-aria Hook → 日历弹层” 的调用链。
DatePicker 的组件结构:当前 DatePicker.tsx 的注释明确写着 “DatePickers combine a DateField and a Calendar popover to allow users to enter or select a date and time value”——即规范中DatePicker extends DatePickerBase, InputBase与 “日历弹层” 的组合形态:文本输入负责手输日期,按钮(aria-haspopup="dialog")打开日历弹层。
minValue/maxValue的透传路径:在 useDatePicker.ts L205-L206,Hook 将props.minValue、props.maxValue原样注入弹层的calendarProps,与规范中CalendarBase同时声明minValue/maxValue的设计一致——边界约束在输入段(手输时的范围校验)和日历弹层(可选范围)两处同时生效。区间选择器 useDateRangePicker.ts L267-L268 采用完全相同的透传方式,印证了DateRangePicker extends DatePickerBase的共享基线设计。
placeholderDate在 v3 的对应实现:规范新增的placeholderDate(v2 的placeholder升级而来)在当前实现中演化为placeholderValue,作用于两处:输入字段的占位值(useDatePicker.ts L169placeholderValue: props.placeholderValue)以及日历弹层打开且无值时的默认聚焦日(L210defaultFocusedValue: state.dateValue ? undefined : props.placeholderValue)——即空值打开弹层时,焦点落在占位日期上,这正是规范中该属性的交互意图。
isQuiet与hideCalendar的组件形态差异:规范里isQuiet来自DatePickerBase(静默样式由InputBase体系承接),当前 DatePicker.tsx L87 中let {autoFocus, isQuiet, isDisabled, placeholderValue, maxVisibleMonths = 1} = props;可见其参与组件内部渲染分支;而hideCalendar对应的是 “只保留输入框、不渲染日历弹层按钮” 的场景,等价于单独使用 DateField.tsx。此外,v3 实现还新增了规范之外的工程化参数,如maxVisibleMonths(弹层一次显示几个月,默认 1,L50-L56)与createCalendar(自定义日历引擎创建函数,配合@internationalized/date支持多种历法),体现该规范是基线而非封闭清单。
导出结构与使用入口
当前仓库通过两个细粒度包对外暴露规范中的四个组件,均可直接复制使用:
- packages/@react-spectrum/datepicker/src/index.ts:导出
DatePicker、DateRangePicker、TimeField、DateField及SpectrumDatePickerProps、SpectrumDateRangePickerProps等类型; - packages/@react-spectrum/calendar/src/index.ts:导出
Calendar、RangeCalendar及SpectrumCalendarProps、SpectrumRangeCalendarProps类型; - 类型
SpectrumDatePickerProps<T extends DateValue>继承AriaDatePickerProps(并Omit掉isInvalid/validationState/autoComplete以适配 Spectrum 侧的校验接管方式,见 DatePicker.tsx L72-L75),泛型参数即规范中的DateValue约束。
需要说明的是,仓库已经历 v3 之后的持续演进:日期值的类型系统从规范中的string | number | Date进一步统一为@internationalized/date的类型体系(如DateValue/Date/DateOnly/CalendarDate,见 exports/DatePicker.ts 中对DateValue类型的再导出),formatOptions等Intl.DateTimeFormatOptions语义则由底层useDateField/useDateSegment等 Hook(useDateField.ts 等)消费。规范文档记录的是 v3 架构切换时点的接口基线,阅读时应以当前exports/与组件源码中的类型声明为准。
关键设计要点总结
- 组件拆分优于枚举:
selectionType="range"拆为<DateRangePicker>/<RangeCalendar>,让 TS 泛型(ValueBase<DateValue>对ValueBase<DateRange>)能精确约束值类型,消除运行时分支。 - 国际化交给
Intl:displayFormat/headerFormat移除、startDay移除,统一由formatOptions(Intl.DateTimeFormatOptions)与 Provider locale 驱动,替代 moment 的手工格式串。 - 值语义标准化:
valueFormat移除,DateValue只接受Date对象、ISO 字符串或 UNIX 时间戳,从源头避免格式解析歧义。 - 边界与占位参数贯穿输入与弹层:
minValue/maxValue/placeholderDate在输入段校验与日历弹层聚焦中同时生效,useDatePicker/useDateRangePicker源码中的透传路径证明了这一点。 - 命名规范统一:布尔属性
is前缀 +validationState状态机,与仓库其他组件(如 Checkbox 所在 specs 体系中的表单规范)保持同一套约定。
按这份规范迁移 v2 代码时,建议优先处理三件事:把moment的displayFormat/valueFormat字符串替换为Intl的formatOptions与标准日期值;把selectionType="range"的调用点拆为独立组件;再按is前缀与validationState批量重命名布尔属性,即可平滑对齐当前仓库的 v3 及后续 API。
【免费下载链接】react-spectrumA collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.项目地址: https://gitcode.com/GitHub_Trending/re/react-spectrum
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考