Swagger UI 插件 API 完全指南:从 Action 到 wrapComponents 的系统级扩展实战

发布时间:2026/9/10 23:20:21
Swagger UI 插件 API 完全指南:从 Action 到 wrapComponents 的系统级扩展实战 Swagger UI 插件 API 完全指南从 Action 到 wrapComponents 的系统级扩展实战【免费下载链接】swagger-uiSwagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.项目地址: https://gitcode.com/GitHub_Trending/sw/swagger-uiSwagger UI 本质上是一个由 React 与 Redux 驱动的可扩展系统而插件Plugin正是向这个系统注入能力、改造行为、覆盖组件的唯一官方入口。本文以docs/customization/plugin-api.md为骨架结合仓库内 系统核心实现 与真实插件如 deep-linking 插件、spec 插件源码完整讲解插件的返回格式、statePlugins六大接口、wrapComponents等全部扩展点并给出可直接运行的代码示例帮助你掌握通过插件深度定制 Swagger UI 的完整方法。插件是什么一个返回对象的函数在 Swagger UI 中插件是一个函数调用后返回一个对象。更准确地说返回的对象可以包含函数和组件用来增强augment与修改modifySwagger UI 的功能。当 Swagger UI 被初始化时会遍历编译所有通过presets与plugins配置项传入的插件把每个插件返回的对象合并进运行时系统System。const MyPlugin function(system) { return { // 插件返回的对象定义状态、组件、工具函数等 } }从源码看这一编译过程发生在 src/core/system.js 的Store.register与combinePlugins中插件可以是单个对象、单个函数或插件数组会被逐个调用、扁平化最终通过systemExtend合并进统一的system对象// src/core/system.js 中的 combinePlugins function combinePlugins(plugins, toolbox) { if(isObject(plugins) !isArray(plugins)) { return merge({}, plugins) } if(isFunc(plugins)) { return combinePlugins(plugins(toolbox), toolbox) } if(isArray(plugins)) { return plugins .map(plugin combinePlugins(plugin, toolbox)) .reduce(systemExtend, { components: toolbox.getComponents() }) } return {} }也就是说插件可以写成函数推荐可以接收system参数、普通对象也可以是由多个插件组成的数组。语义化版本注意内部 API 不受公开契约保护需要特别提醒Swagger UI 的内部 API 不属于公开契约这意味着它们可以在不提升大版本号的情况下发生变化。如果你的自定义插件包装wrap、扩展extend、覆盖override或消费了任何内部核心 API建议在应用中锁定 Swagger UI 的具体次版本号minor version因为补丁版本patch之间这些内部 API 不会改变。通过 NPM 安装时可以使用波浪号~来锁定{ dependencies: { swagger-ui: ~3.11.0 } }插件返回值的格式插件返回的对象可以包含以下任意键其中stateKey是你为一段状态state起的名字{ statePlugins: { [stateKey]: { actions, // 动作创建器 reducers, // 归约器Reducer selectors, // 选择器Selector wrapActions, // 包装已有动作 wrapSelectors // 包装已有选择器 } }, components: {}, // 注册新组件 wrapComponents: {}, // 包装已有组件 rootInjects: {}, // 注入系统顶层 afterLoad: (system) {}, // 插件注册完成后回调 fn: {}, // 工具函数 }对照仓库中真实插件的写法例如 spec 插件 正是按此格式组织// src/core/plugins/spec/index.js const SpecPlugin () ({ statePlugins: { spec: { wrapActions: { ...wrapActions }, reducers: { ...reducers }, actions: { ...actions }, selectors: { ...selectors }, }, }, })其中statePlugins内每个stateKey如spec、layout、configs都对应系统中独立命名空间的状态切片各个键协同工作构成该命名空间的完整数据流。System 会传递给每个插件每个插件都会被传入正在构建中的system引用。假设我们有一个插件NormalPlugin它在normal命名空间下暴露了一个doStuffaction那么另一个插件可以这样引用它const ExtendingPlugin function(system) { return { statePlugins: { extending: { actions: { doExtendedThings: function(...args) { // 你可以在里面做其他事情 return system.normalActions.doStuff(...args) } } } } } }可以看到只要NormalPlugin在ExtendingPlugin之前被编译上述代码就能正常工作。插件系统没有任何内置的依赖管理机制。因此如果你创建的插件依赖另一个插件保证被依赖的插件后于依赖它的插件加载是你自己的责任。加载顺序由你传入presets与plugins配置项的顺序决定见下文预设与加载顺序。从 src/core/system.js 的实现可以看到system是一个绑定好的顶层对象包含getStore、getComponents、getState、getConfigs、Im、React以及所有已绑定的 actions、selectors、fn、configs 和rootInjects见getRootInjects、buildSystem这解释了为什么插件可以拿到system.normalActions.doStuff这样的调用路径。接口详解Actions动作Action 接口用于在系统某段状态state内创建新的 Redux action creatorconst MyActionPlugin () { return { statePlugins: { example: { actions: { updateFavoriteColor: (str) { return { type: EXAMPLE_SET_FAV_COLOR, payload: str } } } } } } }定义之后你可以在任何能拿到system引用的地方使用它// 别处 system.exampleActions.updateFavoriteColor(blue)该 action creator 会以exampleActions.updateFavoriteColor的形式暴露给容器组件container components。当它被调用时返回值应当是一个 Flux Standard Action即含type与payload的普通对象会被传给example命名空间的 reducer下一节定义。关于 Redux 中 action 概念的更多信息可参考 Redux 官方文档的 Actions 部分。从实现层面看src/core/system.js 的getBoundActions会递归处理每个命名空间下的 action 创建器用bindActionCreators绑定dispatch并用wrapWithTryCatch包裹异常——若 action creator 抛出异常会被序列化为NEW_THROWN_ERR类型的错误 action 而不是直接崩溃。Reducers归约器Reducer 接收状态state是一个 Immutable.js Map和一个 action然后返回新的状态。Reducer 必须以它们处理的action type 名称注册到系统里本例中是EXAMPLE_SET_FAV_COLORconst MyReducerPlugin function(system) { return { statePlugins: { example: { reducers: { EXAMPLE_SET_FAV_COLOR: (state, action) { // 你只操作当前命名空间下的状态即 example // 所以你可以放心操作不用担心 /其他/ 命名空间 return state.set(favColor, action.payload) } } } } } }源码佐证在 src/core/system.js 的makeReducer中系统会根据action.type查找对应的 reducer 函数只把 action 交给匹配的命名空间 reducer 处理function makeReducer(reducerObj, getSystem) { return (state new Map(), action) { if(!reducerObj) return state let redFn (reducerObj[action.type]) if(redFn) { const res wrapWithTryCatch(redFn, getSystem)(state, action) return res null ? state : res // 出错时保持原状态不变 } return state } }Selectors选择器选择器Selector用于从自己的命名空间状态中获取或派生数据。它能把逻辑集中在一处优于把状态数据直接传给组件const MySelectorPlugin function(system) { return { statePlugins: { example: { selectors: { myFavoriteColor: (state) state.get(favColor) } } } } }你还可以使用 Reselect 库来**记忆化memoize**选择器。对于会被频繁调用的选择器官方推荐这样做因为 Reselect 会自动缓存选择器结果import { createSelector } from reselect const MySelectorPlugin function(system) { return { statePlugins: { example: { selectors: { // 这个选择器在针对某个 state 值运行一次之后会被记忆化 myFavoriteColor: createSelector((state) state.get(favColor)) } } } } }定义之后在任意能拿到system引用的地方调用system.exampleSelectors.myFavoriteColor() // 为你从 state 中取回 favColor实现细节src/core/system.js 的getBoundSelectors系统会把每个选择器绑定到对应命名空间的子状态上getState().getIn(stateName)即你写选择器时拿到的state已经只是example这一段的 Immutable Map若选择器返回一个函数系统还会把system传给该函数以支持高级用法。Components组件你可以提供一组组件components注册进系统。务必注意组件的键名因为之后你要用这些名字在其他地方引用它们class HelloWorldClass extends React.Component { render() { return h1Hello World!/h1 } } const MyComponentPlugin function(system) { return { components: { HelloWorldClass: HelloWorldClass // 组件也可以只是函数这些被称为无状态组件 HelloWorldStateless: () h1Hello World!/h1, } } }// 别处 const HelloWorldStateless system.getComponent(HelloWorldStateless) const HelloWorldClass system.getComponent(HelloWorldClass)取消组件你也可以通过创建一个总是返回null的无状态组件来取消任何你不想要的组件例如隐藏 Info 区域const NeverShowInfoPlugin function(system) { return { components: { info: () null } } }关于getComponent的参数顺序如果你不想在组件不存在于系统中时收到警告可以使用config.failSilently。注意getComponent的第二个参数false表示不需要容器包装第三个参数是用于抑制缺失组件警告的配置对象const thisVariableWillBeNull getComponent(not_real, false, { failSilently: true })getComponent的真实实现在 src/core/plugins/view/root-injects.jsx当传入container为true或root时会用withConnect把整个系统映射为 props 传给组件即容器组件当container为假值时直接返回裸组件。找不到组件且未设置failSilently时会通过system.log.warn输出警告并返回null。Wrap-Actions包装动作Wrap Actions 允许你覆盖系统中某个动作的行为。它们是函数工厂签名是(oriAction, system) (...args) result。关键点Wrap Action 的第一个参数是oriAction即被包装的原动作。调用oriAction是你的责任——如果不调用它原动作就不会触发这个机制非常适合条件性覆盖内置行为或监听动作// 说明在真实的 Swagger UI 中updateSpec 已在核心代码中定义 // 这里只是为了讲清楚幕后发生了什么 const MySpecPlugin function(system) { return { statePlugins: { spec: { actions: { updateSpec: (str) { return { type: SPEC_UPDATE_SPEC, payload: str } } } } } } } // 这个插件允许你监听内存中 spec 的变化 const MyWrapActionPlugin function(system) { return { statePlugins: { spec: { wrapActions: { updateSpec: (oriAction, system) (str) { // 在这里你可以把值交给 Swagger UI 之外的某个函数 console.log(Here is my API definition, str) return oriAction(str) // 别忘了否则 Swagger UI 不会更新 } } } } } }仓库真实用例在 deep-linking 插件 中就通过wrapActions包装了configs命名空间的loaded动作在原始逻辑执行后解析 URL hash 实现深链接// src/core/plugins/deep-linking/index.js wrapActions: { loaded: (ori, system) (...args) { ori(...args) const hash decodeURIComponent(window.location.hash) system.layoutActions.parseDeepLinkHash(hash) } }实现层面src/core/system.js 的getWrappedAndBoundActions多个 wrap 会被收集成数组并按顺序reduce串联形成一层套一层的调用链若 wrap 未返回函数会抛出TypeError。Wrap-Selectors包装选择器Wrap Selectors 允许你覆盖系统中某个选择器的行为。它们是函数工厂签名是(oriSelector, system) (state, ...args) result。这个接口非常适合控制流入组件的数据。核心代码中就用它来根据 API 定义的版本禁用某些选择器。import { createSelector } from reselect // 说明在真实的 Swagger UI 中url 选择器已经定义 // 这里只是为了讲清楚幕后发生了什么 const MySpecPlugin function(system) { return { statePlugins: { spec: { selectors: { url: createSelector( state state.get(url) ) } } } } } const MyWrapSelectorsPlugin function(system) { return { statePlugins: { spec: { wrapSelectors: { url: (oriSelector, system) (state, ...args) { console.log(someone asked for the spec url!!! it is, state.get(url)) // 你可以在这里返回其他值…… // 但这里我们保持默认行为 return oriSelector(state, ...args) } } } } } }state参数是该命名空间下的子状态...args是调用选择器时传入的额外参数。实现细节见 src/core/system.js 的getWrappedAndBoundSelectors包装后的选择器会以getState().getIn(stateName)作为第一个参数被调用同样支持多个 wrap 的链式串联。Wrap-Components包装组件Wrap Components 允许你覆盖系统中注册的组件。它们是函数工厂签名为(OriginalComponent, system) props ReactElement如果你更愿意提供 React 组件类(OriginalComponent, system) ReactClass也同样可行。基础示例——在 Info 组件上方插入内容const MyWrapBuiltinComponentPlugin function(system) { return { wrapComponents: { info: (Original, system) (props) { return div h3Hello world! I am above the Info component./h3 Original {...props} / /div } } } }完整示例——包含一个将被包装的组件并给出函数式与类式两种包装写法///// 从一个插件中覆盖组件 // 这是我们正常、未修改的组件。 const MyNumberDisplayPlugin function(system) { return { components: { NumberDisplay: ({ number }) span{number}/span } } } // 这是一个定义为函数的组件包装器。 const MyWrapComponentPlugin function(system) { return { wrapComponents: { NumberDisplay: (Original, system) (props) { if(props.number 10) { return div h3Warning! Big number ahead./h3 Original {...props} / /div } else { return Original {...props} / } } } } } // 或者这是同一个组件包装器定义为类。 const MyWrapComponentPlugin function(system) { return { wrapComponents: { NumberDisplay: (Original, system) class WrappedNumberDisplay extends React.component { render() { if(props.number 10) { return div h3Warning! Big number ahead./h3 Original {...props} / /div } else { return Original {...props} / } } } } } }仓库真实用例同样是 deep-linking 插件用wrapComponents包装了operation与OperationTag组件使其渲染时带上深链接跳转能力// src/core/plugins/deep-linking/index.js wrapComponents: { operation: OperationWrapper, OperationTag: OperationTagWrapper, }实现原理src/core/system.js 的systemExtend与getComponentswrapComponents中的包装函数会被合并进对应组件名的数组getComponents(component)会对数组执行reduce把每个包装器依次应用到原始组件上wrapper(ori, system)从而形成组件装饰链。rootInjects顶层注入rootInjects接口允许你把值注入系统的顶层。它接收一个对象该对象会在运行时与系统顶层对象合并const MyRootInjectsPlugin function(system) { return { rootInjects: { myConstant: 123, myMethod: (...params) console.log(...params) } } }注册后system.myConstant与system.myMethod即可全局访问。源码层面src/core/system.js 的getRootInjects会把内置的getSystem、getStore、getComponents、getState、getConfigs、Im、React等与this.system.rootInjects合并getRootInjects() { return Object.assign({ getSystem: this.getSystem, getStore: this.getStore.bind(this), getComponents: this.getComponents.bind(this), getState: this.getStore().getState, getConfigs: this._getConfigs.bind(this), Im, React }, this.system.rootInjects || {}) }afterLoad加载后回调afterLoad插件方法允许你在插件注册完成之后获取 system 的引用。核心代码用它来挂载由绑定的选择器或动作驱动的方法你也可以用它执行需要插件就绪后才能运行的逻辑例如从远程端点获取初始数据并交给插件创建的 action。插件上下文绑定到this虽然未被文档化但下面是一个如何把绑定动作挂载为顶层方法的示例const MyMethodProvidingPlugin function() { return { afterLoad(system) { // 此时你的 actions 已经被绑定进系统 // 所以你可以对它们做任何事 this.rootInjects this.rootInjects || {} this.rootInjects.myMethod system.exampleActions.updateFavoriteColor }, statePlugins: { example: { actions: { updateFavoriteColor: (str) { return { type: EXAMPLE_SET_FAV_COLOR, payload: str } } } } } } }实现细节src/core/system.js 的callAfterLoad系统注册插件后会对每个插件调用afterLoadthis指向当前插件的返回值对象若afterLoad向this.rootInjects写入了新内容callAfterLoad返回needAnotherRebuild为真register会再次调用buildSystem重建系统确保新注入的顶层方法被合并进去。fn工具函数fn接口允许你向系统添加辅助函数供各处使用import leftPad from left-pad const MyFnPlugin function(system) { return { fn: { leftPad: leftPad } } }注册后即可在系统内以system.fn.leftPad(...)调用。在 src/core/system.js 中getFn()会把this.system.fn暴露为{ fn: this.system.fn }与 actions、selectors 一起被合并进 boundSystem。预设Presets与加载顺序预设Preset是插件数组通过presets配置项提供给 Swagger UI。所有预设内的插件会在plugins配置项提供的插件之前被编译。例如const MyPreset [FirstPlugin, SecondPlugin, ThirdPlugin] SwaggerUI({ presets: [ MyPreset ] })重要默认情况下Swagger UI 会包含内部ApisPreset其中含一组提供基础功能的插件。如果你指定了自己的presets需要手动把ApisPreset加进去SwaggerUI({ presets: [ SwaggerUI.presets.apis, MyAmazingCustomPreset ] })需要在添加其他预设时手动提供apis预设是 Swagger UI 早期设计遗留的产物可能会在下一个大版本中移除。从 src/core/presets/apis/index.js 可以看到ApisPreset的真实组成// src/core/presets/apis/index.js export default function PresetApis() { return [ BasePreset, OpenAPI30Plugin, JSONSchema202012Plugin, JSONSchema202012SamplesPlugin, OpenAPI31Plugin, OpenAPI32Plugin, // Load LAST to override previous versions ] }而BasePresetsrc/core/presets/base/index.js则包含了SpecPlugin、AuthPlugin、LayoutPlugin、ConfigsPlugin、DeepLinkingPlugin、FilterPlugin、SafeRenderPlugin等二十余个基础插件——这就是为什么加载顺序如此重要后加载的 OpenAPI 版本插件如 OpenAPI32Plugin会通过 wrap 机制覆盖先前版本的行为注释明确写着 Load LAST to override previous versions。入口处src/core/index.js会把presets与plugins统一交给系统注册并通过SwaggerUI.presets、SwaggerUI.plugins暴露内置预设与插件供你组合使用。实战如何注册一个自定义插件结合前面的全部知识一个完整的自定义插件注册流程如下import SwaggerUI from swagger-ui // 1. 定义插件 const MyCustomPlugin function(system) { return { statePlugins: { example: { actions: { updateFavoriteColor: (str) ({ type: EXAMPLE_SET_FAV_COLOR, payload: str }) }, reducers: { EXAMPLE_SET_FAV_COLOR: (state, action) state.set(favColor, action.payload) }, selectors: { myFavoriteColor: (state) state.get(favColor) } } }, components: { HelloWorld: () h1Hello World!/h1 }, wrapComponents: { info: (Original, system) (props) ( div h3自定义插件的头部/h3 Original {...props} / /div ) } } } // 2. 通过 plugins 配置项注册记得保留 apis 预设 SwaggerUI({ url: https://petstore.swagger.io/v2/swagger.json, dom_id: #swagger-ui, presets: [SwaggerUI.presets.apis], plugins: [MyCustomPlugin] })小结Swagger UI 的插件 API 是一条完整、自洽的扩展链路新增能力用statePluginsactionsreducersselectors管理自己的状态切片用components注册 UI用fn挂工具函数用rootInjects注入顶层 API改造现有行为用wrapActions监听/覆盖动作用wrapSelectors控制流入组件的数据用wrapComponents装饰任意内置组件生命周期钩子用afterLoad在插件就绪后执行初始化逻辑。所有接口的实现都集中在 src/core/system.js插件编译、绑定与包装链入口注册在 src/core/index.js内置插件组成可在 src/core/presets/apis/index.js 与 src/core/presets/base/index.js 中查阅深链接插件 src/core/plugins/deep-linking/index.js 则是wrapActions与wrapComponents的教科书级范例。结合本文的示例与源码路径你可以放心地在此基础上构建自己的 Swagger UI 定制化方案。【免费下载链接】swagger-uiSwagger UI is a collection of HTML, JavaScript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.项目地址: https://gitcode.com/GitHub_Trending/sw/swagger-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询