
Bytebase 模式编辑器 Vue 到 React 迁移设计Context 状态管理、无头组件库与渐进式共存实践【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebaseSchemaEditorLite 是 Bytebase 前端中最复杂的交互面之一约 88 个文件、1.03 万行代码覆盖表/列/索引/分区/视图/存储过程/函数的可视化 DDL 编辑。本文以 设计文档 为主体结合仓库中已落地的 React 实现源码完整讲解这套用 React Context 替换 provide/inject Emittery、用无头组件库替换 naive-ui、纯 TS 算法层零改动复用、分五阶段渐进迁移的方案。读完本文你将掌握大规模 Vue 3 组件套件向 React 渐进迁移的完整方法论以及 Bytebase 项目中每个关键决策的落地细节。迁移背景与设计目标Bytebase 前端正处于从 Vue 3 到 React 的增量迁移期已有 React 页面ProjectDatabaseDetailPage、ProjectSyncSchemaPage、IssueDetailPage通过useVueState()桥接 Vue store并复用 Vue 层的纯 TS 工具。迁移遵循 迁移 Playbook 的约束迁移有边界的界面、复用既有 store 与工具、只有在调用方消失后才能删除 Vue 文件。SchemaEditorLite 之所以是迁移的重中之重是因为它承载了 Plan/Issue 流程中数据库变更规格的可视化编辑。原 Vue 实现frontend/src/components/SchemaEditorLite/的三大痛点在于依赖 naive-uiNTree 虚拟滚动、NSplit 可伸缩面板、NDropdown 等使用 Vue provide/inject 组合上下文provideSchemaEditorContext()组合useTabs、useEditStatus、useSelection、useScrollStatus四个子 hook使用 Emittery 事件总线做组件间通信共 6 种事件。这些模式在 React 生态中没有直接对应物。设计文档给出五条设计目标每一项都有明确的可验证标准与 Vue 版功能对等数据库/模式/表/视图/存储过程/函数编辑、列 CRUD类型/默认值/外键单元格、索引编辑、分区编辑、树导航右键菜单、Tab 管理、编辑状态跟踪、选择/rollout 对象跟踪、DDL 预览全部要有 React 对应物Vue 调用方零回归SchemaEditorDrawer、EditorView 等 Vue 父组件在迁移期间和迁移后保持原样工作Vue 版组件及其导入不被修改或删除复用纯 TS 层、不 forkalgorithm/、types.ts、spec.ts、utils/由 React 组件从原位置直接导入遵循既有 React 模式用 React Context 而非 zustand/redux用useVueState访问 store用 shadcn 风格 UI 与/react/components/ui/原语不引入新的状态管理库增量交付每个阶段产出可用、可测试的组件。架构总览镜像 Vue 组件树React SchemaEditorLite 镜像 Vue 组件的架构根节点是 Context Provider左侧是可伸缩的 aside 树右侧是带 Tab 的编辑面板用户动作触发模态框。关键差异在于把 Vue 专属模式换成 React 等价物SchemaEditorLite (React Context provider react-resizable-panels) ├── AsideTree (react-arborist with custom node renderers) │ ├── Node renderers (database, schema, table, view, procedure, function) │ ├── Node checkboxes (selection state) │ └── Context menu (dropdown-menu from ui/) ├── EditorPanel (tab-based routing) │ ├── DatabaseEditor │ ├── TableEditor │ │ ├── TableColumnEditor (table from ui/ with editable cells) │ │ │ ├── DataTypeCell (combobox) │ │ │ ├── DefaultValueCell (input) │ │ │ ├── ForeignKeyCell (button modal trigger) │ │ │ ├── OperationCell │ │ │ ├── ReorderCell (drag handle) │ │ │ └── SelectionCell (checkbox) │ │ ├── IndexesEditor │ │ └── PartitionsEditor │ ├── ViewEditor (Monaco wrapper) │ ├── ProcedureEditor (Monaco wrapper) │ ├── FunctionEditor (Monaco wrapper) │ └── PreviewPane (Monaco wrapper, DDL diff output) └── Modals ├── TableNameModal (dialog) ├── SchemaNameModal (dialog) ├── EditColumnForeignKeyModal (sheet, wide) ├── ViewNameModal (dialog) ├── FunctionNameModal (dialog) ├── ProcedureNameModal (dialog) └── ActionConfirmModal (alert-dialog)这套架构已经在仓库中落地实际 React 实现位于frontend/src/modules/schema-editor/目录结构与设计图一一对应Aside/、Modals/、Panels/、EditorPanel.tsx、TabsContainer.tsx、context.tsx、useTabs.ts等。根组件 SchemaEditorLite.tsx 用forwardRef导出内部渲染SchemaEditorProvider包裹的PanelGroup双栏布局。状态管理React Context 替换 provide/inject 与 Emittery设计文档的核心决策是schema editor 的状态是编辑器实例作用域内的不是全局的。React Context 天然把状态限定在组件子树内这与 Vue provide/inject 精确对应引入 zustand 反而会把组件局部关注点提升为全局状态。因此设计目标 4 明确禁止新增 zustand/TanStack Query尽管frontend/package.json中仓库已有 zustand 依赖但 schema editor 不使用它。Emittery 六事件的 React 等价物原 Vue 实现通过 Emittery 总线传播 6 种事件设计文档将其逐一映射为 context 上的回调函数Emittery 事件React 等价物update:selected-rollout-objectsonSelectedRolloutObjectsChangeprop提升到父组件rebuild-treerebuildTree()context 函数组件直接调用rebuild-edit-statusrebuildEditStatus(resets)context 函数clear-tabsclearTabs()context 函数tabs hook 的一部分refresh-previewrefreshPreview()context 函数merge-metadatamergeMetadata(metadatas)context 函数React 的单向数据流取代了 Vue 的双向事件模式。实际实现中SchemaEditorLite.tsx 正是这样做的rebuildTree通过自增treeBuildVersion让 AsideTree 重新派生树数据refreshPreview通过自增previewVersion让 PreviewPane 重新生成 DDLmergeMetadata遍历传入的DatabaseMetadata用mergeTableMetadataToTarget把新表合并进 target 与 baselineMetadata。Provider 组合模式设计文档给出的 provider 结构SchemaEditorProvider (creates context with composed hooks) └── provides: tabs, editStatus, selection, scrollStatus, callbacks └── children consume via useSchemaEditorContext()落地代码 context.tsx 就是标准模式createContextSchemaEditorContextValue | null(null)、SchemaEditorProvider组件、以及一个在 Provider 外调用时抛错的useSchemaEditorContext()。四个子 hook 的 Vue→React 映射是直接对应的Vueref()→ ReactuseState()Vuecomputed()→ ReactuseMemo()Vuewatch()→ ReactuseEffect()以 useTabs.ts 为例可看到实际实现比设计更精细tabMap用useRef(new Map())保存、currentTabId用useState、tabList用useMemo派生addTab先findTab复用已存在 Tab按 type database schema/table/view/procedure/function 名称匹配否则用uuidv1()生成新 id并通过requestAnimationFrame延迟设置当前 Tab与 Vue 行为一致。closeTab关闭当前 Tab 时自动切换到相邻 Tab。而 useEditStatus.ts 则用dirtyPathsRef记录对象路径 → 编辑状态映射通过version计数器触发重渲染并提供getSchemaStatus/getTableStatus/getColumnStatus等派生查询与isDirty。可伸缩面板react-resizable-panels 替换 NSplitVue 版用 naive-ui 的NSplit参数为min0.15、max0.4、default-size0.25。设计文档选定 react-resizable-panels2025-2026 年的主流方案shadcn/ui 的 Resizable 组件也基于它理由是无头无样式契合 shadcn 模式、WAI-ARIA 无障碍、支持布局持久化与命令式 API。对应的 JSX 结构PanelGroup (directionhorizontal) Panel (defaultSize25, minSize15, maxSize40) → AsideTree PanelResizeHandle Panel (defaultSize75) → EditorPanel实际代码 SchemaEditorLite.tsx 与设计完全一致PanelGroup的idschema-editor、左侧Panel defaultSize25% minSize15% maxSize40%、右侧Panel defaultSize75%中间用自定义resizeHandleClass(vertical, w-0.5)分隔条。依赖版本见 frontend/package.jsonreact-resizable-panels为^4.11.0。侧边树react-arborist 替换 NTreeVue aside 用 naive-ui 的 NTree 虚拟滚动展示 database/schema/table 层级。设计文档选定 react-arborist基于 react-window 的无头虚拟化树支持 1 万 节点、键盘导航、拖拽、内联重命名与自定义节点渲染。对比 NTreereact-arborist 的核心能力映射为大 schema 的虚拟化渲染1 万 节点自定义节点渲染器database、schema、table group、table、view、procedure、function 节点键盘导航与 ARIA 支持选中状态管理。落地实现 AsideTree.tsx 中可见buildTree(targets, { byInstance })从 metadata 构建TreeNode[]与nodeMap再convertToArboristData转换为 arborist 兼容格式Tree组件设置rowHeight{28}、indent{16}、searchTerm/searchMatch支持搜索节点渲染通过NodeRenderer按类型分发图标DatabaseIcon、Table2、View、FileCode、FunctionSquare并用statusClassName给 created/updated/dropped 状态着色dropped 为红色删除线。值得一提的细节组件用ErrorBoundary包裹Tree因为 react-arborist 对畸形数据如 falsy 的 node id会硬抛异常需要将其隔离在单个 pane 内。右键菜单与菜单设计文档提出右键菜单用/react/components/ui/的 dropdown-menu 替换 NDropdown菜单项建表、重命名、删除等由节点类型决定逻辑与 Vuecontext-menu.ts对齐。实际实现 useContextMenu.ts 返回{ menuState, menuOptions, showMenu, hideMenu }menuOptions按节点类型 引擎能力engineSupportsEditViews等 spec 谓词计算。AsideTree 同时提供了始终可见的下拉菜单放在搜索框旁路由到第一个 target 第一个 schema覆盖最常见的单库单 schema 场景多 schema 的高级用户仍可右键具体 schema。handleMenuSelect中可以看到完整的操作语义create-table/rename-table 打开TableNamePopover、drop-table 对已创建的表直接splice并从 editStatus 移除、对既有表markEditStatus(dropped)并关闭其已打开 Tab、create-view/procedure/function 用create(ViewMetadataSchema, ...)构造新 metadata 并打开对应 Tab。节点复选框10 变体收敛为 1 个组件Vue 版有 10 个节点复选框变体因 Vue 模板组合需要独立组件文件。设计文档的方案是收敛为单个NodeCheckbox组件接收节点的 metadata 类型并委托给 selection context——React 的 JSX 条件渲染在一个组件内即可完成。落地见Aside/NodeCheckbox.tsx树行内同时渲染NodeCheckbox选择状态与StatusBadgeBYT-9473 的改进纯文字颜色太弱改为在 created/updated/dropped 旁加单字符徽标/~/−。Tab 管理useReducer 风格的状态与类型路由VueuseTabs维护Mapstring, TabContext与当前 Tab。设计文档建议 React 用useReducer配合ADD_TAB、CLOSE_TAB、SET_CURRENT_TAB、CLEAR_TABS动作实际实现 useTabs.ts 选择了useRefMapuseState版本号的自定义 hook 形态无 reducer语义等价。Tab 列表用 ui/ 的Tabs原语渲染。Tab 到面板的路由由 EditorPanel.tsx 完成顶部渲染TabsContainer下方按currentTab.type分发——database→DatabaseEditor、table→TableEditor、view→ViewEditor、procedure→ProcedureEditor、function→FunctionEditor并用key{currentTab?.id ?? empty}强制 Tab 切换时重挂载。无 Tab 时显示选择对象占位提示。额外细节每个 Tab 的 schema 选择状态按 Tab id 存储selectedSchemas避免跨 Tab 泄漏。编辑器面板与单元格组件每个面板都是其 Vue 对应物的直接移植TableEditorColumns/Indexes/Partitions 多模式 Tab工具栏含 Add Column/Add Index/Add Partition 与选择汇总徽标底部渲染 PreviewPaneTableColumnEditor最复杂的子组件用 ui/ 的Table渲染可内联编辑的表6 类单元格各成一个 React 组件——DataTypeCellCombobox 类型建议来自getDataTypeSuggestionList(engine)、DefaultValueCellnull/expression/value 下拉 输入复用getColumnDefaultValuePlaceholder()、ForeignKeyCellschema.table(column) 链接 编辑按钮、OperationCell删除/恢复禁止删除最后一列、ReorderCell上移/下移arraySwap()、SelectionCell列选择复选框主键检测通过查找primarytrue的索引并检查列是否在pk.expressions中IndexesEditor / PartitionsEditor较简单的表式编辑器直接移植分区编辑器仅支持 MySQL/TiDB 分区类型依 spec.ts 谓词ViewEditor / ProcedureEditor / FunctionEditor三个几乎相同的 Monaco 编辑器包装分别编辑view.definition、procedure.definition、function.definition变更即markEditStatus(updated)PreviewPane只读 Monaco 展示generateDiffDDL()输出监听 context 的refreshPreview触发重新生成hidePreview控制显隐。模态框映射到现有 React UI 原语设计文档给出的模态框映射表Vue ModalReact 组件UI 原语ActionConfirmModalActionConfirmDialogAlertDialogTableNameModalTableNameDialogDialogSchemaNameModalSchemaNameDialogDialogEditColumnForeignKeyModalEditColumnForeignKeySheetSheet(wide)ViewNameModalViewNameDialogDialogFunctionNameModalFunctionNameDialogDialogProcedureNameModalProcedureNameDialogDialog只有 EditColumnForeignKeyModal 值得用宽版 Sheet——它包含带表/列选择器的多字段表单其余都是单字段对话框名称输入 确认/取消。落地实现全部位于frontend/src/modules/schema-editor/Modals/TableNamePopover.tsx、SchemaNameDialog.tsx、EditColumnForeignKeySheet.tsx、ActionConfirmDialog.tsx等。其中 TableName 采用了 Popover 形式锚定在右键位置符合设计文档anchor 于菜单位置的意图EditColumnForeignKeySheet 用级联选择选 schema 重置 tablecolumn、选 table 重置 column依赖upsertColumnFromForeignKey()/removeColumnFromForeignKey()编辑工具函数。纯 TS 层零改动复用保住约 1,200 行经过测试的逻辑设计文档最重要的工程决策之一是识别出以下模块不含任何 Vue 导入可被 React 直接原样导入实际路径为frontend/src/modules/schema-editor/core/algorithm/diff-merge.ts— metadata 比较与调和790 行algorithm/rebuild.ts— 从编辑操作重建 metadataalgorithm/apply.ts— 将变更应用到内存 metadatatypes.ts— EditTarget、TabContext、RolloutObject、EditStatus 类型spec.ts— 引擎功能支持谓词engineSupportsEditIndexes、engineSupportsEditTablePartitions等utils/columnDefaultValue.ts— 默认值占位逻辑utils/metadata.ts— metadata 转换辅助common.ts—generateDiffDDL()RPC 调用这样 Vue 与 React 两个实现共享完全相同的 DDL 生成行为避免双实现分叉。计划文档 T8 进一步说明接入方式DiffMerge类只依赖 context 的markEditStatusByKey所以 React 侧只需定义一个仅含该方法的最小DiffMergeContext接口并做类型断言无需改动 Vue 算法文件。公共 API 与 Bridge 桥接React 版对外暴露与 Vue 版相同的接口Propsproject、readonly、selectedRolloutObjects、targets、loading、hidePreview回调onSelectedRolloutObjectsChange、onIsEditingChange命令式句柄useImperativeHandle/forwardRefapplyMetadataEdit()、refreshPreview()、isDirty。实际实现 SchemaEditorLite.tsx 中useImperativeHandle正是暴露这三个方法。设计文档还给出了未来 Vue 父组件嵌入 React 版的方案用createRoot挂载的薄 Vue 包装组件。但这不是初始迁移的必需项——React 版只被 React 父组件消费Vue 父组件继续用 Vue 版对应设计目标 2 与不迁移所有 Vue 调用方的非目标。仓库中frontend/src/routes/project/plan-detail/components/SchemaEditorSheet.tsx即 React 侧的消费方示例。分五阶段迁移每阶段可交付可验证设计文档把迁移拆成五个阶段每阶段都有明确的规模估算、入口与退出标准Phase 1: Foundation约 2,000 行React context provider替换provideSchemaEditorContext EmitteryuseTabs、useEditStatus、useSelection、useScrollStatus四个 hook带 react-resizable-panels 的 SchemaEditorLite 外壳入口确认types.ts、spec.ts、utils/、algorithm/可被 React 导入退出空编辑器以可伸缩 pane 渲染context 对子组件可用Phase 2: Aside Tree约 1,500 行react-arborist 集成 自定义节点渲染器含 create/rename/drop 动作的右键菜单节点复选框选择单组件 类型分发渲染搜索/过滤退出树可导航 database metadata右键菜单触发桩Phase 3: Core Editors约 2,500 行基于类型的 Tab 容器与路由TableEditor 列编辑器6 类单元格组件DatabaseEditorschema 选择器、建表触发退出表列可编辑、可新建表Phase 4: Extended Editors约 1,500 行IndexesEditor、PartitionsEditorViewEditor、ProcedureEditor、FunctionEditorMonaco 包装PreviewPaneDDL diff 展示退出所有面板类型可用Phase 5: Modals and Polish约 1,500 行全部 7 个模态框编辑状态指示器created/updated/dropped 视觉标记滚动位置保持applyMetadataEditgenerateDiffDDL集成测试退出与 Vue 实现验证功能完全对等设计目标 5 强调部分完成的迁移如没有分区编辑器的表编辑器本身就有价值每阶段的退出标准即为可测试性保障。计划文档将上述阶段展开为 28 个具体任务T1 安装依赖 → T28 最终验证T28 的验证清单包括pnpm --dir frontend fix、check、type-check、test全部通过且git diff frontend/src/components/SchemaEditorLite/确认 Vue 文件未被改动。非目标与权衡什么不做为什么设计文档明确划出了边界这些边界是保证迁移质量的关键不重写algorithm/、spec.ts、utils/、types.ts纯 TS、无 Vue 依赖不迁移 Plan/Issue 页面本身React 组件被嵌入Vue 父页面通过桥接使用不迁移 SQL 编辑器视图TablesPanel、ViewsPanel、ExternalTablesPanel不替换 ELK 布局引擎或 Monaco 集成框架无关库不引入新状态管理库zustand、TanStack Query迁移期间不改变 schema editor 行为、不添加新功能不迁移所有 Vue 调用方——Vue 调用方在自己被迁移前继续使用 Vue 版SchemaDiagram 与 SchemaPane 不在本次迁移范围——它们是调用方不同的独立界面ER 图查看器、SQL 编辑器侧边栏模式浏览器应作为独立迁移任务。行业基线部分给出的权衡同样值得注意双框架并行会带来构建复杂度且若两者管理同一 DOM 子树会产生 reconciliation 冲突增量迁移意味着 Vue 版必须维护到所有调用方迁移完成为止存在临时重复成本DrawDB 式的分布式 context 对自包含编辑器很合适但嵌入有外部状态如 Pinia store的大型应用时需要小心协调。结语从设计到落地的验证这份设计文档不是停留在纸面的方案——仓库中frontend/src/modules/schema-editor/的完整实现与设计逐条对应Context providercontext.tsx、四子 hookuseTabs.ts、useEditStatus.ts、useSelection.ts、useScrollStatus.ts、可伸缩双栏SchemaEditorLite.tsx、react-arborist 侧边树AsideTree.tsx、类型路由面板EditorPanel.tsx、模态框集Modals均已存在react-arborist ^3.5.0与react-resizable-panels ^4.11.0也已在 frontend/package.json 中。对于正在规划大规模前端框架迁移的团队本方案在状态局部化、无头组件、纯逻辑复用、渐进交付、Vue 调用方零回归五个维度上提供了可直接参照的工程样板。【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考