插件
插件是一个返回对象的函数 - 更具体地说,该对象可能包含增强和修改 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")
操作接口允许在 Swagger UI 系统中的一块状态内创建新的 Redux 操作创建器。
此操作创建器函数将作为 exampleActions.updateFavoriteColor
公开给容器组件。 调用此操作创建器时,返回值(应该是 Flux 标准操作)将传递给 example
归约器,我们将在下一节中定义它。
有关 Redux 中操作概念的更多信息,请参阅 Redux 操作文档。
归约器
归约器获取状态(这是一个 Immutable.js 映射)和一个操作,然后返回一个新的状态。
必须以它们处理的操作类型的名称(在本例中为 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 库来记忆您的选择器,这对于任何会被大量使用的选择器都是建议的,因为 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
指的是容器的存在,第 3 个参数是用于禁止丢失组件警告的配置对象。
1const thisVariableWillBeNull = getComponent("not_real", false, { failSilently: true })
包装操作
包装操作允许您覆盖系统中操作的行为。
它们是具有签名 (oriAction, system) => (...args) => result
的函数工厂。
包装操作的第一个参数是 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}
包装选择器
包装选择器允许您覆盖系统中选择器的行为。
它们是具有签名 (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}
包装组件
包装组件允许您覆盖系统中注册的组件。
包装组件是具有签名 (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}