我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

OpenMetadata 前端中的 React 组合模式:用复合组件与状态提升终结布尔属性泛滥

OpenMetadata 前端中的 React 组合模式:用复合组件与状态提升终结布尔属性泛滥 OpenMetadata 前端中的 React 组合模式用复合组件与状态提升终结布尔属性泛滥【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata本指南以 OpenMetadata 仓库内置的 React Composition Patterns 技能规则集位于 skills/vendor/composition-patterns为主线系统讲解一套可规模化落地的 React 组件组合方法论如何用复合组件Compound Components、状态提升Lifting State与内部组合Composing Internals替代不断膨胀的布尔属性让组件库在人和 AI Agent 的双重维护下都保持可读、可扩展。读完本文你将掌握避免布尔属性泛滥、解耦状态与 UI、定义可依赖注入的 Context 接口、创建显式组件变体以及适配 React 19 新 API 的完整实操方案并看到这些模式在 OpenMetadata 前端 UIopenmetadata-ui/src/main/resources/ui真实代码中的落地形态。这套规则从哪里来仓库内的 vendored skill 结构OpenMetadata 仓库将这份 React 组合模式规则集以 vendored 方式固化在 skills/vendor/composition-patterns 目录下而不是通过npx skills add按需安装。按照 VENDORED.md 的说明这样做的目的是让每一位贡献者与每一次 CI 运行都能在无网络的情况下获得完全一致的规则避免因上游变更导致的行为漂移因此该目录下的文件不应被本地修改。整个技能包由以下文件构成文件角色README.md技能入口目录结构、规则清单、核心原则、新增规则流程、影响级别SKILL.mdAgent 侧快速参考何时应用、优先级表格、各规则一句话摘要AGENTS.md编译产物全部规则展开后的完整指南含全部代码示例metadata.json文档元数据版本1.0.0、所属组织、摘要与参考rules/每条规则一个文件外加_sections.md章节元数据与_template.md新规则模板规则按四个章节组织每章有明确的优先级Priority与影响级别Impact且每个规则文件用统一的前缀命名便于检索与自动加载优先级章节影响级别文件名前缀1组件架构Component ArchitectureHIGHarchitecture-2状态管理State ManagementMEDIUMstate-3实现模式Implementation PatternsMEDIUMpatterns-4React 19 APIMEDIUMreact19-SKILL.md 明确给出了这套规则的适用时机重构带有大量布尔属性的组件、构建可复用组件库、设计灵活的组件 API、评审组件架构以及处理复合组件或 Context Provider 相关任务。每个规则文件都遵循统一模板——先讲为什么重要再给错误示例 解释再给正确示例 解释最后补充额外上下文与参考。四条核心原则无论规则如何细分这套方法论始终围绕四条核心原则展开见 README.md组合优于配置Composition over configuration——与其不断给组件加 props不如让使用者自己组合需要的能力提升你的状态Lift your state——状态放在 Provider 中而不是被困死在单个组件内部组合你的内部实现Compose your internals——子组件通过 Context 读取共享状态而不是通过 props 层层透传显式变体Explicit variants——创建ThreadComposer、EditComposer这样的具名组件而不是一个带isThread开关的万能Composer。第一部分组件架构Component Architecture组件架构章节承载两条规则architecture-avoid-boolean-props与architecture-compound-components。前者是整套规则中唯一被标记为CRITICAL的规则防止产生无法维护的组件变体后者则给出了替代布尔属性泛滥的组件组织方式。1.1 避免布尔属性泛滥CRITICAL不要为了定制组件行为而添加isThread、isEditing、isDMThread这类布尔属性。每增加一个布尔属性组件的可能状态数量就会翻倍随之而来的是成片难以维护的条件渲染逻辑。正确的做法是用组合composition替代条件分支。错误示例布尔属性制造指数级复杂度function Composer({ onSubmit, isThread, channelId, isDMThread, dmId, isEditing, isForwarding, }: Props) { return ( form Header / Input / {isDMThread ? ( AlsoSendToDMField id{dmId} / ) : isThread ? ( AlsoSendToChannelField id{channelId} / ) : null} {isEditing ? ( EditActions / ) : isForwarding ? ( ForwardActions / ) : ( DefaultActions / )} Footer onSubmit{onSubmit} / /form ) }在上面的代码里isThread/isDMThread/isEditing/isForwarding四组布尔开关相互叠加调用者必须自行脑补所有合法组合——而其中相当一部分组合根本不会出现既是编辑又是转发这些不可能状态恰恰是 bug 的温床。从源码结构看这类组件的渲染输出已经完全无法从 JSX 中直接读懂。正确示例组合消除条件分支// Channel composer function ChannelComposer() { return ( Composer.Frame Composer.Header / Composer.Input / Composer.Footer Composer.Attachments / Composer.Formatting / Composer.Emojis / Composer.Submit / /Composer.Footer /Composer.Frame ) } // Thread composer - adds also send to channel field function ThreadComposer({ channelId }: { channelId: string }) { return ( Composer.Frame Composer.Header / Composer.Input / AlsoSendToChannelField id{channelId} / Composer.Footer Composer.Formatting / Composer.Emojis / Composer.Submit / /Composer.Footer /Composer.Frame ) } // Edit composer - different footer actions function EditComposer() { return ( Composer.Frame Composer.Input / Composer.Footer Composer.Formatting / Composer.Emojis / Composer.CancelEdit / Composer.SaveEdit / /Composer.Footer /Composer.Frame ) }每个变体都明确宣告自己渲染什么、包含哪些能力同时各个变体之间可以共享内部构件Composer.Input、Composer.Footer等而不必共享一个臃肿的父组件。规则文件位于 rules/architecture-avoid-boolean-props.md。1.2 使用复合组件HIGH将复杂组件组织为带共享 Context 的复合组件每个子组件通过 Context 读取共享状态而不是通过 props 接收。使用者按需组合自己需要的部件从而在不进行 props 钻取prop drilling的前提下实现灵活组合。错误示例带 render props 的巨型单体组件function Composer({ renderHeader, renderFooter, renderActions, showAttachments, showFormatting, showEmojis, }: Props) { return ( form {renderHeader?.()} Input / {showAttachments Attachments /} {renderFooter ? ( renderFooter() ) : ( Footer {showFormatting Formatting /} {showEmojis Emojis /} {renderActions?.()} /Footer )} /form ) }正确示例带共享 Context 的复合组件const ComposerContext createContextComposerContextValue | null(null) function ComposerProvider({ children, state, actions, meta }: ProviderProps) { return ( ComposerContext value{{ state, actions, meta }} {children} /ComposerContext ) } function ComposerFrame({ children }: { children: React.ReactNode }) { return form{children}/form } function ComposerInput() { const { state, actions: { update }, meta: { inputRef }, } use(ComposerContext) return ( TextInput ref{inputRef} value{state.input} onChangeText{(text) update((s) ({ ...s, input: text }))} / ) } function ComposerSubmit() { const { actions: { submit }, } use(ComposerContext) return Button onPress{submit}Send/Button } // Export as compound component const Composer { Provider: ComposerProvider, Frame: ComposerFrame, Input: ComposerInput, Submit: ComposerSubmit, Header: ComposerHeader, Footer: ComposerFooter, Attachments: ComposerAttachments, Formatting: ComposerFormatting, Emojis: ComposerEmojis, }使用方式Composer.Provider state{state} actions{actions} meta{meta} Composer.Frame Composer.Header / Composer.Input / Composer.Footer Composer.Formatting / Composer.Submit / /Composer.Footer /Composer.Frame /Composer.Provider使用者显式组合自己真正需要的部分没有任何隐藏的条件分支同时state、actions、meta由上层 Provider 依赖注入同一套组件结构可以被多个使用场景复用。规则文件位于 rules/architecture-compound-components.md。第二部分状态管理State Management状态管理章节包含三条规则state-decouple-implementation、state-context-interface、state-lift-state核心议题是把状态从组件里搬出去、把状态与 UI 彻底解耦。2.1 将状态管理与 UI 解耦MEDIUMProvider 组件应该是唯一知道状态如何被管理的地方。UI 组件只消费 Context 接口——它们不需要知道状态究竟来自useState、Zustand 还是服务端同步。这样做的收益是在不改动 UI 的前提下随意替换状态实现。错误示例UI 与状态实现强耦合function ChannelComposer({ channelId }: { channelId: string }) { // UI component knows about global state implementation const state useGlobalChannelState(channelId) const { submit, updateInput } useChannelSync(channelId) return ( Composer.Frame Composer.Input value{state.input} onChange{(text) sync.updateInput(text)} / Composer.Submit onPress{() sync.submit()} / /Composer.Frame ) }正确示例状态管理被隔离在 Provider 中// Provider handles all state management details function ChannelProvider({ channelId, children, }: { channelId: string children: React.ReactNode }) { const { state, update, submit } useGlobalChannel(channelId) const inputRef useRef(null) return ( Composer.Provider state{state} actions{{ update, submit }} meta{{ inputRef }} {children} /Composer.Provider ) } // UI component only knows about the context interface function ChannelComposer() { return ( Composer.Frame Composer.Header / Composer.Input / Composer.Footer Composer.Submit / /Composer.Footer /Composer.Frame ) } // Usage function Channel({ channelId }: { channelId: string }) { return ( ChannelProvider channelId{channelId} ChannelComposer / /ChannelProvider ) }不同 Provider同一套 UI// Local state for ephemeral forms function ForwardMessageProvider({ children }) { const [state, setState] useState(initialState) const forwardMessage useForwardMessage() return ( Composer.Provider state{state} actions{{ update: setState, submit: forwardMessage }} {children} /Composer.Provider ) } // Global synced state for channels function ChannelProvider({ channelId, children }) { const { state, update, submit } useGlobalChannel(channelId) return ( Composer.Provider state{state} actions{{ update, submit }} {children} /Composer.Provider ) }同一个Composer.Input组件之所以能同时服务于两个 Provider是因为它只依赖 Context 接口而非具体实现。规则文件位于 rules/state-decouple-implementation.md。2.2 为依赖注入定义通用 Context 接口HIGH为组件 Context 定义一个通用接口包含三部分state、actions、meta。这个接口是一份契约任何 Provider 都可以实现它——从而让同一套 UI 组件对接完全不同的状态实现。其核心原则是提升状态、组合内部实现、让状态可依赖注入。错误示例UI 与特定状态实现耦合function ComposerInput() { // Tightly coupled to a specific hook const { input, setInput } useChannelComposerState() return TextInput value{input} onChangeText{setInput} / }正确示例通用接口开启依赖注入// Define a GENERIC interface that any provider can implement interface ComposerState { input: string attachments: Attachment[] isSubmitting: boolean } interface ComposerActions { update: (updater: (state: ComposerState) ComposerState) void submit: () void } interface ComposerMeta { inputRef: React.RefObjectTextInput } interface ComposerContextValue { state: ComposerState actions: ComposerActions meta: ComposerMeta } const ComposerContext createContextComposerContextValue | null(null)UI 组件消费接口而非实现function ComposerInput() { const { state, actions: { update }, meta, } use(ComposerContext) // This component works with ANY provider that implements the interface return ( TextInput ref{meta.inputRef} value{state.input} onChangeText{(text) update((s) ({ ...s, input: text }))} / ) }不同 Provider 实现同一接口// Provider A: Local state for ephemeral forms function ForwardMessageProvider({ children }: { children: React.ReactNode }) { const [state, setState] useState(initialState) const inputRef useRef(null) const submit useForwardMessage() return ( ComposerContext value{{ state, actions: { update: setState, submit }, meta: { inputRef }, }} {children} /ComposerContext ) } // Provider B: Global synced state for channels function ChannelProvider({ channelId, children }: Props) { const { state, update, submit } useGlobalChannel(channelId) const inputRef useRef(null) return ( ComposerContext value{{ state, actions: { update, submit }, meta: { inputRef }, }} {children} /ComposerContext ) }同一套组合 UI 与两个 Provider 都兼容// Works with ForwardMessageProvider (local state) ForwardMessageProvider Composer.Frame Composer.Input / Composer.Submit / /Composer.Frame /ForwardMessageProvider // Works with ChannelProvider (global synced state) ChannelProvider channelIdabc Composer.Frame Composer.Input / Composer.Submit / /Composer.Frame /ChannelProviderProvider 边界之外的自定义 UI 也能访问状态与动作关键在于起决定作用的是 Provider 边界而不是视觉嵌套。需要共享状态的组件不必位于Composer.Frame内部只要位于 Provider 之内即可。function ForwardMessageDialog() { return ( ForwardMessageProvider Dialog {/* The composer UI */} Composer.Frame Composer.Input placeholderAdd a message, if youd like. / Composer.Footer Composer.Formatting / Composer.Emojis / /Composer.Footer /Composer.Frame {/* Custom UI OUTSIDE the composer, but INSIDE the provider */} MessagePreview / {/* Actions at the bottom of the dialog */} DialogActions CancelButton / ForwardButton / /DialogActions /Dialog /ForwardMessageProvider ) } // This button lives OUTSIDE Composer.Frame but can still submit based on its context! function ForwardButton() { const { actions: { submit }, } use(ComposerContext) return Button onPress{submit}Forward/Button } // This preview lives OUTSIDE Composer.Frame but can read composers state! function MessagePreview() { const { state } use(ComposerContext) return Preview message{state.input} attachments{state.attachments} / }ForwardButton与MessagePreview虽然并不在编辑框Composer.Frame的视觉内部却依然能读写其状态与动作——这正是把状态提升进 Provider的力量所在。UI 是可复用的积木状态由 Provider 依赖注入更换 Provider保留 UI。规则文件位于 rules/state-context-interface.md。2.3 把状态提升到 Provider 组件HIGH把状态管理移入专门的 Provider 组件让主 UI 之外的兄弟组件也能在不做 props 钻取、不用别扭的 ref的前提下访问与修改状态。规则文件先给出三种常见错误做法再给出正解。错误做法一状态被困在组件内部function ForwardMessageComposer() { const [state, setState] useState(initialState) const forwardMessage useForwardMessage() return ( Composer.Frame Composer.Input / Composer.Footer / /Composer.Frame ) } // Problem: How does this button access composer state? function ForwardMessageDialog() { return ( Dialog ForwardMessageComposer / MessagePreview / {/* Needs composer state */} DialogActions CancelButton / ForwardButton / {/* Needs to call submit */} /DialogActions /Dialog ) }错误做法二用 useEffect 向上同步状态function ForwardMessageDialog() { const [input, setInput] useState() return ( Dialog ForwardMessageComposer onInputChange{setInput} / MessagePreview input{input} / /Dialog ) } function ForwardMessageComposer({ onInputChange }) { const [state, setState] useState(initialState) useEffect(() { onInputChange(state.input) // Sync on every change }, [state.input]) }每次输入变化都触发一次useEffect同步既产生多余渲染也让数据流变得晦涩。错误做法三提交时从 ref 读取状态function ForwardMessageDialog() { const stateRef useRef(null) return ( Dialog ForwardMessageComposer stateRef{stateRef} / ForwardButton onPress{() submit(stateRef.current)} / /Dialog ) }正确做法状态提升到 Providerfunction ForwardMessageProvider({ children }: { children: React.ReactNode }) { const [state, setState] useState(initialState) const forwardMessage useForwardMessage() const inputRef useRef(null) return ( Composer.Provider state{state} actions{{ update: setState, submit: forwardMessage }} meta{{ inputRef }} {children} /Composer.Provider ) } function ForwardMessageDialog() { return ( ForwardMessageProvider Dialog ForwardMessageComposer / MessagePreview / {/* Custom components can access state and actions */} DialogActions CancelButton / ForwardButton / {/* Custom components can access state and actions */} /DialogActions /Dialog /ForwardMessageProvider ) } function ForwardButton() { const { actions } use(Composer.Context) return Button onPress{actions.submit}Forward/Button }ForwardButton位于Composer.Frame之外却依然能调用提交动作因为它处于 Provider 之内。即便它是一个一次性组件也能从 UI 外部访问编辑框的状态与动作。关键洞察需要共享状态的组件不必在视觉上互相嵌套——它们只需要处于同一个 Provider 之内。规则文件位于 rules/state-lift-state.md。第三部分实现模式Implementation Patterns3.1 创建显式组件变体MEDIUM与其做一个带大量布尔属性的全能组件不如创建显式的变体组件每个变体组合自己需要的部件让代码自我文档化self-documenting不留任何隐藏的条件分支。错误示例一个组件承载多种模式// What does this component actually render? Composer isThread isEditing{false} channelIdabc showAttachments showFormatting{false} /正确示例显式变体// Immediately clear what this renders ThreadComposer channelIdabc / // Or EditMessageComposer messageIdxyz / // Or ForwardMessageComposer messageId123 /完整实现function ThreadComposer({ channelId }: { channelId: string }) { return ( ThreadProvider channelId{channelId} Composer.Frame Composer.Input / AlsoSendToChannelField channelId{channelId} / Composer.Footer Composer.Formatting / Composer.Emojis / Composer.Submit / /Composer.Footer /Composer.Frame /ThreadProvider ) } function EditMessageComposer({ messageId }: { messageId: string }) { return ( EditMessageProvider messageId{messageId} Composer.Frame Composer.Input / Composer.Footer Composer.Formatting / Composer.Emojis / Composer.CancelEdit / Composer.SaveEdit / /Composer.Footer /Composer.Frame /EditMessageProvider ) } function ForwardMessageComposer({ messageId }: { messageId: string }) { return ( ForwardMessageProvider messageId{messageId} Composer.Frame Composer.Input placeholderAdd a message, if youd like. / Composer.Footer Composer.Formatting / Composer.Emojis / Composer.Mentions / /Composer.Footer /Composer.Frame /ForwardMessageProvider ) }每个变体都明确声明三件事使用哪个 Provider/状态、包含哪些 UI 元素、暴露哪些动作。没有需要推理的布尔属性组合也没有不可能出现的状态。规则文件位于 rules/patterns-explicit-variants.md。3.2 优先组合 children 而非 render propsMEDIUM组合静态结构时优先使用children而不是renderX这类 render props。children可读性更强、天然支持嵌套组合且不需要调用方理解回调函数签名。错误示例render propsfunction Composer({ renderHeader, renderFooter, renderActions, }: { renderHeader?: () React.ReactNode renderFooter?: () React.ReactNode renderActions?: () React.ReactNode }) { return ( form {renderHeader?.()} Input / {renderFooter ? renderFooter() : DefaultFooter /} {renderActions?.()} /form ) } // Usage is awkward and inflexible return ( Composer renderHeader{() CustomHeader /} renderFooter{() ( Formatting / Emojis / / )} renderActions{() SubmitButton /} / )正确示例带 children 的复合组件function ComposerFrame({ children }: { children: React.ReactNode }) { return form{children}/form } function ComposerFooter({ children }: { children: React.ReactNode }) { return footer classNameflex{children}/footer } // Usage is flexible return ( Composer.Frame CustomHeader / Composer.Input / Composer.Footer Composer.Formatting / Composer.Emojis / SubmitButton / /Composer.Footer /Composer.Frame )render props 何时仍然适用// Render props work well when you need to pass data back List data{items} renderItem{({ item, index }) Item item{item} index{index} /} /决策标准很清晰当父组件需要向子组件传递数据或状态时用 render props组合静态结构时用 children。规则文件位于 rules/patterns-children-over-render-props.md。第四部分React 19 APIMEDIUM⚠️ 仅适用于 React 19。如果你仍在 React 18 或更早版本请跳过本节。React 19 中ref变成了普通属性不再需要forwardRef包装use()取代了useContext()。规则文件 rules/react19-no-forwardref.md 给出了明确的迁移写法。错误示例React 19 中仍用 forwardRefconst ComposerInput forwardRefTextInput, Props((props, ref) { return TextInput ref{ref} {...props} / })正确示例ref 作为普通属性function ComposerInput({ ref, ...props }: Props { ref?: React.RefTextInput }) { return TextInput ref{ref} {...props} / }错误示例React 19 中仍用 useContextconst value useContext(MyContext)正确示例用 use 替代 useContextconst value use(MyContext)use()还可以被条件调用这是useContext()不具备的能力——useContext()必须遵守 Hooks 调用规则而use()作为普通函数可以在条件分支中调用。规则文件位于 rules/react19-no-forwardref.md。在 OpenMetadata UI 中的印证Provider 模式的真实落地这套规则并非纸上谈兵——OpenMetadata 前端本身就是一个以 Context Provider 组织共享状态的典型案例。当前 OpenMetadata UI 的 React 版本为^18.2.0见 openmetadata-ui/src/main/resources/ui/package.json因此上述 React 19 API 部分在当前仓库中尚不适用但Provider 封装状态 自定义 hook 消费 Context的组合模式已大量存在。以 EntityAttachmentProvider.tsx 为例它完整复刻了本文第二部分描述的模式定义了EntityAttachmentType接口作为 Context 契约包含entityType、handleFileUpload、errorMessage、isPopoverOpenRef等字段通过createContextEntityAttachmentType({} as EntityAttachmentType)创建 Context在EntityAttachmentProvider组件内部集中管理全部上传状态errorMessage、isPopoverOpenRef、handleFileUpload、handleErrorMessage等并通过useMemo组装 context value 后经EntityAttachmentContext.Provider下发对外暴露useEntityAttachment()自定义 hook让任何处于 Provider 之内的子组件包括编辑器、弹窗等都能读取上传能力而无需逐层传递 props——这正是把状态提升到 Provider、UI 组件只消费 Context 接口的直接印证。类似的 Provider 封装在 UI 目录中还有多处例如 EntityExportModalProvider.component.tsx它通过createContextEntityExportModalContextProps({} as EntityExportModalContextProps)建立契约将 CSV 导出任务的轮询、WebSocket 状态同步、错误处理等全部封装在 Provider 内部业务组件只需消费上下文即可触发导出弹窗与任务跟踪。从源码结构看OpenMetadata UI 中凡是需要跨组件共享能力/状态的场景普遍采用Context 接口 Provider 自定义 hook这一组合与本文档推荐的状态提升与依赖注入模式高度一致。如何新增一条规则按 README.md 与 rules/_template.md 的约定新增规则共四步将 rules/_template.md 复制为rules/area-description.md选择对应的章节前缀architecture-组件架构、state-状态管理、patterns-实现模式此外 React 19 相关规则使用react19-前缀填充 frontmattertitle、impact、impactDescription、tags与正文确保每条规则都包含带讲解的清晰示例错误示例 正确示例。模板文件本身给出了规则文件的统一骨架frontmatter 声明标题、影响级别与影响描述正文先是规则是什么、为什么重要的简要说明然后是**Incorrect:**与**Correct:**两个代码块最后可附参考链接。章节归属与排序由 rules/_sections.md 统一维护该文件以括号内的小节 IDarchitecture、state、patterns、react19作为规则文件名的前缀约定。值得注意的是依据 VENDORED.md 的说明vendored 目录内的文件不应被直接修改刷新时会丢失仓库特定规范应放入.claude/rules/目录按路径自动加载且优先级更高。影响级别如何权衡规则的优先级规则按影响程度分为三级见 README.mdCRITICAL—— 基础性模式防止产生无法维护的代码对应避免布尔属性泛滥architecture-avoid-boolean-props因为它直接决定组件 API 的可维护性底线HIGH—— 显著的维护性提升对应复合组件、通用 Context 接口、状态提升、状态解耦等规则MEDIUM—— 让代码更整洁的良好实践对应显式变体、children 组合、React 19 API 适配等规则。注意 SKILL.md 中标注的章节级影响Component Architecture 为 HIGH其余为 MEDIUM与单条规则内部的 frontmatter 影响级别并不完全一致——例如避免布尔属性泛滥在章节层面归入 HIGH 的组件架构但其规则自身为 CRITICAL通用 Context 接口与状态提升虽属 MEDIUM 章节规则自身也是 HIGH。实际落地时应以每个规则文件 frontmatter 中的impact字段为准。结语从加开关到做组合React Composition Patterns 这套规则集的最终诉求是把需求变化 → 再给组件加一个布尔开关的惯性扭转成需求变化 → 拆出一个新的具名变体 / 换一个 Provider。在 OpenMetadata 这类长期演进、多人协作且开始引入 AI Agent 辅助维护的前端仓库中显式、自文档化、可依赖注入的组件结构直接决定了代码的可读性上限与 Agent 理解代码的准确性。四个要点可以浓缩为一句话组合优于配置、状态住进 Provider、子组件只读 Context、变体永远具名——在此基础上无论未来是接入 React 19 的use()还是继续停留在 React 18 的useContext()这套骨架都不会过时。【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表