最初のフロントエンド会議は1か月前にRaiffeisenBankで開催されました。ほんの数日でトピック「Recomposeを使用した機能パターンを備えた高次コンポーネント」に関するプレゼンテーションを準備し、レポートの1週間前にインターネットでRecomposeに関する情報を簡単に取得したため、何も準備できませんでしたプレゼンテーション資料の最後に彼の連絡先の詳細さえ書いていないので、あまり良くありませんでした。 「あなたのスライドはどこで見ることができますか?」という質問に対して、私はためらいましたが答えませんでした。
状況を修正し、参考資料を作成し、一連の記事をリリースして、私のプレゼンテーションに充てられたすべてを詳しく説明したいと思います。
再構成ライブラリのドキュメントは非常に貧弱ですが、説明的な例が含まれていないため、誰もが理解できるとは限りません。 このギャップを埋めようとし、サイクルの終わりにRxJSに触れます。
この記事では、コンポーネントを構成および分解する方法について説明し、簡単な短い質問と定義から始め、ステートレスコンポーネントがどのように見え、次にステートフルコンポーネントになるかを例で示します。
そして最初の質問は「状態のないコンポーネントの名前は何ですか?」です。
![](https://habrastorage.org/webt/et/ud/ij/etudijqah4_n63t6byltmpwms-s.png)
ステートレスコンポーネントは、状態を持たないコンポーネントです。
例(矢印関数):
const stateComponent = ({name}) => <div>{name}</div>
![](https://habrastorage.org/webt/71/6a/oc/716aocw31ff7s8v5al84fg9fvy8.png)
2番目の質問は、「状態を持つコンポーネントの名前は何ですか?」です。
![](https://habrastorage.org/webt/bk/_i/w3/bk_iw3mmn5cx4bdbyk0mehty_uc.png)
ステートフルコンポーネントは、ステートを持つコンポーネントです。
ここで、ステートフルコンポーネントでステートを使用し、ライフサイクルメソッドを使用する必要がないことを想像してください。 より正確には、すべてを使用できますが、取り出してから別のコンポーネントで再利用できます。 そして、これはHOCの助けを借りて行われます。
HOCとは何ですか?
高次コンポーネントは、コンポーネントを取得し、改善された新しいコンポーネントを返す関数です。
抽象的には、次のようになります。
![](https://habrastorage.org/webt/ks/kc/op/kskcopwtrgnnboiveivmopby1w4.png)
ここで、関数がarg1とarg2を取り、 Componentを取り、新しいEnhanced EnhancedComponentを返す関数を返すことがわかります。
Hocには次の2つのタイプがあります。
1.ステートレス
![](https://habrastorage.org/webt/vn/z7/8k/vnz78kuhx-gtf34rjcpd4d3pjdm.png)
2.ステートフル
![](https://habrastorage.org/webt/qt/kc/hq/qtkchqenwdjafy_yryytz2fodna.png)
ステートフルコンポーネントには、テンプレートだけでなくライフサイクルメソッドも指定できるという利点があります。
使用例:
![](https://habrastorage.org/webt/ja/pj/bx/japjbxjsu8zznfkroqrf-ybomei.png)
ここでは、HOCを使用して、Bobコンポーネントが作成されます。 HOC-aの最初の部分では、{name:“ Bob”}オブジェクトを引数として渡し、2番目の部分では、コンポーネントに基づいて、「改善された」ボブコンポーネントを取得します。
→ 参照により高次のコンポーネントを使用する実例
![](https://habrastorage.org/webt/ls/rd/bx/lsrdbxljjvt7chnn1w1bnfzcarc.png)
再構成
再構成は、高次のコンポーネントが事前に作成されたライブラリです。 アイデアは、ステートレスコンポーネントを記述し、コードを論理部分に分割することです。 既製のHOCを使用すると、ライフサイクルメソッドを分離し、ビジネスロジックを取り出し、イベントハンドラーをコンポーネントの内部ではなく外部にアタッチできます。 同時に、使用したものをすべて再利用し、基本コンポーネントに基づいて独自のコンポーネントを作成します。
再構成はAndrew Clarkによって作成されました。詳細については、公式リポジトリgithub.com/acdlite/recomposeを参照してください。
次に、「再構成」という単語を見て、最初の2文字を削除します。 composeメソッドを取得します。このメソッドは、いくつかのHOCを適用するために非常によく使用されます。
作曲とは何かを見てみましょう。
![](https://habrastorage.org/webt/oj/9s/ga/oj9sga1wbmi-l6b73q5xbtqyeu4.png)
引数を取る関数があるとします:
![](https://habrastorage.org/webt/go/np/cx/gonpcxsitnvoqy2thbsljntrlky.png)
しかし、ある関数を別の関数で実行したい場合はどうでしょうか:
![](https://habrastorage.org/webt/wu/6e/_y/wu6e_y_zvhvlxgpmmuba-cq4_0g.png)
そして、実行結果を3番目の関数にも埋め込みます。
![](https://habrastorage.org/webt/d3/sm/tu/d3smtuzrsrjw0msroykbdlmr3dy.png)
1つの引数に対して順番に実行したい関数が20個あると想像してください。 悪夢のような営巣はどうなるでしょう。 ここで、composeメソッドが役立ちます。
![](https://habrastorage.org/webt/pl/hw/ty/plhwtyz8ytclf8jpc1s18es9kfk.png)
Composeは、最初の部分で関数のリストを受け取り、2番目の引数で関数が実行されます。 さらに、関数の実行順序は最後から始まります:
- func1
- func2
- func3
hocは、コンポーネントを取得して新しいコンポーネントを返す関数であることを思い出してください。 これは関数なので、composeを使用して、1つのコンポーネントに複数のhocを適用できます。 そして、 composeがrecomposeの setDisplayNameおよびsetPropTypesメソッドと相互作用する方法の簡単な例を考えてみましょう 。
![](https://habrastorage.org/webt/ff/ox/j2/ffoxj2gopirup1zfmvkf_64qrm0.png)
setDisplayName-文字列を受け取り、コンポーネントのdisplayName(表示名)を設定します。
setPropTypes-他のHOCまたは引数自体で再利用できる小道具を持つオブジェクトを受け入れます。
→ ライブサンプルリンク
const { Component, PropTypes } = React; const { compose, setDisplayName, setPropTypes } = Recompose; const enhance = compose( setDisplayName('User'), setPropTypes({ name: React.PropTypes.string.isRequired, status: React.PropTypes.string }) ); const User = enhance(({ name, status, dispatch }) => <div className="User" onClick={ () => dispatch({ type: "USER_SELECTED" }) }> { name }: { status } </div> ); console.log(User.displayName); ReactDOM.render( <User name="Tim" status="active" />, document.getElementById('main') );
次の手順:
1. setDisplayNameメソッドとsetPropTypesメソッドをRecomposeライブラリからインポートしますが、codepen.ioの制限により、ここではインポートの代わりに構造化が使用されます。 拡張変数には、高次コンポーネントsetDisplayNameおよびsetPropTypesを記述します。
const { Component, PropTypes } = React; const { compose, setDisplayName, setPropTypes } = Recompose; const { connect } = Redux(); const enhance = compose( setDisplayName('User'), setPropTypes({ name: React.PropTypes.string.isRequired, status: React.PropTypes.string }), );
2.次に、ステートレスコンポーネントに拡張メソッドを適用します
const User = enhance(({ name, status, dispatch }) => <div className="User"> { name }: { status } </div> );
3.レンダリング
ReactDOM.render( <User name="Tim" status="active" />, document.getElementById('main') );
ここで、composeメソッドでは、3つのHOCで構成される最初の部分のみを指定し、拡張変数に記録したが、2番目の部分はまったく指定しなかったことに注意してください。
これを行った理由を理解するには、composeメソッドがどのように機能するかを理解する必要があります。
これが高階関数であることを理解してください:
高階関数は、他の関数を受け入れて新しい関数を返す関数です。
次に、composeメソッドの操作を簡単に説明します。
function compose(...funcs) { return funcs.reduce((a, b) => (...args) => a(b(...args))) }
- 純粋な関数のリストは、composeメソッドの引数に渡されます。
- 次に、この関数シートはreduceメソッドを使用してソートされます。
- reduceメソッドでは、aとbが引数として渡されます。aはバッテリー関数、bは現在実行中の関数です
- reduceメソッドの関数本体は、関数の配列を最後から反復する再帰関数にすぎません。
withState&withHandlers
withStateは、3つの引数を取るhocです:
1. stateName-アクセス可能な状態の名前。
2. stateUpdaterName-状態を更新する純粋な関数の名前。
3. initialState-初期状態(初期状態);
例を考えてみましょう。
![](https://habrastorage.org/webt/ig/y6/nw/igy6nwx7izrzpktpbsy-kp-jmdg.png)
StatusとTooltipの2つのコンポーネントがあるとします。これらの2つのコンポーネントには、異なる状況下でのみ同じ状態を変更する状態といくつかのイベントハンドラーがあることがわかります。 StatusListは、コンポーネントをクリックするとStatusコンポーネントに表示され、ブロックにカーソルを合わせると、Tooltipコンポーネントにテキストが表示されます。
![](https://habrastorage.org/webt/ec/cm/jz/eccmjzr9or6xxm_ug6_aoy1aviy.png)
これらのコンポーネントの状態はまったく同じであり、初期状態も同じです。
![](https://habrastorage.org/webt/x9/ze/vn/x9zevnkrcfbhaqybceubsaiqaf4.png)
イベントハンドラは何をしますか? 各メソッドは独自の方法で同じフラグを処理しますが、そのような違いがあっても、1つのHOCに組み合わせることができます。
ここで、コンポーネントから状態ハンドラーとイベントハンドラーを取得できることを想像してください。 どうやって? 答えは簡単です:「再構成ライブラリのHOC」!
![](https://habrastorage.org/webt/6v/wg/xr/6vwgxrnqmylgyzm5_rj5tor-bxq.png)
これらのコンポーネントの唯一の違いはrenderメソッドです。 ただし、ステートレスコンポーネントに移動することは可能です。
さて、サブタイトルwithState&withHandlersに戻り 、最初にwithStateを、次にwithHandlersを。
withState
シンプルなコンポーネントを作成してみましょう。マウスオーバーするとツールチップが表示され、ステータスをクリックするとStatusListが表示されます。
→ ライブサンプルリンク
const { Component } = React; const { compose, withState } = Recompose; // compose withState const StatusList = () => // StatusList - Active <div className="StatusList"> <div>pending</div> <div>inactive</div> <div>active</div> </div>; // hoc withState, // isToggle — , toggle - stateUpdater- // initialState const Status = withState('isToggle', 'toggle', false) (({ status, isToggle, toggle }) => // 'isToggle', 'toggle' <span onClick={ () => toggle(!isToggle) }> {/* event onClick */} { status } { isToggle && <StatusList /> } </span> ); // hoc withState, // isToggle — , toggle - stateUpdater- // initialState const Tooltip = withState('isToggle', 'toggle', false) (({ text, children, isToggle, toggle }) => // 'isToggle', 'toggle' <span> { isToggle && <div className="Tooltip">{ text }</div> } <span onMouseEnter={ () => toggle(true) } onMouseLeave={ () => toggle(false) }>{ children }</span> {/* event- onMouseEnter onMouseLeave */} </span> ); // hoc withState, // isToggle — , toggle - stateUpdater- // initialState const User = ({ name, status }) => <div className="User"> <Tooltip text="Cool Dude!">{ name }</Tooltip>— <Status status={ status } /> </div>; const App = () => <div> <User name="Tim" status="active" /> </div>; ReactDOM.render( <App />, document.getElementById('main') );
withState( 'isToggle'、 'toggle'、false)が2つのコンポーネントで繰り返されていることがわかるので、それをwithToggle変数に入れましょう。
→ ライブサンプルリンク
const { Component } = React; const { compose, withState } = Recompose; // compose withState const StatusList = () => // StatusList - Active <div className="StatusList"> <div>pending</div> <div>inactive</div> <div>active</div> </div>; // hoc withState, // isToggle — , toggle - stateUpdater- // initialState const withToggle = withState('isToggle', 'toggle', false); const Status = withToggle(({ status, isToggle, toggle }) => // 'isToggle', 'toggle' <span onClick={ () => toggle(!isToggle) }> {/* event onClick */} { status } { isToggle && <StatusList /> } </span> ); // hoc withState, // isToggle — , toggle - stateUpdater- // initialState const Tooltip = withToggle(({ text, children, isToggle, toggle }) => // 'isToggle', 'toggle' <span> { isToggle && <div className="Tooltip">{ text }</div> } <span onMouseEnter={ () => toggle(true) } onMouseLeave={ () => toggle(false) }>{ children }</span> {/* event- onMouseEnter, onMouseLeave */} </span> ); // hoc withState, // isToggle — , toggle - stateUpdater- // initialState const User = ({ name, status }) => <div className="User"> <Tooltip text="Cool Dude!">{ name }</Tooltip>— <Status status={ status } /> </div>; const App = () => <div> <User name="Tim" status="active" /> </div>; ReactDOM.render( <App />, document.getElementById('main') );
withHandlersを使用すると、イベントハンドラーをhocにプルし、小道具からコンポーネントを呼び出すことができます。 方法を考えてみましょう
const { Component } = React; const { compose, withState, withHandlers } = Recompose; // compose, withState withHandlers const withToggle = compose( // withState & withHandlers compose withState('toggledOn', 'toggle', false), withHandlers({ // withHandlers // toggle, stateUpdater- show: ({ toggle }) => (e) => toggle(true), hide: ({ toggle }) => (e) => toggle(false), toggle: ({ toggle }) => (e) => toggle((current) => !current) }) ) const StatusList = () => // StatusList - Active <div className="StatusList"> <div>pending</div> <div>inactive</div> <div>active</div> </div>; const Status = withToggle(({ status, toggledOn, toggle }) => <span onClick={ toggle }> { status } { toggledOn && <StatusList /> } </span> ); const Tooltip = withToggle(({ text, children, toggledOn, show, hide }) => <span> { toggledOn && <div className="Tooltip">{ text }</div> } <span onMouseEnter={ show } onMouseLeave={ hide }>{ children }</span> </span> ); const User = ({ name, status }) => <div className="User"> <Tooltip text="Cool Dude!">{ name }</Tooltip>— <Status status={ status } /> </div>; const App = () => <div> <User name="Tim" status="active" /> </div>; ReactDOM.render( <App />, document.getElementById('main') );
→ ライブサンプルリンク
次に、前後のコードがどのように見えるかを見てみましょう。
![](https://habrastorage.org/webt/7l/kg/mz/7lkgmzlg5hoc4vp0k581zhla4kw.png)
![](https://habrastorage.org/webt/i3/yh/m6/i3yhm6pkzi6ms2gfor5nh12xjke.png)
Withreducer
withReducer<S, A>( stateName: string, dispatchName: string, reducer: (state: S, action: A) => S, initialState: S | (ownerProps: Object) => S ): HigherOrderComponent
withReducerはwithStateメソッドに似ており、構造も似ていますが、reducer-a関数を使用して状態が更新されます。 例を考えてみましょう:
→ ライブサンプルリンク
const { Component } = React; const { compose, withReducer, withHandlers } = Recompose; // compose, withReducer withHandlers const withToggle = compose( withReducer('toggledOn', 'dispatch', (state, action) => { switch(action.type) { // case 'SHOW': return true; case 'HIDE': return false; case 'TOGGLE': return !state; default: return state; } }, false), withHandlers({ show: ({ dispatch }) => (e) => dispatch({ type: 'SHOW' }), // action- dispatch hide: ({ dispatch }) => (e) => dispatch({ type: 'HIDE' }), toggle: ({ dispatch }) => (e) => dispatch({ type: 'TOGGLE' }) }) ); const StatusList = () => // StatusList - Active <div className="StatusList"> <div>pending</div> <div>inactive</div> <div>active</div> </div>; const Status = withToggle(({ status, toggledOn, toggle }) => <span onClick={ toggle }> { status } { toggledOn && <StatusList /> } </span> ); const Tooltip = withToggle(({ text, children, toggledOn, show, hide }) => <span> { toggledOn && <div className="Tooltip">{ text }</div> } <span onMouseEnter={ show } onMouseLeave={ hide }>{ children }</span> </span> ); const User = ({ name, status }) => <div className="User"> <Tooltip text="Cool Dude!">{ name }</Tooltip>— <Status status={ status } /> </div>; const App = () => <div> <User name="Tim" status="active" /> </div>;
結論:
- コンポーネントの構成と分解
- ステートレスコンポーネントのみを使用できます
- 高次コンポーネントを使用すると、デコレータに似たものを作成し、コンポーネントに不純物を追加できます
- 小型のHOCユーティリティは、大きくて便利なHOCにコンパイルできます。