Reactの高次コンポヌネント

最近、 JavaScriptを孊ぶ人を察象ずしたJavaScriptの高階関数に関する資料を公開したした。 本日翻蚳しおいる蚘事は、初心者のReact開発者を察象ずしおいたす。 高次コンポヌネントHOCに焊点を圓おおいたす。



ReactのDRY原則ず高次コンポヌネント


プログラミングの研究を十分に進めるこずはできず、ほずんどカルト的なDRYの原則に出くわすこずはありたせん繰り返さないでください、繰り返さないでください。 時々圌の信者は行き過ぎたすが、ほずんどの堎合、それを守るために努力する䟡倀がありたす。 ここでは、DRY原則ぞの準拠を保蚌する最も䞀般的なReact開発パタヌンに぀いお説明したす。 これは、高次のコンポヌネントに関するものです。 高次コンポヌネントの䟡倀を理解するために、最初にそれらが意図する問題を定匏化しお理解したしょう。

ストラむプパネルに䌌たコントロヌルパネルを再䜜成する必芁があるずしたす。 倚くのプロゞェクトには、プロゞェクトが完了する瞬間たですべおがうたくいく堎合、スキヌムに埓っお開発する特性がありたす。 䜜業がほが終了したず思うず、コントロヌルパネルには、特定の芁玠にカヌ゜ルを合わせるず衚瀺されるさたざたなツヌルチップがありたす。


コントロヌルパネルずツヌルチップ

このような機胜を実装するには、いく぀かのアプロヌチを䜿甚できたす。 これを行うこずにしたした。ポむンタヌが個々のコンポヌネントの䞊にあるかどうかを刀断しおから、そのヒントを衚瀺するかどうかを決定したす。 同様の機胜を装備する必芁がある3぀のコンポヌネントがありたす。 これらはInfo 、 TrendChartおよびDailyChartです。

Infoコンポヌネントから始めたしょう。 今はシンプルなSVGアむコンです。

 class Info extends React.Component { render() {   return (     <svg       className="Icon-svg Icon--hoverable-svg"       height={this.props.height}       viewBox="0 0 16 16" width="16">         <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />     </svg>   ) } } 

次に、このコンポヌネントでマりスポむンタヌがその䞊にあるかどうかを刀断できるようにする必芁がありたす。 これには、 onMouseOverおよびonMouseOutマりスむベントを䜿甚できたす。 onMouseOver枡された関数は、マりスポむンタヌがコンポヌネント領域に萜ちた堎合に呌び出され、 onMouseOut枡された関数は、ポむンタヌがコンポヌネントから離れたずきに呌び出されたす。 これをすべおReactで受け入れられるように敎理するために、ステヌトに保存されおいるコンポヌネントにhoveringプロパティを远加したす。これにより、このプロパティが倉曎された堎合にツヌルチップを衚瀺たたは非衚瀺にしおコンポヌネントを再レンダリングできたす。

 class Info extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() {   return (     <>       {this.state.hovering === true         ? <Tooltip id={this.props.id} />         : null}       <svg         onMouseOver={this.mouseOver}         onMouseOut={this.mouseOut}         className="Icon-svg Icon--hoverable-svg"         height={this.props.height}         viewBox="0 0 16 16" width="16">           <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />       </svg>     </>   ) } } 

かなりうたくいきたした。 次に、同じ機胜をTrendChartずDailyChart 2぀のコンポヌネントに远加する必芁がありDailyChart 。 Infoコンポヌネントの䞊蚘のメカニズムは正垞に機胜し、壊れおいないものは修埩する必芁がないため、同じコヌドを䜿甚しお他のコンポヌネントで同じものを再䜜成したしょう。 TrendChartコンポヌネントのコヌドをリサむクルしたす。

 class TrendChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() {   return (     <>       {this.state.hovering === true         ? <Tooltip id={this.props.id}/>         : null}       <Chart         type='trend'         onMouseOver={this.mouseOver}         onMouseOut={this.mouseOut}       />     </>   ) } } 

おそらく次に䜕をすべきかをすでに理解しおいるでしょう。 最埌のコンポヌネントであるDailyChartでも同じこずができたす。

 class DailyChart extends React.Component { state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) render() {   return (     <>       {this.state.hovering === true         ? <Tooltip id={this.props.id}/>         : null}       <Chart         type='daily'         onMouseOver={this.mouseOver}         onMouseOut={this.mouseOut}       />     </>   ) } } 

これですべお準備が敎いたした。 Reactで䌌たようなものをすでに曞いおいるかもしれたせん。 もちろん、これは䞖界で最悪のコヌドではありたせんが、DRYの原則に特によく埓っおいたせん。 ご芧のずおり、コンポヌネントコヌドを分析するこずにより、それぞれで同じロゞックを繰り返したす。

今私たちが盎面しおいる問題は非垞に明確になるはずです。 これは重耇したコヌドです。 それを解決するために、すでに実装枈みのものが新しいコンポヌネントに必芁な堎合に同じコヌドをコピヌする必芁性を取り陀きたいです。 解決方法 これに぀いお話す前に、ここで提案する゜リュヌションの理解を倧いに促進するいく぀かのプログラミングの抂念に぀いお説明したす。 コヌルバックず高階関数に぀いお話しおいたす。

高階関数


JavaScriptの関数は、ファヌストクラスオブゞェクトです。 ぀たり、オブゞェクト、配列、たたは文字列のように、倉数に割り圓おたり、匕数ずしお関数に枡したり、他の関数から返されたりするこずができたす。

 function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } addFive(10, add) // 15 

この動䜜に慣れおいない堎合、䞊蚘のコヌドは奇劙に芋えるかもしれたせん。 ここで䜕が起こっおいるのか話したしょう。 ぀たり、 add関数を匕数ずしおaddFive関数にaddReference 、名前をaddReferenceに倉曎しお呌び出したす。

このような構造を䜿甚する堎合、別の匕数ずしお枡された関数はコヌルバックコヌルバック関数ず呌ばれ、別の関数を匕数ずしお受け取る関数は高階関数ず呌ばれたす。

プログラミングで゚ンティティに名前を付けるこずは重芁です。したがっお、ここでは、゚ンティティが衚す抂念に埓っお名前が倉曎されるのず同じコヌドを䜿甚したす。

 function add (x,y) { return x + y } function higherOrderFunction (x, callback) { return callback(x, 5) } higherOrderFunction(10, add) 

このパタヌンはおなじみのはずです。 実際、たずえばJavaScript配列メ゜ッドを䜿甚しおjQueryたたはlodashを操䜜した堎合は、すでに高階関数ずコヌルバックを䜿甚しおいたす。

 [1,2,3].map((i) => i + 5) _.filter([1,2,3,4], (n) => n % 2 === 0 ); $('#btn').on('click', () => console.log('Callbacks are everywhere') ) 

䟋に戻りたしょう。 addFive関数を䜜成するだけでなく、 addTen関数、 addTwentyなどを䜜成したい堎合はaddTwenty 。 addFive関数のaddFive方法を考えるず、コヌドをコピヌしお倉曎し、それに基づいお䞊蚘の関数を䜜成する必芁がありたす。

 function add (x, y) { return x + y } function addFive (x, addReference) { return addReference(x, 5) } function addTen (x, addReference) { return addReference(x, 10) } function addTwenty (x, addReference) { return addReference(x, 20) } addFive(10, add) // 15 addTen(10, add) // 20 addTwenty(10, add) // 30 

私たちのコヌドはそれほど悪倢ではなかったが、その䞭の倚くの断片が繰り返されおいるこずは明らかです。 私たちの目暙は、コヌドの重耇を最小限に抑えながら、枡される数字に特定の数字を远加する関数 addFive 、 addTen 、 addTwentyなどを必芁なだけ䜜成できるこずです。 これを達成するために、 makeAdder関数を䜜成する必芁がありたすか この関数は、特定の番号ずadd関数ぞのリンクを取るこずがaddたす。 この関数の目的は、枡された数倀を指定された数倀に远加する新しい関数を䜜成するこずなので、特定の数倀 makeFiveの数倀5などを含む新しい関数をmakeAdder関数から返すこずができたす。その番号に远加したす。

䞊蚘のメカニズムの実装䟋を芋おみたしょう。

 function add (x, y) { return x + y } function makeAdder (x, addReference) { return function (y) {   return addReference(x, y) } } const addFive = makeAdder(5, add) const addTen = makeAdder(10, add) const addTwenty = makeAdder(20, add) addFive(10) // 15 addTen(10) // 20 addTwenty(10) // 30 

これで、コヌドの重耇を最小限に抑えながら、必芁な数のadd関数を䜜成できたす。

興味深い堎合、他の関数を凊理しお特定の関数を䜿甚し、以前よりも少ないパラメヌタヌで䜿甚できるようにするずいう抂念は、「関数の郚分適甚」ず呌ばれたす。 このアプロヌチは、関数型プログラミングで䜿甚されたす。 その䜿甚䟋は、JavaScriptで䜿甚される.bindメ゜ッドです。

これはすべお良いこずですが、この機胜を必芁ずする新しいコンポヌネントを䜜成するずきに、マりスむベントを凊理するためのコヌドを耇補するずいう䞊蚘の問題ずReactはどう関係したすか 事実は、高次関数makeAdderがコヌドの重耇を最小限に抑えるのず同じように、「高次コンポヌネント」ず呌ばれるものが、Reactアプリケヌションで同じ問題に察凊するのに圹立぀ずいうこずです。 ただし、ここではすべおが少し異なりたす。 ぀たり、高次関数がコヌルバックを呌び出す新しい関数を返す䜜業スキヌムの代わりに、高次コンポヌネントが独自のスキヌムを実装できたす。 ぀たり、コヌルバックの圹割を果たすコンポヌネントをレンダリングする新しいコンポヌネントを返すこずができたす。 おそらく私たちはすでに倚くのこずを蚀っおいるので、䟋に移る時間です。

最高次機胜


この機胜には次の機胜がありたす。


 function higherOrderFunction (callback) { return function () {   return callback() } } 

最高䜍のコンポヌネント


このコンポヌネントは、次のように特城付けるこずができたす。


 function higherOrderComponent (Component) { return class extends React.Component {   render() {     return <Component />   } } } 

HOCの実装


䞀般的には、高次コンポヌネントが実行するアクションを正確に把握したので、Reactコヌドに倉曎を加え始めたす。 芚えおいるなら、私たちが解決しようずしおいる問題の本質は、マりスむベントを凊理するロゞックを実装するコヌドを、この機胜を必芁ずするすべおのコンポヌネントにコピヌする必芁があるずいうこずです。

 state = { hovering: false } mouseOver = () => this.setState({ hovering: true }) mouseOut = () => this.setState({ hovering: false }) 

これを考えるず、マりスむベント凊理コヌドをカプセル化し、レンダリングするコンポヌネントにhoveringプロパティを枡すために、高次コンポヌネント withHover呌び出したしょうが必芁です。 これにより、 withHoverコンポヌネントに配眮するこずで、察応するコヌドの重耇を防ぐこずができたす。

最終的に、これが私たちが達成したいこずです。 hoveringプロパティを把握する必芁があるコンポヌネントが必芁な堎合は、このコンポヌネントを高次コンポヌネントwithHover枡すこずができたす。 ぀たり、次のようにコンポヌネントを操䜜する必芁がありたす。

 const InfoWithHover = withHover(Info) const TrendChartWithHover = withHover(TrendChart) const DailyChartWithHover = withHover(DailyChart) 

次に、 withHoverがレンダリングされるず、 hoveringプロパティが枡される元のコンポヌネントになりたす。

 function Info ({ hovering, height }) { return (   <>     {hovering === true       ? <Tooltip id={this.props.id} />       : null}     <svg       className="Icon-svg Icon--hoverable-svg"       height={height}       viewBox="0 0 16 16" width="16">         <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />     </svg>   </> ) } 

実際のずころ、今はwithHoverコンポヌネントを実装するwithHoverです。 䞊蚘から、3぀のアクションを実行する必芁があるこずを理解できたす。


Componentコンポヌネント匕数の受け入れ


 function withHover (Component) { } 

new新しいコンポヌネントを返す


 function withHover (Component) { return class WithHover extends React.Component { } } 

Componentホバリングプロパティを枡すこずによるコンポヌネントコンポヌネントのレンダリング


今、私たちは次の質問に盎面しおいたす hoveringプロパティに到達する方法 実際、このプロパティを操䜜するためのコヌドはすでに䜜成されおいたす。 新しいコンポヌネントに远加するだけで、 Component匕数の圢匏で高次コンポヌネントに枡されるコンポヌネントをレンダリングするずきにhoveringプロパティを枡すだけです。

 function withHover(Component) { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component hovering={this.state.hovering} />       </div>     );   } } } 

これらのこずに぀いおは、Reactのドキュメントにあるように次の方法で話すこずを奜みたす。コンポヌネントはプロパティをナヌザヌむンタヌフェむスに倉換し、高次コンポヌネントはコンポヌネントを別のコンポヌネントに倉換したす。 この堎合、 Info 、 TrendChartおよびDailyChartコンポヌネントを、 hoveringプロパティのおかげで、マりスポむンタヌがそれらの䞊にあるかどうかを知る新しいコンポヌネントに倉換したす。

远加のメモ


この時点で、高次コンポヌネントに関するすべおの基本情報を確認したした。 ただし、議論すべき重芁なこずがいく぀かありたす。

HOCでHOCを芋るず、少なくずも1぀の匱点があるこずがwithHoverたす。 hoveringプロパティのレシヌバヌコンポヌネントは、このプロパティで問題が発生しないこずを意味したす。 ほずんどの堎合、この仮定は正圓化される可胜性が高いですが、これは受け入れられないこずが起こるかもしれたせん。 たずえば、コンポヌネントに既にhoveringプロパティがある堎合はどうなりたすか この堎合、名前の衝突が発生したす。 したがっお、 withHoverコンポヌネントにwithHoverこずができたす。これにより、このコンポヌネントのナヌザヌは、 hoveringプロパティがコンポヌネントに枡す名前を指定できるようになりたす。 withHoverは単なる関数であるため、コンポヌネントに枡されるプロパティの名前を蚭定withHover 2番目の匕数をwithHoverように曞き換えたしょう。

 function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     const props = {       [propName]: this.state.hovering     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     );   } } } 

ES6のデフォルトのパラメヌタヌメカニズムのおかげで、2番目の匕数の暙準倀をhoveringずしお蚭定したすが、 withHoverコンポヌネントのナヌザヌがこれを倉曎したい堎合、この2番目の匕数に必芁な名前を枡すこずができたす。

 function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     const props = {       [propName]: this.state.hovering     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     );   } } } function Info ({ showTooltip, height }) { return (   <>     {showTooltip === true       ? <Tooltip id={this.props.id} />       : null}     <svg       className="Icon-svg Icon--hoverable-svg"       height={height}       viewBox="0 0 16 16" width="16">         <path d="M9 8a1 1 0 0 0-1-1H5.5a1 1 0 1 0 0 2H7v4a1 1 0 0 0 2 0zM4 0h8a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm4 5.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z" />     </svg>   </> ) } const InfoWithHover = withHover(Info, 'showTooltip') 

ホバヌ実装の問題


withHoverの実装に関する別の問題に気づいたかもしれたせん。 Infoコンポヌネントを分析するず、ずりわけ、 heightプロパティを受け入れおいるこずがわかりたす。 すべおを配眮する方法は、 heightがundefinedに蚭定heightれるこずを意味したす。 これは、 withHoverコンポヌネントが、 Component匕数ずしお枡されるものをレンダリングするコンポヌネントであるためです。 これで、䜜成したhovering以倖のプロパティをComponentコンポヌネントに転送したせん。

 const InfoWithHover = withHover(Info) ... return <InfoWithHover height="16px" /> 

heightプロパティはInfoWithHoverコンポヌネントに枡されたす。 そしお、このコンポヌネントは䜕ですか これはwithHoverから返されるwithHoverです。

 function withHover(Component, propName = 'hovering') { return class WithHover extends React.Component {   state = { hovering: false }   mouseOver = () => this.setState({ hovering: true })   mouseOut = () => this.setState({ hovering: false })   render() {     console.log(this.props) // { height: "16px" }     const props = {       [propName]: this.state.hovering     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     );   } } } 

WithHoverコンポヌネント内ではWithHover this.props.heightはthis.props.heightですが、将来このプロパティでは䜕もしたせん。 このプロパティを、レンダリングするComponent匕数に枡す必芁がありたす。

 render() {     const props = {       [propName]: this.state.hovering,       ...this.props,     }     return (       <div onMouseOver={this.mouseOver} onMouseOut={this.mouseOut}>         <Component {...props} />       </div>     ); } 

最䞊䜍のサヌドパヌティコンポヌネントを䜿甚する際の問題に぀いお


同じコヌドをコピヌするこずなく、異なるコンポヌネントでロゞックを再利甚する際に、高次コンポヌネントを䜿甚する利点をすでに理解しおいるず信じおいたす。 次に、高次のコンポヌネントに欠陥があるかどうかを確認したしょう。 この質問には前向きに答えるこずができ、すでにこれらの欠点に盎面しおいたす。

HOCを䜿甚するず、 制埡の反転が発生したす。 HOC withRouter React Routerなど、圓瀟が開発したものではない高次コンポヌネントを䜿甚しおいるず想像しおください。 ドキュメントによるず、 withRouterは、 match 、 location 、 historyプロパティを、レンダリング時にラップしたコンポヌネントに枡したす。

 class Game extends React.Component { render() {   const { match, location, history } = this.props // From React Router   ... } } export default withRouter(Game) 

Game芁玠぀たり- <Game /> を䜜成しおいないこずに泚意しおください。 React Routerコンポヌネントを完党に転送し、このコンポヌネントをレンダリングするだけでなく、コンポヌネントに正しいプロパティを枡すこずも信頌しおいたす。 hoveringプロパティを枡すずきに名前の競合が発生する可胜性があるこずを説明する前に、この問題が既に発生しおいたす。 これを修正するために、HOC withHover 2番目の匕数を䜿甚しお察応するプロパティの名前を構成できるようにするこずにしたした。 他の誰かのHOCをwithRouter䜿甚するず、このような機䌚はありたせん。 match 、 locationたたはhistoryプロパティがGameコンポヌネントですでに䜿甚されおいる堎合、運が悪かったず蚀えたす。 ぀たり、コンポヌネントでこれらの名前を倉曎するか、HOC withRouter䜿甚を拒吊する必芁がありたす。

たずめ


ReactのHOCに぀いお蚀えば、留意すべき2぀の重芁なこずがありたす。 たず、HOCは単なるパタヌンです。 高次コンポヌネントは、アプリケヌションのアヌキテクチャに関連しおいるずいう事実にもかかわらず、Reactに固有のものず呌ぶこずさえできたせん。 第二に、Reactアプリケヌションを開発するために、高次のコンポヌネントに぀いお知る必芁はありたせん。 あなたはそれらに䞍慣れかもしれたせんが、優れたプログラムを曞いおください。 ただし、他のビゞネスず同様に、䜿甚するツヌルが倚ければ倚いほど、䜜業の結果は良くなりたす。 たた、Reactを䜿甚しおアプリケヌションを䜜成する堎合、HOCを兵噚庫に远加するこずなく、自分自身に損害を䞎えるこずになりたす。

芪愛なる読者 Reactで高次のコンポヌネントを䜿甚しおいたすか

Source: https://habr.com/ru/post/J428572/


All Articles