ReactUI 组件库设计分析:FineUI 体系的 React 化桥接与双栈共存
AI 摘要基于 react-ui 源码与历史提交,分析其 monorepo 分层、FineUI 适配桥、布局体系复刻与双栈迁移的演进路径。
react-ui 这套仓库很容易被误判成“又一个 React 组件库”。但只要把源码稍微读深一点,就会发现它的真实任务不是从零发明一套新 UI,而是把已有的 FineUI 设计体系、组件语义和一部分运行时能力迁移到 React 世界,同时还要保留与旧栈共存的能力。
这决定了它的很多设计都和普通 React 组件库不一样。
它不是像 Chakra、Mantine 那样先从 React 组件模型出发,再慢慢长出设计系统;也不是像 Ant Design 那样以 React 为唯一宿主重新定义整套 API。react-ui 更像一个“桥接型框架”:
- 上游承接 FineUI 的视觉语言、命名方式和布局思维
- 中间通过
react-adapter做旧运行时到 JSX 的映射 - 下游大量复用
rc-*生态完成 React 时代的基础设施搭建
从工程视角看,这个方向非常务实,也非常有企业软件味。
1. 项目定位与核心约束
从 origin/main 分支的结构看,这个仓库是一个 monorepo,至少包含两个核心包:
packages/
├── react-ui/ React 组件库主体
└── react-adapter/ FineUI / BI 运行时适配层
再结合 packages/react-ui/README.md 和依赖清单,可以看出它的目标并不是做一个通用开源设计系统,而是在以下约束下推进 React 化:
| 约束 | 技术后果 |
|---|---|
| 已有 FineUI 设计体系和大量历史组件认知 | API 命名、布局组件、交互语言需要延续 |
旧系统仍然跑在 BI.createWidget / BI.Widget 体系上 | 需要专门的 react-adapter 做桥接 |
| 新库要提供现代 React 开发体验 | 使用 TypeScript、dumi、father-build、测试和文档体系 |
| 企业组件需求广而杂 | 不能只做 Button / Modal,必须覆盖布局、表格、树、日期、筛选、拖拽等 |
| 迁移成本必须可控 | 大量复用 rc-* 生态,而不是从底层重写 |
这几个约束加在一起,决定了 react-ui 的最优解不是“最纯的 React 设计”,而是“最稳的迁移设计”。
2. 架构分层
这套库的结构相当有辨识度。
2.1 仓库级分层
根目录职责大致如下:
.umirc.ts dumi 文档站配置
.fatherrc.ts monorepo 构建配置
packages/react-ui 组件实现、文档、测试
packages/react-adapter
FineUI ↔ React 适配层
这说明文档站、库构建和适配层是被明确当成一等公民来设计的,而不是散落在脚本里的附属物。
2.2 组件级分层
packages/react-ui/src/components 内部又被拆成三层:
_base 基础原子/组合能力
_case 业务型中间组件
_widget 更接近完整控件的封装
这套分层不是 React 社区常见的 atoms / molecules / organisms 术语,但它和 FineUI 早期的“single / combination / widget”思路是同一脉的。也就是说,这个项目即使迁移到了 React,内部分类哲学仍然强烈继承了 FineUI 体系。
这点非常关键。因为它说明这个库不是简单“换框架”,而是在尽量保留原有心智模型。
2.3 布局层独立存在
另一个很有代表性的目录是 src/react-layout/,里面不是一两个容器组件,而是一整套布局家族:
HorizontalLayoutVerticalLayoutLeftLayoutRightLayoutCenterAdaptLayoutHorizontalAdaptLayoutVerticalTapeLayoutTableLayoutAbsoluteLayoutWindowLayout
这不是普通 React UI 库会做的事情。今天大多数组件库默认把布局问题交给 Flexbox / Grid 和用户自己写 CSS,而 react-ui 明显保留了 FineUI 的核心传统:布局是一等抽象,不是附属样式。
3. 一条真实的演进主线
如果只看当前代码,react-ui 很容易被理解成“一套已经成型的 React 组件库”。但从提交历史看,它其实更像一个分阶段推进的迁移项目:先把组件库生产线搭起来,再接入 FineUI,再逐步 TypeScript 化,最后不断修正与现代 React 生态之间的兼容边界。
3.1 起点不是组件细节,而是先把组件库生产线搭起来
仓库最早一批提交并不是围绕复杂控件,而是围绕文档站、monorepo 和工程脚手架:
c6381b9 chore: 🚀 integrate dumi and monorepo7a8d0b7 chore: 🚀 init project1772768 docs: ✏️ KERNEL-9408 组件文档打包配置hash路由
这说明 react-ui 的起点判断很清楚:它首先要成为一个可发布、可演示、可持续迭代的组件库工程,而不是先零散写几个 React 组件再慢慢收拢。也正因为如此,今天看到的 dumi + father + workspaces + gh-pages 并不是后补,而是项目从起步阶段就很重视的基础设施。
3.2 很快就从“React 组件库”转向“接入 FineUI 体系”
真正决定它后续形态的转折点,是很早就出现的:
cd513dc feature: 集成FineUI
从这之后,历史里反复出现的是对 FineUI 依赖的持续跟进,而不是简单脱钩:
43dda4e 无JIRA任务 chore: 更新依赖的fineui版本8e33d57 无JIRA任务 chore: 升级fineui版本57b22e0 无JIRA任务 chore: 更新fineui/core版本76735ce 无JIRA任务 chore: 更新依赖的fineui版本5dac11e KERNEL-13521 chore: 更新fineui版本
这条线说明 react-ui 从来就不是一个完全独立于 FineUI 的新世界,而是一个长期跟随上游 FineUI 演进、持续对齐语义和能力边界的桥接层。换句话说,它的核心任务不是“摆脱 FineUI”,而是“把 FineUI 的设计母体迁进 React”。
3.3 TypeScript 化和布局复刻是一起推进的
项目中期最明显的一波建设,是组件和布局的连续 TypeScript 化:
4e069b1 feat: ✨ 布局组件Ts化f8bb8ba feat: ✨ Button,Text组件Ts化44de2bb feat: ✨ Radio,Checkbox,Switch 组件Ts化以及编写Api文档7693dd4 feat: ✨ Cascader,DatePane 组件Ts化以及API文档c36ca78 feat: ✨ Tree、TreeSelect 组件Ts化2fb9640 feat: ✨ ColorChooser、Dialog、Confirm、Modal组件Ts化
这里最值得注意的是:布局系统的 TS 化非常靠前。这说明团队当时的重点不是只把单个控件改成 TS,而是优先把 FineUI 最核心的布局哲学一起迁过来。与此同时,像 b69e856 让布局支持ie9 这样的提交又提醒我们,这套布局不是在一个纯现代浏览器环境里设计的,而是在迁移过程中仍然承担着历史兼容压力。
3.4 后期主题是继续跟上 React 生态,而不是只补组件
到了后期,历史里的关键信号开始从“新增组件”转向“修正桥接层与 React 新版本之间的边界”:
3fb9846 KERNEL-11366 fix: ReactDom.render在React18下报错5995e62 KERNEL-11366 fix: newInstance在React18中的回调表现不一致,优化代码将notice放在回调中执行
这说明项目后期真正难的地方已经不是“有没有组件”,而是“这套迁移式运行时还能不能继续跟上 React 自身的演进”。今天看到的 react-adapter、newInstance、全局通知这类代码之所以显得偏重,本质上就是历史桥接方案在新 React 语义下持续被检验的结果。
所以如果把 react-ui 看成一个静态组件库,很容易误判它为什么会同时有 React 原生组件、FineUI 语义包装、react-adapter 和大量上游版本同步;但如果把它看成一个长期迁移工程,这些设计反而是合理的。
4. 最核心的设计决策:不是重写 FineUI,而是桥接 FineUI
如果说这套仓库最值得分析的点是什么,我会把答案放在 packages/react-adapter。
很多团队做“React 化”时,口号是全部重写。但 react-ui 的做法不是推翻旧世界,而是在旧世界和新世界之间做一层转接。
这体现在三个地方。
4.1 重写 BI.createWidget
react-adapter/src/index.js 里直接接管了 BI.createWidget 的行为。逻辑大致是:
- 如果不在 React 环境中,继续走原始 FineUI 的
createWidget - 如果在 React 环境中,就把 widget 配置转成一个轻量对象
- 把这些配置挂到
element.$foundation上,供 React 渲染阶段消费
这是一种非常激进、但很实用的设计。
它的本质不是“把 FineUI 编译成 React”,而是“让 FineUI 的 widget 描述在 React 环境里拥有一种可解释的中间形态”。
这带来两个直接好处:
- 老的
type: 'bi.xxx'配置系统不需要立即报废 - 组件内部仍然可以复用 FineUI 时代的配置和语义
但代价也很明显:
- 全局猴子补丁会让运行时边界变脆
- React 环境与非 React 环境共享同一套全局
BI命名空间,调试复杂度会上升 - 适配层一旦出问题,影响范围不是单个组件,而是整个渲染协议
换句话说,这是典型的迁移期架构,不是最终理想形态。
4.2 重写 React.createElement
更“狠”的一点在于,react-adapter/src/core/shortcut.js 还拦截了 React.createElement。
当组件类型在 ComponentsTypeMap 中命中时,它会:
- 根据
xtype取到真实组件 - 运行
configMap[type]做配置重写 - 再转发给原始的
React.createElement
这套机制的作用非常像 FineUI 里的 shortcut + config:
shortcut(type, Component)建立字符串类型到组件的映射config(type, configFn)可以全局改写该组件的 props
这其实把 FineUI 时代的“字符串注册 + 全局配置”思想原封不动地搬进了 React。
优点:
- 和 FineUI 历史心智一致
- 全局约束和默认配置非常方便
- 适合企业内部统一规范和组件定制
缺点:
- 破坏了 React “组件就是函数/类”的朴素直觉
- 某些行为变成运行时隐式改写,不够显式
- 未来若升级到更严格的编译优化或 React Compiler 体系,这种做法天然不友好
所以这套方案不是“现代 React 最佳实践”,但它是“从 FineUI 平滑迁移到 React 的最佳妥协”。
4.3 自定义 Element,模拟旧世界 DOM 抽象
react-adapter/src/element/element.js 里定义了一个自制 Element,带有:
classMapstyleschildrenappendChildgetParentgetChildren
它明显不是浏览器真实 DOM,而是一个兼容层抽象。这个 Element 的意义在于:让 FineUI 原本围绕 element 对象的组织方式,在 React 世界里仍然有落点。
这一步说明作者并没有试图强行让 React 吞掉所有旧概念,而是承认旧概念仍然有价值,于是把它们转译成 React 可以消费的中间结构。
这是成熟迁移系统常见的做法。
5. 布局系统:React 外壳下,仍然是 FineUI 的灵魂
很多人第一次看 react-ui 会先注意 Button、Drawer、Table,但我认为最能说明这个库“出身”的,反而是布局模块。
react-layout/Layout.tsx 并不是一个普通容器,它把大量 FineUI 传统属性保留了下来:
lgap / rgap / tgap / bgap_lgap / _rgap / _tgap / _bgaphgap / vgapscrollx / scrolly / scrollablehorizontalAlign / verticalAlignwidth / height的像素比换算
这里的设计理念很清楚:
- 布局要对业务开发者保持高层语义,而不是只暴露 CSS
- 旧体系里那些“带 gap 的布局组件”“左右/上下自适应布局”的表达方式要继续有效
- 像素精度、缩放比和组件尺寸的一致性要由框架接管一部分
这和现代 React 社区的主流风格是不同的。主流方案通常会说:
- 布局交给 CSS
- 组件库只负责控件
- 设计系统通过 token 和 utility class 约束
而 react-ui 的判断是:在企业中后台里,布局本身也是组件契约的一部分,不能完全下放给业务层。
这个判断是有业务依据的。因为中后台页面的复杂度很大一部分确实来自布局组合,而不是单个控件。
6. 组件实现策略:双轨并行
继续往下看组件实现,会发现 react-ui 不是单一路线,而是明显采用了双轨策略。
6.1 一条轨道:FineUI 语义组件的 React 包装
比如基础按钮 Button.tsx 的典型流程是:
- 先通过
createWidget生成一个bi.button对应的 widget 描述 - 再通过
createJSX把这个 widget 转成 React 节点 - 补上 React 世界需要的键盘事件等能力
这意味着:
- 组件外观和配置风格仍然贴近 FineUI
- 很多基础部件仍然在沿用旧体系的语义
- React 更多承担“视图壳”和“生态接入器”的角色
6.2 另一条轨道:基于 rc-* 生态的现代封装
另一方面,复杂控件又大量直接站在 rc-* 上:
Table基于rc-tableTabs基于rc-tabsDrawer基于rc-drawerMenu基于rc-menuTree/TreeSelect基于rc-treeTooltip/Combo基于rc-tooltip/rc-trigger
这说明作者并不执着于“所有东西都必须自己实现”。只要 React 生态已经有成熟基础设施,就优先复用,再叠加 FineUI 的视觉与交互语义。
这是一个非常理性的技术路线。
如果不这么做,这个库要同时完成:
- React 化迁移
- 组件重写
- 设计语言统一
- 复杂控件底层能力自研
那几乎不可能按合理成本落地。
7. rc-* 复用不是简单套皮,而是语义重组
这里有必要强调一点:react-ui 虽然大量复用了 rc-*,但它并不是“套个皮就完了”。
以 Drawer 为例,代码里可以看到:
- 继续使用 FineUI 的布局组件
HorizontalTapeLayout - 头部、关闭按钮、标题区都套入 FineUI 的类名体系
push、placement、size这些 API 又参考了 Ant Design / rc-drawer 的现代习惯
这说明它实际上在做两件事:
- 把 React 生态成熟能力拿来用
- 再重新包成符合 FineUI 视觉和中后台习惯的组件
因此它不是简单 copy antd,也不是简单延续 FineUI,而是在两者之间做了工程拼装。
这类库的好坏,恰恰取决于拼装是否自洽。从目前结构看,react-ui 至少在架构意图上是清晰的。
8. 文档、测试和构建:这不是实验项目,而是可发布产品
从仓库基础设施看,这个项目并不是个人玩具。
8.1 文档站是主交付面
根目录使用 dumi,gh-pages 分支则直接存放文档站静态产物。这意味着组件文档不是“有空再补”,而是发布链路的一部分。
这点很重要。因为对于企业组件库来说,文档本身就是产品能力的一部分。
8.2 monorepo + workspaces + lerna
根目录通过 Yarn workspaces 管理包,配合 lerna 做多包协同。再结合 father-build 输出 esm / cjs,说明这套库的发布形态已经标准化:
- 开发用 dumi
- 构建用 father
- 类型声明单独生成
- 文档与组件包分开交付
这是一条非常典型的中型组件库生产线。
8.3 测试和 Demo 覆盖不低
源码里大量组件目录都带有:
__tests__/index.test.tsx__snapshots__/docs/<component>/demos/*.tsx
这说明作者不是只追求“页面能跑起来”,而是已经把组件的行为稳定性和文档演示都纳入了维护体系。
当然,它还不是像 Material UI 那样高度工业化,但对于企业内部演进型组件库来说,这个完成度已经不低。
9. 它和 Ant Design、FineUI 分别像什么
要理解 react-ui,一个最有效的方式就是看它站在谁的中间。
9.1 它不像 Ant Design 的地方
- 它不是从 React 世界原生长出来的
- 它不把布局交给用户自己写 CSS
- 它保留了大量 FineUI 风格的字符串类型、布局语义和运行时适配思想
9.2 它不像老 FineUI 的地方
- 它已经是标准 React + TypeScript + dumi + father 的工程体系
- 大量复杂控件不再靠自研底层支撑,而是站在 rc 生态上
- 文档、类型、测试、npm 包发布都更符合现代前端协作方式
9.3 它真正像什么
如果一定要下一个定义,我会说它更像:
“以 FineUI 为设计母体、以 React 为宿主、以 rc 生态为基础设施的迁移型组件平台。”
这个定义比“React 版 FineUI”更准确,因为它强调的不是简单翻译,而是三套体系之间的折中。
10. 这套设计最有价值的地方
10.1 它承认迁移是长期共存,而不是瞬间切换
源码里最聪明的地方,不是某个单一组件写得多漂亮,而是整个项目从一开始就默认:
- FineUI 不会立刻消失
- React 也不能只做薄薄一层皮
- 新旧系统必须在一段时间内共存
这才是大型前端架构迁移的真实世界。
10.2 它把迁移成本压到了可管理范围
通过 react-adapter 和 rc-* 复用,项目避免了两个最昂贵的坑:
- 全量重写 FineUI 组件体系
- 全量自研 React 复杂控件底层
这使它能够把研发预算集中在真正有价值的事情上:API 统一、视觉延续、业务场景落地。
10.3 它的设计哲学是连续的
很多迁移项目的失败,不是因为技术不够新,而是因为前后两代系统心智断裂。react-ui 虽然用了 React,但从布局、命名、分层到适配方式,都能明显看出它在尽量延续 FineUI 的认知习惯。
这对组织协作非常重要。
11. 它的问题也很明显
一个成熟评价必须承认这套设计的代价。
11.1 运行时桥接过重
拦截 BI.createWidget、重写 React.createElement、维护自定义 Element 抽象,这些都意味着系统存在较强的运行时魔法。
短期看,这很高效;长期看,会带来:
- 可读性下降
- 调试路径变长
- 新同学理解成本高
- 与未来 React 生态演进的耦合风险增加
11.2 它还没有彻底摆脱历史包袱
依赖上仍然能看到一些明确的时代痕迹:
dumi 1father-build 1react 17IE11兼容目标
这些并不代表项目不好,但说明它的工程基线仍然处在一个“兼容优先”的历史区间,而不是“现代能力优先”的区间。
11.3 双轨实现会带来一致性压力
当一部分组件更偏 FineUI 包装,另一部分组件更偏 rc-* 封装时,团队必须持续处理几个问题:
- API 风格是否一致
- 事件命名是否一致
- className / 样式约定是否一致
- 受控 / 非受控语义是否一致
这类问题不会在第一版暴露得很尖锐,但随着组件数增长,一定会成为维护重点。
12. 如果今天继续演进,我会优先做什么
如果以“不破坏现有迁移成果”为前提,我会优先做四件事。
12.1 把 react-adapter 从“魔法层”逐步收敛成“显式桥”
这不是说立刻删掉适配层,而是要逐步减少对全局补丁的依赖,把:
- 注册机制
- 配置改写
- widget 到 JSX 的转换
从隐式运行时行为,慢慢收敛成更显式的 API。
12.2 把布局系统和控件系统进一步解耦
现在布局模块非常强势,这是历史决定的;但长期看,布局系统越独立,越容易被现代 CSS 能力替代或部分替代,也越利于组件库向更开放的使用方式演化。
12.3 系统性统一“FineUI 包装组件”和“rc 封装组件”的 API 语法
这一步做得越早,后面债越少。组件库最怕的不是技术栈混搭,而是用户感觉同一个库里住着两套语言。
12.4 升级工具链,但不要急着推翻分类体系
工具链升级值得做,尤其是 React 版本、文档基建、构建体系和测试体验。但 _base / _case / _widget 这种分类体系未必需要急着换掉,因为它本身就是组织经验的一部分。
13. 总结
react-ui 的真正价值,不是“把 FineUI 用 React 重写了一遍”,而是它非常清楚迁移项目最难的从来不是写组件,而是处理新旧体系之间的连续性。
它做的最重要的事情有三件:
- 保留 FineUI 的设计语言和布局哲学
- 用
react-adapter把旧运行时接到 React 生态上 - 用
rc-*复用现代复杂控件基础设施,避免重复造轮子
这使它既不像传统 FineUI,也不像纯正的 Ant Design 风格组件库,而是一个很典型、也很有现实意义的企业前端迁移产物。
如果只从“纯度”评价,它并不完美;但如果从“组织迁移成本、业务连续性、工程可落地性”评价,这套设计其实相当聪明。
它不是最理想化的 React UI 框架,却很可能是当时约束下最正确的 React UI 框架。