插件
插件是一个函数,它返回一个对象——更具体地说,该对象可以包含增强和修改 Swagger UI 功能的函数和组件。
注意:语义版本控制
Swagger UI 的内部 API 不属于我们的公共契约,这意味着它们可以在不更改主要版本的情况下进行更改。
如果您的自定义插件包装、扩展、覆盖或使用任何内部核心 API,我们建议在您的应用程序中指定 Swagger UI 的特定次要版本,因为它们在补丁版本之间不会改变。
例如,如果您通过 NPM 安装 Swagger UI,您可以使用波浪号来做到这一点
1{2 "dependencies": {3 "swagger-ui": "~3.11.0"4 }5}
格式
插件返回值可以包含以下任何键,其中 stateKey
是状态片段的名称
1{2 statePlugins: {3 [stateKey]: {4 actions,5 reducers,6 selectors,7 wrapActions,8 wrapSelectors9 }10 },11 components: {},12 wrapComponents: {},13 rootInjects: {},14 afterLoad: (system) => {},15 fn: {},16}
系统提供给插件
假设我们有一个插件 NormalPlugin
,它在 normal
状态命名空间下暴露了一个 doStuff
动作。
1const ExtendingPlugin = function(system) {2 return {3 statePlugins: {4 extending: {5 actions: {6 doExtendedThings: function(...args) {7 // you can do other things in here if you want8 return system.normalActions.doStuff(...args)9 }10 }11 }12 }13 }14}
正如您所看到的,每个插件都传递了对正在构建的 system
的引用。只要 NormalPlugin
在 ExtendingPlugin
之前编译,这将没有任何问题地工作。
插件系统中没有内置的依赖管理,因此如果您创建了一个依赖于另一个插件的插件,您有责任确保依赖插件在被依赖插件之后加载。
接口
动作
1const MyActionPlugin = () => {2 return {3 statePlugins: {4 example: {5 actions: {6 updateFavoriteColor: (str) => {7 return {8 type: "EXAMPLE_SET_FAV_COLOR",9 payload: str10 }11 }12 }13 }14 }15 }16}
一旦定义了动作,您就可以在任何可以获取系统引用的地方使用它
1// elsewhere2system.exampleActions.updateFavoriteColor("blue")
Action 接口允许在 Swagger UI 系统中某个状态片段内创建新的 Redux action creators。
此 action creator 函数将作为 exampleActions.updateFavoriteColor
暴露给容器组件。当此 action creator 被调用时,返回值(应为 Flux Standard Action)将传递给 example
reducer,我们将在下一节中定义它。
有关 Redux 中动作概念的更多信息,请参阅 Redux 动作文档。
Reducers
Reducers 接收一个状态(这是一个 Immutable.js map)和一个动作,然后返回一个新状态。
Reducers 必须以它们处理的动作类型的名称提供给系统,在本例中是 EXAMPLE_SET_FAV_COLOR
。
1const MyReducerPlugin = function(system) {2 return {3 statePlugins: {4 example: {5 reducers: {6 "EXAMPLE_SET_FAV_COLOR": (state, action) => {7 // you're only working with the state under the namespace, in this case "example".8 // So you can do what you want, without worrying about /other/ namespaces9 return state.set("favColor", action.payload)10 }11 }12 }13 }14 }15}
选择器
选择器进入其命名空间的状态,从状态中检索或派生数据。
它们是保持逻辑在一个地方的简单方法,并且比直接将状态数据传递给组件更受青睐。
1const MySelectorPlugin = function(system) {2 return {3 statePlugins: {4 example: {5 selectors: {6 myFavoriteColor: (state) => state.get("favColor")7 }8 }9 }10 }11}
您还可以使用 Reselect 库来记忆(memoize)您的选择器,这对于任何将大量使用的选择器都推荐这样做,因为 Reselect 会自动为您记忆选择器调用
1import { createSelector } from "reselect"2
3const MySelectorPlugin = function(system) {4 return {5 statePlugins: {6 example: {7 selectors: {8 // this selector will be memoized after it is run once for a9 // value of `state`10 myFavoriteColor: createSelector((state) => state.get("favColor"))11 }12 }13 }14 }15}
一旦定义了选择器,您就可以在任何可以获取系统引用的地方使用它
1system.exampleSelectors.myFavoriteColor() // gets `favColor` in state for you
组件
您可以提供一个组件映射以集成到系统中。
请注意您提供的组件的键名,因为您需要在其他地方使用这些名称来引用组件。
1class HelloWorldClass extends React.Component {2 render() {3 return <h1>Hello World!</h1>4 }5}6
7const MyComponentPlugin = function(system) {8 return {9 components: {10 HelloWorldClass: HelloWorldClass11 // components can just be functions, these are called "stateless components"12 HelloWorldStateless: () => <h1>Hello World!</h1>,13 }14 }15}
1// elsewhere2const HelloWorldStateless = system.getComponent("HelloWorldStateless")3const HelloWorldClass = system.getComponent("HelloWorldClass")
您还可以通过创建一个始终返回 null
的无状态组件来“抵消”任何您不想要的组件
1const NeverShowInfoPlugin = function(system) {2 return {3 components: {4 info: () => null5 }6 }7}
如果当系统中不存在组件时您不希望出现警告,可以使用 config.failSilently
。
请注意 getComponent
参数的顺序。在下面的示例中,布尔值 false
指的是是否存在容器,第三个参数是用于抑制缺少组件警告的配置对象。
1const thisVariableWillBeNull = getComponent("not_real", false, { failSilently: true })
Wrap-Actions
Wrap Actions 允许您覆盖系统中某个动作的行为。
它们是具有签名 (oriAction, system) => (...args) => result
的函数工厂。
Wrap Action 的第一个参数是 oriAction
,它正在被包装的动作。您有责任调用 oriAction
- 如果不调用,原始动作将不会触发!
此机制对于有条件地覆盖内置行为或监听动作非常有用。
1// FYI: in an actual Swagger UI, `updateSpec` is already defined in the core code2// it's just here for clarity on what's behind the scenes3const MySpecPlugin = function(system) {4 return {5 statePlugins: {6 spec: {7 actions: {8 updateSpec: (str) => {9 return {10 type: "SPEC_UPDATE_SPEC",11 payload: str12 }13 }14 }15 }16 }17 }18}19
20// this plugin allows you to watch changes to the spec that is in memory21const MyWrapActionPlugin = function(system) {22 return {23 statePlugins: {24 spec: {25 wrapActions: {26 updateSpec: (oriAction, system) => (str) => {27 // here, you can hand the value to some function that exists outside of Swagger UI28 console.log("Here is my API definition", str)29 return oriAction(str) // don't forget! otherwise, Swagger UI won't update30 }31 }32 }33 }34 }35}
Wrap-Selectors
Wrap Selectors 允许您覆盖系统中某个选择器的行为。
它们是具有签名 (oriSelector, system) => (state, ...args) => result
的函数工厂。
此接口对于控制流入组件的数据非常有用。我们在核心代码中使用它来根据 API 定义的版本禁用选择器。
1import { createSelector } from 'reselect'2
3// FYI: in an actual Swagger UI, the `url` spec selector is already defined4// it's just here for clarity on what's behind the scenes5const MySpecPlugin = function(system) {6 return {7 statePlugins: {8 spec: {9 selectors: {10 url: createSelector(11 state => state.get("url")12 )13 }14 }15 }16 }17}18
19const MyWrapSelectorsPlugin = function(system) {20 return {21 statePlugins: {22 spec: {23 wrapSelectors: {24 url: (oriSelector, system) => (state, ...args) => {25 console.log('someone asked for the spec url!!! it is', state.get('url'))26 // you can return other values here...27 // but let's just enable the default behavior28 return oriSelector(state, ...args)29 }30 }31 }32 }33 }34}
Wrap-Components
Wrap Components 允许您覆盖系统中注册的组件。
Wrap Components 是具有签名 (OriginalComponent, system) => props => ReactElement
的函数工厂。如果您更愿意提供一个 React 组件类,(OriginalComponent, system) => ReactClass
也同样有效。
1const MyWrapBuiltinComponentPlugin = function(system) {2 return {3 wrapComponents: {4 info: (Original, system) => (props) => {5 return <div>6 <h3>Hello world! I am above the Info component.</h3>7 <Original {...props} />8 </div>9 }10 }11 }12}
这是另一个示例,其中包含将被包装的组件的代码示例
1///// Overriding a component from a plugin2
3// Here's our normal, unmodified component.4const MyNumberDisplayPlugin = function(system) {5 return {6 components: {7 NumberDisplay: ({ number }) => <span>{number}</span>8 }9 }10}11
12// Here's a component wrapper defined as a function.13const MyWrapComponentPlugin = function(system) {14 return {15 wrapComponents: {16 NumberDisplay: (Original, system) => (props) => {17 if(props.number > 10) {18 return <div>19 <h3>Warning! Big number ahead.</h3>20 <Original {...props} />21 </div>22 } else {23 return <Original {...props} />24 }25 }26 }27 }28}29
30// Alternatively, here's the same component wrapper defined as a class.31const MyWrapComponentPlugin = function(system) {32 return {33 wrapComponents: {34 NumberDisplay: (Original, system) => class WrappedNumberDisplay extends React.component {35 render() {36 if(props.number > 10) {37 return <div>38 <h3>Warning! Big number ahead.</h3>39 <Original {...props} />40 </div>41 } else {42 return <Original {...props} />43 }44 }45 }46 }47 }48}
rootInjects
rootInjects
接口允许您在系统的顶层注入值。
此接口接受一个对象,该对象将在运行时与顶层系统对象合并。
1const MyRootInjectsPlugin = function(system) {2 return {3 rootInjects: {4 myConstant: 123,5 myMethod: (...params) => console.log(...params)6 }7 }8}
afterLoad
afterLoad
插件方法允许您在插件注册后获取系统引用。
此接口在核心代码中用于附加由绑定选择器或动作驱动的方法。您还可以使用它来执行需要您的插件已经准备就绪的逻辑,例如从远程端点获取初始数据并将其传递给您的插件创建的动作。
插件上下文,它绑定到 this
,是未文档化的,但下面是一个如何将绑定动作作为顶级方法附加的示例
1const MyMethodProvidingPlugin = function() {2 return {3 afterLoad(system) {4 // at this point in time, your actions have been bound into the system5 // so you can do things with them6 this.rootInjects = this.rootInjects || {}7 this.rootInjects.myMethod = system.exampleActions.updateFavoriteColor8 },9 statePlugins: {10 example: {11 actions: {12 updateFavoriteColor: (str) => {13 return {14 type: "EXAMPLE_SET_FAV_COLOR",15 payload: str16 }17 }18 }19 }20 }21 }22}
fn
fn 接口允许您向系统添加辅助函数,以便在其他地方使用。
1import leftPad from "left-pad"2
3const MyFnPlugin = function(system) {4 return {5 fn: {6 leftPad: leftPad7 }8 }9}