项目:react-ui | 版本:v1.0 | 生成日期:2026-10-06 | 依据:源码分析
三个可发布包的 exports 字段构成完整对外契约(以 packages/ui/package.json 为准):
| 包 | exports 子路径 | 指向 | 说明 |
|---|---|---|---|
@xiaoye-react/ui | "." | types: es/index.d.ts、import: es/index.js、require: es/index.cjs | 唯一运行时入口,ESM/CJS 双份 |
@xiaoye-react/ui | "./style.css" | es/style.css | 全量组件样式(CSS Modules 编译产物 + --ui-* 变量定义) |
@xiaoye-react/ui | "./package.json" | package.json | 便于工具读取版本 |
@xiaoye-react/hooks | "." | 同上三条件 | 无样式文件(sideEffects: false) |
@xiaoye-react/pro | "." / "./style.css" | es/index.js(ESM,require 也指向同文件,纯 ESM 包) / es/style.css | 纯 ESM;README 强调其具名导出靠消费者侧复验兜底 |
files 字段三包一致为 ["es"]——发出去的就是构建目录。入口文件 packages/ui/src/index.ts 的结构值得逐行理解:
'use client' // ESM 产物由 vite banner 保留(Next.js RSC 承诺)
export { RemoveScroll } from 'react-remove-scroll' // 转发滚动锁定工具
export * from './components' // 129 个组件目录
export * from './core' // Box/factory/styles-api/UIProvider/store/utils
export * from './dates' // 日期家族
export * from './schedule' // 日程
export * from './form' // useForm 体系
export * from './emotion' // 可选 CSS-in-JS 适配层
export * from './colors-generator' // 色板生成
// —— 星导出歧义消解(源码注释:ESM 规范中多个星导出的歧义名会被整体丢弃,调用即崩)——
export { getStartOfWeek, getEndOfWeek, getMonthDays, isSameMonth } from './schedule';
export type { DateStringValue, DateTimeStringValue, DateLabelFormat, DayOfWeek } from './dates';
export type { CSSProperties } from './core';
export { toDateString } from './dates';入口注释明确记录了一个真实事故模型:dates(components/Month)与 schedule(utils)存在同名星导出,按 ESM 规范歧义名会被整体静默丢弃(产物中该导出直接消失),因此用优先级更高的显式导出消歧——getStartOfWeek 等 4 个函数取 schedule 版(对象参数形态),toDateString 取 dates 版(toDateString(new Date(...)) === 'YYYY-MM-DD',schedule 版会带 00:00:00)。这组显式导出同时锁定了两个模块的函数签名约定。
导出契约的守护机制:根脚本 pnpm check:exports(scripts/validate-package-exports.mjs)校验每个包 exports 指向真实文件、公开面没有「只有类型没有运行时」的悬空导出;ui 包构建后还有 check:published-types——用 tsc --noEmit --skipLibCheck false 对产物 es/index.d.ts 独立编译,保证发布出去的类型文件可被消费。
从 @xiaoye-react/ui 导出的类型可归为五层,全部可在源码定位:
| 类型 | 出处 | 语义 |
|---|---|---|
BoxProps / BoxComponentProps | core/Box/Box.tsx | 一切组件的属性基类:className/style/__vars/__size/hiddenFrom/visibleFrom/lightHidden/darkHidden/mod |
Mod / BoxMod | 同上 | Record<string, any> | string 及其数组嵌套,假值剔除后转 data 属性 |
UIStyleProps | core/Box/style-props/ | Box 的 style props 集(m/p/px/py/bg/fw 等缩写,经 extractStyleProps 抽取) |
CssVarsProp / UIStyleProp | core/Box/Box.types.ts | __vars 与可订阅主题的 style 类型 |
PolymorphicComponentProps<C, Props> / PolymorphicRef<C> | core/factory/create-polymorphic-component.ts | 多态组件属性与 ref 约束(排除 string ref) |
Factory / PolymorphicFactory<Payload> / FactoryPayload | core/factory/factory.tsx、polymorphic-factory.tsx | 工厂载荷:props/ctx/ref/stylesNames/vars/variant/staticComponents/compound/signature |
StylesApiProps<Factory> | core/styles-api/styles-api.types.ts | classNames/styles/vars/varsResolver/unstyled/attributes 六件套 |
Styles / ClassNames / ClassNamesArray / Attributes / GetStylesApiOptions | 同上 | 选择器化样式定制的载荷类型(支持对象或 (theme, props, ctx) => … 函数形态) |
VarsResolver / PartialVarsResolver | core/styles-api/create-vars-resolver/ | (theme, props, ctx) => Record<selector, Record<cssVar, value>> |
UITheme / UIThemeOverride | core/UIProvider/theme.types.ts | 主题全量/覆盖类型(focusRing/scale/colors/primaryShade/variantColorResolver/headings/radius/spacing/fontSizes/breakpoints/shadows…) |
UIColorScheme / UIStylesTransform | core/UIProvider/theme.types.ts、UI.context.ts | 'light' | 'dark' | 'auto';样式转换器接口({ sx, styles }) |
UIProviderProps | core/UIProvider/UIProvider.tsx | Provider 全部 11 个配置项 |
UIStore<Value> / createStore / useStore | core/store/store.ts | 外部 store 契约(getState/setState/updateState/initialize/subscribe) |
CSSProperties(再导出) | src/index.ts ← core | 库内统一样式对象类型 |
ElementProps<ElementType, PropsToOmit> | core/Box/Box.tsx | Omit<ComponentPropsWithoutRef<E>, 'style' | PropsToOmit>,组件声明原生属性透传的标准写法 |
每个组件经 export namespace Xxx 暴露:Props、StylesNames、CssVariables、Factory、(可选)Variant/Size/Column 等。例如 Button.Props、Button.StylesNames、DataTable.Column<T>、DataTable.Props<T>。配合静态成员 Xxx.classes(CSS Modules 映射)与 Xxx.varsResolver,二次封装方可以不碰源码完成「类型级镜像」。
DataTableColumn<T>、DataTableAccessor<T>、DataTableSortStatus、DataTableSortDirection、DataTableSelectionMode、DataTableStylesNames、DataTableCssVariables(packages/ui/src/components/DataTable/types.ts);SelectData、SelectItem、SelectGroupData、SelectRenderOption(components/Select/Select.tsx);NotificationData、NotificationPosition、NotificationsState(components/Notifications/notifications.store.ts);ProTableRequestParams({ page, pageSize, search?, sortStatus? })、ProTableRequestResult<T>({ records, total })、ProTableProps<T>(packages/@xiaoye-react/pro/src/pro-table/ProTable.tsx);DatePickerProps<Type>、DatePickerPreset<Type>、DateStringValue、DateValue、DatePickerType(src/dates/);use-form.ts / types.ts 导出表单句柄与校验类型;schema 校验经 schema-resolver.ts 支持 Standard Schema(@standard-schema/spec)。{classNamesPrefix}-{组件名}-{选择器},如 ui-Button-root(get-static-class-names 生成)。它面向调试与 e2e 定位(withStaticClasses: false 可全局关闭以减体积),不是样式覆盖契约——CSS Modules 哈希类才是真正的样式载体;data-variant、尺寸 data-size(数字 size 不写,见 Box.tsx 的 isNumberLike 判断)、状态由 mod 翻译成 data-*(如 Button 的 data-loading、DataTable 的 data-selected/data-odd/data-sticky/data-sticky-last);CSS Modules 内用属性选择器写状态样式;classNames={{ root: 'my-x' }}(支持数组形态 ClassNamesArray 与函数形态)注入到对应选择器。<Card
styles={(theme) => ({ root: { padding: theme.spacing.xl } })}
vars={{ root: { '--button-height': '48px' } }}
/>styles 可为对象或 (theme, props, ctx) => …(StylesApiRecord 联合类型),inline style 直接合并到选择器节点;vars 是 CSS 变量覆盖,键受组件的 XxxCssVariables 白名单约束(如 Button 只接受 --button-justify/--button-height/…/--button-bd 10 个);compound: true 的 Payload)不能在 UIProvider 主题上配置 classNames/styles/vars,只能在子件自身上传(factory.tsx 注释)。UIProvider theme={{ ... }}:UIThemeOverride 深合并进当前主题(mergeUITheme:显式 undefined 按未配置处理、不污染父主题——merge-ui-theme.test.ts 用 5 个用例固化了这些语义);theme.components: { [组件名]: { defaultProps, classNames, styles, vars } }:全局改某组件默认 props 与样式(useProps 读取,支持函数形态 (theme) => props);cssVariablesResolver:整体接管 --ui-* 变量生成(默认 defaultCssVariablesResolver,产出 z-index 分层、5 套尺寸刻度、明暗两套语义色等,见 core/UIProvider/UICssVariables/default-css-variables-resolver.ts);stylesTransform:换样式引擎,官方提供 emotionTransform(src/emotion/emotion-transform.ts,含 useSxTransform/useEmotionStylesTransform 两个钩子式转换器);headless: true:无头模式,去掉全部库样式(getClassName/getStyle 里 unstyled || headless 双触发点)。UIProvider 的 colorScheme/localStorageKey(默认 ui-color-scheme-value)与 SSR 组件 ColorSchemeScript(core/UIProvider/ColorSchemeScript/)构成防 FOUC 契约:Script 生成的内联脚本读 localStorage → 处理 auto → 直接在 HTML 解析期写 data-ui-color-scheme;两者的 localStorageKey 必须一致(UIProviderProps 的 JSDoc 显式要求)。脚本属性带 data-ui-script 标记,支持去重。
除类型定义外,全库 props 遵循一套稳定到可以「猜」的命名语义,这本身也是接口设计的一部分:
value + defaultValue + onChange 是受控/非受控双模的标准签名;凡是带 finalValue 兜底的组件(DataTable 五组状态、Tabs、Select),非受控且未传 defaultValue 时落到 finalValue。布尔开合类组件另有 opened/onClose 约定(Modal 系);onXxxChange(onSortStatusChange、onSelectedKeysChange、onHiddenColumnKeysChange),单一事件用 onXxx(onRowClick、onPageChange);进出场结束回调精确到 onEnterTransitionEnd/onExitTransitionEnd;variant(视觉变体)、size(尺寸刻度)、radius(圆角)、color(主题色键或任意 CSS 颜色)、gradient(渐变)出现在一切有视觉的组件上,且默认值都指向主题(theme.primaryColor、theme.defaultRadius);主题键或 CSS 值通吃的参数在 JSDoc 里统一写作「主题 xx 键或任意有效 CSS 值」;withXxx 表达「带不带某个部件」(withOverlay、withCloseButton、withTableBorder、withColumnSettings),keepXxx 表达「不卸载」(keepMounted),onXxx 前的布尔条件用 closeOnEscape/closeOnClickOutside/highlightOnHover/striped 这类动宾或形容词直陈;leftSection/rightSection(Button),自定渲染是 render/renderXxx 函数族(DataTableColumn.render、renderExpanded、renderOption),透传属性是 xxxProps(loaderProps、overlayProps、portalProps、removeScrollProps、thProps/tdProps);__vars/__size、ModalBase 的 __staticSelector、DatePicker 的 __onPresetSelect)是库内部使用的「半私有」接口——公开可见但不承诺兼容,消费者不应依赖。这套约定的价值在于:新组件的 API 在 review 时有一把现成的尺子,消费者在组件之间迁移时肌肉记忆可以复用——这也是文档站能用脚本(lint-demo-controls.mjs)核对 demo 控制项 prop 名的前提。
库把「运行时错误消息」也当作对外接口维护,全部为中文并携带包名前缀,可直接用于问题定位:
| 场景 | 错误消息 | 抛出位置 |
|---|---|---|
| 组件外缺 Provider | @xiaoye-react/ui: UIProvider was not found in component tree, make sure you have it in your app | core/UIProvider/UIThemeProvider/ 的 useUITheme() |
| 上下文缺 UIProvider | [@xiaoye-react/ui] UIProvider was not found in tree | core/UIProvider/UI.context.ts 的 useUIContext() |
| 复合组件拆散使用 | 'Tabs component was not found in the tree'(同名模式适用于 Combobox/ModalBase 等所有 createSafeContext 用户) | 各组件 *.context.ts |
| 开发态排序警告 | [@xiaoye-react/ui] DataTable: columns with a function accessor cannot be sorted locally. … | DataTable.tsx 的 handleSortClick(NODE_ENV !== 'production' 时) |
createSafeContext 的模式是「错误消息在创建 context 时声明一次」,因此每个复合组件的错误文案与其文件放在一起、随组件演进同 PR 更新。
@xiaoye-react/hooks(83 个 hooks)hooks 包的公共面就是 packages/hooks/src/index.ts 的星导出,每个 hook 独立目录、独立单测。按消费频率挑重点:交互类(use-click-outside、use-hotkeys、use-focus-trap、use-focus-return、use-long-press、use-drag/use-move/use-radial-move)、状态原语类(use-uncontrolled、use-disclosure、use-toggle、use-list-state、use-map、use-queue、use-set-state、use-state-history、use-pagination)、时序类(use-debounced-callback/use-debounced-state/use-debounced-value、use-throttled-* 三件、use-interval、use-timeout、use-effect-event、use-did-update)、观测类(use-intersection、use-in-viewport、use-resize-observer、use-mutation-observer、use-viewport-size、use-scroll-into-view、use-scroll-spy)、浏览器环境类(use-media-query、use-color-scheme、use-network、use-os、use-idle、use-fullscreen、use-clipboard、use-local-storage/use-session-storage、use-eye-dropper、use-favicon、use-document-title)、工程基建类(use-id、use-merged-ref、use-callback-ref、use-isomorphic-effect、use-force-update、use-mounted、use-is-first-render、use-mask、use-splitter、use-floating-window、use-roving-index、use-selection)。接口设计上与 ui 同源:useUncontrolled 的四件套签名、稳定 setter 纪律、中文 JSDoc(含反例)全部一致。
@xiaoye-react/pro(三个页面模式)pro 的公共面极小而稳定:PageContainer(页头/面包屑/内容容器)、SearchFilter(声明式查询表单区,SearchFilterProps 含字段配置与 onSearch/onReset)、ProTable(request 驱动的表格页模式,actionsRef.refresh() 命令式刷新)。类型出口有 ProTableRequestParams/ProTableRequestResult 等。包内每个组件带 *.test.tsx(17 个单测,ADMIN-ROADMAP 记录),style.css 子路径提供三个组件的样式。
除 129 个 components 目录外,入口还导出六个子模块的公共面:core(Box/factory/styles-api/UIProvider/store/utils 全部类型与工具——二次封装者的一级 API)、dates(组件 + DatesProvider + 类型 + toDateString 等工具)、schedule(AgendaView 及日历工具函数)、form(useForm/useField/FormProvider/validators/actions/schema-resolver)、emotion(UIEmotionProvider/createStyles/emotionTransform/Global/mergeSx)、colors-generator(chroma-js 色板生成,generate-colors.ts)。另有两条「再导出」:RemoveScroll(转发 react-remove-scroll,让消费者不必直接依赖)与 CSSProperties 类型(统一样式对象类型出口)。
| 维度 | 约定 | 佐证 |
|---|---|---|
| 版本策略 | 三包版本独立手工管理(ui 3.0.1 / hooks 1.4.1 / pro 0.2.2),不用 changesets | 根 README「版本号手工改三个 package.json」 |
| React 兼容 | ui/pro 仅 React 19(peer ^19.0.0/^19.1.1);hooks 兼容 18/19 | 各包 peerDependencies |
| hooks 下限 | ui peer @xiaoye-react/hooks ^1.4.0,因产物引用其 1.4.0 的 attachMediaListener;留 ^1.0.0 会在 ESM 下报错 | 根 README「发布相关的坑」 |
| workspace 协议 | pro peer 是 workspace:^,只有 pnpm publish 会重写;用裸 npm publish 直接坏 | 同上 |
| 模块格式 | ESM(module/import)+ CJS(main/require)双份,preserveModules 保留可摇树结构 | 各包 package.json + vite.config.ts |
| 'use client' | ESM 产物每个模块顶部带 banner;CJS 不带 | packages/ui/vite.config.ts 注释 |
| CJS/ESM 双格式依赖消费 | 对 rrule 这类双入口依赖,产物写成「命名空间取值、取不到回退 default(计算键)」 | 根 README 与入口相关注释 |
| 发布校验 | prepublishOnly = build +(ui 独有)check:exports;release:check 显式补跑三包测试;消费者侧 ESM 复验(含 pro 具名导出检查) | 根 package.json scripts、README 发布流程 |
| 回滚窗口 | npm 72 小时内可 unpublish,之后只能 deprecate | 根 README |
| 弃用与迁移 | 文档站提供 MIGRATION-ANTD-TO-REACTUI.md、CHANGELOG.zh-CN.md(apps/docs 下) | apps/docs/ 目录 |
这套仓库把「接口不漂移」做成了门禁,而非口头约定:
pnpm docs:lint(apps/docs/scripts/ 三个脚本):lint-docs-imports.mjs 核对文档导入、lint-demo-controls.mjs 核对 demo 控制项引用的 prop 名、lint-docs-api-tables.mjs 逐行核对文档 API 表与类型定义——API 表的每一行必须能在类型里找到,跨包继承成员依赖 hooks 构建产物类型(CONTRIBUTING 两条环境教训即源于此);pnpm check:exports 必须在 build 之后跑,防止「文档/代码导出了但产物里不存在」;pnpm typecheck 三套配置(ui 产物类型、hooks、docs demos)把示例代码也纳入类型检查(apps/docs/tsconfig.demos.json);merge-ui-theme.test.ts 固化主题合并语义、DataTable.test.tsx 固化受控排序语义、ProTable 测试固化「切换 request 即重请求」语义——CONTRIBUTING 的裁决原则「测试显式编码的行为 > 审查者直觉」把这些测试升级为接口契约。UITheme 的完整键位即主题定制的能力边界,逐键列出(出处 core/UIProvider/theme.types.ts 与 default-theme.ts):focusRing('auto' | 'never' | 'always',键盘焦点环策略)、scale(rem 缩放,适配自定义根字号)、fontSmoothing(body 字体平滑)、white/black(两极色)、colors(命名词 ×10 阶色板,DEFAULT_COLORS 内置全色系)、primaryShade(数字或 { light, dark },默认 { light: 6, dark: 8 })、primaryColor(默认 'blue')、variantColorResolver(variant→色解析策略函数,可整体替换实现品牌化)、autoContrast 与 luminanceThreshold(按背景亮度自动反色的开关与阈值,默认 0.3)、fontFamily/fontFamilyMonospace、headings(fontFamily/fontWeight/textWrap/sizes 六级)、radius(xs–xl 五档,默认 sm)、spacing(xs–xl 五档)、fontSizes(xs–xl)、lineHeights(xs–xl)、breakpoints(em 单位五档)、shadows(阴影刻度)、defaultGradient(默认 { from: 'blue', to: 'cyan', deg: 45 })、respectReducedMotion(尊重系统减少动画)、cursorType、activeClassName/focusClassName,以及 components(每组件的 defaultProps/classNames/styles/vars 覆盖,ExtendsRootComponent 形状)。理解键位分布有助于判断「某个定制诉求该走 theme 还是走组件级 vars」:全局视觉语言改 theme,单点微调走 vars/styles。同时注意 mergeUITheme 对这些键的合并语义是深合并且「显式 undefined 按未配置处理」,覆盖行为已被 merge-ui-theme.test.ts 的用例逐条固化,依赖这些语义是安全的。反过来,直接修改 DEFAULT_THEME 对象不是受支持的定制方式——合并可能就地污染默认主题,这一历史缺陷正是当初补测试的动因。
// 1) 应用根组件
import '@xiaoye-react/ui/style.css';
import { UIProvider, ColorSchemeScript } from '@xiaoye-react/ui';
// SSR(Next.js App Router 的 <head> 中):
<ColorSchemeScript defaultColorScheme="auto" />
// html 标签建议 suppressHydrationWarning(color scheme 由脚本先行写入 data 属性)
<UIProvider theme={{ primaryColor: 'blue' }} colorScheme="auto">
<App />
</UIProvider>
// 2) 局部换主题
<UIProvider theme={{ primaryColor: 'grape' }}>…</UIProvider> // 嵌套继承外层色彩方案
// 3) 组件级样式定制
<Button classNames={{ root: 'cta' }} styles={{ root: { height: 52 } }} vars={{ root: { '--button-radius': '24px' } }} />
// 4) 命令式能力
notifications.show({ message: '已保存' });
// Modals 包提供命令式确认框(components/Modals/)