プラグイン
プラグインはオブジェクトを返す関数です。より具体的には、そのオブジェクトには 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}
システムはプラグインに提供されます
normal
状態の名前空間の下に doStuff
アクションを公開するプラグイン NormalPlugin
があると仮定しましょう。
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 Standard Action であるべきです)は、次のセクションで定義する example
リデューサーに渡されます。
Redux のアクションの概念の詳細については、Redux Actions ドキュメントを参照してください。
リデューサー
リデューサーは状態(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}