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/,里面不是一两个容器组件,而是一整套布局家族:

  • HorizontalLayout
  • VerticalLayout
  • LeftLayout
  • RightLayout
  • CenterAdaptLayout
  • HorizontalAdaptLayout
  • VerticalTapeLayout
  • TableLayout
  • AbsoluteLayout
  • WindowLayout

这不是普通 React UI 库会做的事情。今天大多数组件库默认把布局问题交给 Flexbox / Grid 和用户自己写 CSS,而 react-ui 明显保留了 FineUI 的核心传统:布局是一等抽象,不是附属样式。

3. 一条真实的演进主线

如果只看当前代码,react-ui 很容易被理解成“一套已经成型的 React 组件库”。但从提交历史看,它其实更像一个分阶段推进的迁移项目:先把组件库生产线搭起来,再接入 FineUI,再逐步 TypeScript 化,最后不断修正与现代 React 生态之间的兼容边界。

3.1 起点不是组件细节,而是先把组件库生产线搭起来

仓库最早一批提交并不是围绕复杂控件,而是围绕文档站、monorepo 和工程脚手架:

  • c6381b9 chore: 🚀 integrate dumi and monorepo
  • 7a8d0b7 chore: 🚀 init project
  • 1772768 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-adapternewInstance、全局通知这类代码之所以显得偏重,本质上就是历史桥接方案在新 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 的行为。逻辑大致是:

  1. 如果不在 React 环境中,继续走原始 FineUI 的 createWidget
  2. 如果在 React 环境中,就把 widget 配置转成一个轻量对象
  3. 把这些配置挂到 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,带有:

  • classMap
  • styles
  • children
  • appendChild
  • getParent
  • getChildren

它明显不是浏览器真实 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 / _bgap
  • hgap / vgap
  • scrollx / scrolly / scrollable
  • horizontalAlign / verticalAlign
  • width / height 的像素比换算

这里的设计理念很清楚:

  1. 布局要对业务开发者保持高层语义,而不是只暴露 CSS
  2. 旧体系里那些“带 gap 的布局组件”“左右/上下自适应布局”的表达方式要继续有效
  3. 像素精度、缩放比和组件尺寸的一致性要由框架接管一部分

这和现代 React 社区的主流风格是不同的。主流方案通常会说:

  • 布局交给 CSS
  • 组件库只负责控件
  • 设计系统通过 token 和 utility class 约束

react-ui 的判断是:在企业中后台里,布局本身也是组件契约的一部分,不能完全下放给业务层。

这个判断是有业务依据的。因为中后台页面的复杂度很大一部分确实来自布局组合,而不是单个控件。

6. 组件实现策略:双轨并行

继续往下看组件实现,会发现 react-ui 不是单一路线,而是明显采用了双轨策略。

6.1 一条轨道:FineUI 语义组件的 React 包装

比如基础按钮 Button.tsx 的典型流程是:

  1. 先通过 createWidget 生成一个 bi.button 对应的 widget 描述
  2. 再通过 createJSX 把这个 widget 转成 React 节点
  3. 补上 React 世界需要的键盘事件等能力

这意味着:

  • 组件外观和配置风格仍然贴近 FineUI
  • 很多基础部件仍然在沿用旧体系的语义
  • React 更多承担“视图壳”和“生态接入器”的角色

6.2 另一条轨道:基于 rc-* 生态的现代封装

另一方面,复杂控件又大量直接站在 rc-* 上:

  • Table 基于 rc-table
  • Tabs 基于 rc-tabs
  • Drawer 基于 rc-drawer
  • Menu 基于 rc-menu
  • Tree / TreeSelect 基于 rc-tree
  • Tooltip / Combo 基于 rc-tooltip / rc-trigger

这说明作者并不执着于“所有东西都必须自己实现”。只要 React 生态已经有成熟基础设施,就优先复用,再叠加 FineUI 的视觉与交互语义。

这是一个非常理性的技术路线。

如果不这么做,这个库要同时完成:

  • React 化迁移
  • 组件重写
  • 设计语言统一
  • 复杂控件底层能力自研

那几乎不可能按合理成本落地。

7. rc-* 复用不是简单套皮,而是语义重组

这里有必要强调一点:react-ui 虽然大量复用了 rc-*,但它并不是“套个皮就完了”。

Drawer 为例,代码里可以看到:

  • 继续使用 FineUI 的布局组件 HorizontalTapeLayout
  • 头部、关闭按钮、标题区都套入 FineUI 的类名体系
  • pushplacementsize 这些 API 又参考了 Ant Design / rc-drawer 的现代习惯

这说明它实际上在做两件事:

  1. 把 React 生态成熟能力拿来用
  2. 再重新包成符合 FineUI 视觉和中后台习惯的组件

因此它不是简单 copy antd,也不是简单延续 FineUI,而是在两者之间做了工程拼装。

这类库的好坏,恰恰取决于拼装是否自洽。从目前结构看,react-ui 至少在架构意图上是清晰的。

8. 文档、测试和构建:这不是实验项目,而是可发布产品

从仓库基础设施看,这个项目并不是个人玩具。

8.1 文档站是主交付面

根目录使用 dumigh-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-adapterrc-* 复用,项目避免了两个最昂贵的坑:

  • 全量重写 FineUI 组件体系
  • 全量自研 React 复杂控件底层

这使它能够把研发预算集中在真正有价值的事情上:API 统一、视觉延续、业务场景落地。

10.3 它的设计哲学是连续的

很多迁移项目的失败,不是因为技术不够新,而是因为前后两代系统心智断裂。react-ui 虽然用了 React,但从布局、命名、分层到适配方式,都能明显看出它在尽量延续 FineUI 的认知习惯。

这对组织协作非常重要。

11. 它的问题也很明显

一个成熟评价必须承认这套设计的代价。

11.1 运行时桥接过重

拦截 BI.createWidget、重写 React.createElement、维护自定义 Element 抽象,这些都意味着系统存在较强的运行时魔法。

短期看,这很高效;长期看,会带来:

  • 可读性下降
  • 调试路径变长
  • 新同学理解成本高
  • 与未来 React 生态演进的耦合风险增加

11.2 它还没有彻底摆脱历史包袱

依赖上仍然能看到一些明确的时代痕迹:

  • dumi 1
  • father-build 1
  • react 17
  • IE11 兼容目标

这些并不代表项目不好,但说明它的工程基线仍然处在一个“兼容优先”的历史区间,而不是“现代能力优先”的区间。

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 重写了一遍”,而是它非常清楚迁移项目最难的从来不是写组件,而是处理新旧体系之间的连续性。

它做的最重要的事情有三件:

  1. 保留 FineUI 的设计语言和布局哲学
  2. react-adapter 把旧运行时接到 React 生态上
  3. rc-* 复用现代复杂控件基础设施,避免重复造轮子

这使它既不像传统 FineUI,也不像纯正的 Ant Design 风格组件库,而是一个很典型、也很有现实意义的企业前端迁移产物。

如果只从“纯度”评价,它并不完美;但如果从“组织迁移成本、业务连续性、工程可落地性”评价,这套设计其实相当聪明。

它不是最理想化的 React UI 框架,却很可能是当时约束下最正确的 React UI 框架。