反応パターン

こんにちはHabr! Michael Chanの記事「 React Patterns 」の無料翻訳に、いくつかのメモと追加を加えて、あなたの注意を喚起します。



まず、元のテキストの作者に感謝したいと思います。 翻訳では、ステートレスコンポーネント(ダンプコンポーネント、コンポーネントvsコンテナ)の指定として「シンプルコンポーネント」の概念を使用しました。

コメントでは、建設的な批判と、Reactの代替パターンおよび機能を歓迎します。



目次

  • 単純なコンポーネント -ステートレス機能
  • JSX属性の分布 -JSXスプレッド属性
  • 引数の破壊 - 引数の破壊
  • 条件付きレンダリング
  • 子孫タイプ -子タイプ
  • 子孫としての配列 -子としての配列
  • 子としての機能 - 子としての機能
  • レンダリングの関数 -コールバックのレンダリング
  • 子孫 -子供のパススルー
  • コンポーネントリダイレクト -プロキシコンポーネント
  • コンポーネントのスタイル設定-スタイルコンポーネント
  • イベントスイッチ
  • レイアウトコンポーネント
  • コンテナコンポーネント
  • 高次コンポーネント


行こう!



ステートレス機能



ステートレス関数(以降、シンプルコンポーネントと呼びます)は、ユニバーサルコンポーネントを定義するための優れた方法です。 状態やDOM要素への参照(ref)は含まれていません。これらは単なる関数です。



const Greeting = () => <div>Hi there!</div>
      
      





彼らはパラメータ(小道具)とコンテキストを渡します



 const Greeting = (props, context) => <div style={{color: context.color}}>Hi {props.name}!</div>
      
      





ブロック({})を使用すると、ローカル変数を定義できます



 const Greeting = (props, context) => { const style = { fontWeight: "bold", color: context.color, } return <div style={style}>{props.name}</div> }
      
      





しかし、別の関数を使用すると同じ結果を得ることができます



 const getStyle = context => ({ fontWeight: "bold", color: context.color, }) const Greeting = (props, context) => <div style={getStyle(context)}>{props.name}</div>
      
      





defaultProps、propTypes、contextTypesを定義できます



 Greeting.propTypes = { name: PropTypes.string.isRequired } Greeting.defaultProps = { name: "Guest" } Greeting.contextTypes = { color: PropTypes.string }
      
      





JSXスプレッド属性



属性配布はJSXの機能です。 オブジェクトのすべてのプロパティをJSX属性として渡すような構文上の工夫



これら2つの例は同等です。



-小道具は属性として書かれています:



 <main className="main" role="main">{children}</main>
      
      





-小道具はオブジェクトから「配布」されます:



 <main {...{className: "main", role: "main", children}} />
      
      





作成したオブジェクトへの小道具リダイレクトを使用する



 const FancyDiv = props => <div className="fancy" {...props} />
      
      





これで、目的の属性(className)が存在すること、および関数で直接指定しなかったがpropsとともに渡された属性が存在することを確認できます。



 <FancyDiv data-id="my-fancy-div">So Fancy</FancyDiv>
      
      





結果:



 <div className="fancy" data-id="my-fancy-div">So Fancy</div>
      
      





順序が重要であることを覚えておいてください。 props.classNameが定義されている場合、このプロパティはFancyDivで定義されたclassNameを上書きします



 <FancyDiv className="my-fancy-div" />
      
      





結果:



 <div className="my-fancy-div"></div>
      
      





FancyDivs classNameは、スプレッドプロップ({... props})の後に配置することで、常に「勝つ」ことができます。

プロパティが常に小道具を通過して上書きすることを確認できます



 const FancyDiv = props => <div {...props} className="fancy" />
      
      





よりエレガントなアプローチがあります-両方のプロパティを組み合わせます。



 const FancyDiv = ({ className, ...props }) => <div className={["fancy", className].join(' ')} {...props} />
      
      





引数の破壊



破壊的な割り当ては、ES2015標準の機能です。 Simple Componentsの小道具によく合います。



これらの例は同等です。



 const Greeting = props => <div>Hi {props.name}!</div> const Greeting = ({ name }) => <div>Hi {name}!</div>
      
      





残りの(...)演算子構文を使用すると、残りのプロパティをオブジェクトに収集できます



 const Greeting = ({ name, ...props }) => <div>Hi {name}!</div>
      
      





さらに、このオブジェクトを使用して、作成されたコンポーネントで未割り当てのプロパティをさらに転送できます。



 const Greeting = ({ name, ...props }) => <div {...props}>Hi {name}!</div>
      
      





構成されたコンポーネントに非DOM小道具を転送しないでください。 コンポーネント固有のプロップを使用せずに新しいプロップオブジェクトを作成できるため、これは非常に簡単です。



条件付きレンダリング



コンポーネントでは通常のif / else構文を使用できます。 しかし、条件付き(三項)演算子はあなたの友達です



もし



 {condition && <span>Rendered when `truthy`</span> }
      
      





でない限り



 {condition || <span>Rendered when `falsey`</span> }
      
      





if-else(きちんとしたワンライナー)



 {condition ? <span>Rendered when `truthy`</span> : <span>Rendered when `falsey`</span> }
      
      





if-else(大きなブロック)



 {condition ? ( <span> Rendered when `truthy` </span> ) : ( <span> Rendered when `falsey` </span> )}
      
      





*私は最後の例の構成を使用しないことを好みます。この場合、特定のコードにすべて依存しますが、通常のif / elseを使用することはより明白です。



子供の種類



Reactは、あらゆるタイプの子をレンダリングできます。 基本的には配列または文字列です



ひも



 <div> Hello World! </div>
      
      





配列



 <div> {["Hello ", <span>World</span>, "!"]} </div>
      
      





関数は子孫としても使用できます。 ただし、それらの動作を親コンポーネントと調整する必要があります。



機能



 <div> {() => { return "hello world!"}()} </div>
      
      





子としての配列



子の配列を使用することは一般的なパターンです。たとえば、これはReactでリストを作成する方法です。



map()を使用して、配列の各値に対してReact要素の配列を作成します。



 <ul> {["first", "second"].map((item) => ( <li>{item}</li> ))} </ul>
      
      





これは、オブジェクトを持つ配列リテラルと同等です



 <ul> {[ <li>first</li>, <li>second</li>, ]} </ul>
      
      





このようなパターンは、コードの記述を簡素化するために、構造化、属性分布、およびその他の機能と組み合わせて使用​​できます。



 <ul> {arrayOfMessageObjects.map(({ id, ...message }) => <Message key={id} {...message} /> )} </ul>
      
      





子どもとしての機能



関数を子孫として使用するには、それらの恩恵を受けることができるように、さらに注意を払う必要があります。



 <div>{() => { return "hello world!»}()}</div>
      
      





ただし、これらはコンポーネントに超強度を与えることができます。これは一般的にレンダーコールバックと呼ばれる手法です。



この強力な手法は、ReactMotionなどのライブラリで使用されています。 これを適用すると、レンダリングロジックを完全にコンポーネント自体に渡すのではなく、親コンポーネントから制御できます。



コールバックをレンダリング



レンダリングコールバックを使用するコンポーネントの例を次に示します。 これは一般的には役に立たないが、これは可能性の良い例である。



 const Width = ({ children }) => children(500)
      
      





コンポーネントは、特定の引数を持つ関数として子孫を呼び出します。 この場合、数は500です。



このコンポーネントを使用するには、子コンポーネントとして関数を渡します。



 <Width> {width => <div>window is {width}</div>} </Width>
      
      





この結果が得られます



 <div>window is 500</div>
      
      





このアプローチでは、条件付きレンダリングに(幅)パラメーターを使用できます



 <Width> {width => width > 600 ? <div>min-width requirement met!</div> : null } </Width>
      
      





この条件を何度も使用する場合、このロジックを渡すために別のコンポーネントを定義できます。



 const MinWidth = ({ width: minWidth, children }) => <Width> {width => width > minWidth ? children : null } </Width>
      
      





明らかに、静的なWidthコンポーネントはあまり有用ではありませんが、このアプローチでブラウザーウィンドウのサイズを確認できます。これは既に何かです



 class WindowWidth extends React.Component { constructor() { super() this.state = { width: 0 } } componentDidMount() { this.setState( {width: window.innerWidth}, window.addEventListener( "resize", ({ target }) => this.setState({width: target.innerWidth}) ) ) } render() { return this.props.children(this.state.width) } }
      
      





多くの人は、このタイプの機能のために高次コンポーネントを好みます。 これは個人的な好みの問題です。



子供のパススルー



コンテキストを適用して子孫をレンダリングするコンポーネントを作成できます。



 class SomeContextProvider extends React.Component { getChildContext() { return {some: "context"} } render() { // how best do we return `children`? } }
      
      





ここで、決定を下す必要があります。 子孫を別のhtmlタグ(div)でラップするか、子孫のみを返します。 最初のオプションは既存のマークアップに影響し、スタイルに違反する可能性があります。 2番目のものはエラーになります(コンポーネントから返すことができる親要素は1つだけであることを覚えています)



オプション1:オプションのdiv



 return <div>{children}</div>
      
      





オプション2:エラー



 return children
      
      





特別なメソッド-React.Childrenを使用して子孫を管理することが最善です。 たとえば、次の例では、子孫のみを返すことができ、追加のラッパーは不要です



 return React.Children.only(this.props.children)
      
      





プロキシコンポーネント



(この名前が何かを意味するかどうかはわかりません) 記事の著者



ボタンはアプリのいたるところにあります。 そして、それぞれに「ボタン」タイプの属性が必要です



 <button type=«button">
      
      





ペンで何百回もそのようなことを書くことは私たちの方法ではありません。 高レベルのコンポーネントを記述して、小道具を低レベルのコンポーネントにリダイレクトできます。



 const Button = props => <button type="button" {…props}>
      
      





次に、ボタンの代わりにボタンを使用するだけで、目的の属性が各ボタンに存在することを確認できます。



 <Button /> //  <button type="button"><button>
      
      





 <Button className="CTA">Send Money</Button> //  <button type="button" class="CTA">Send Money</button>
      
      





スタイルコンポーネント



これは、スタイルに適用されるプロキシコンポーネントです。 ボタンがあるとします。 彼女はクラスを使用して「プライマリ」のように見えます。



 <button type="button" className="btn btn-primary»>
      
      





いくつかの簡単なコンポーネントを使用して、これをかき立てることができます。



 const PrimaryBtn = props => <Btn {...props} primary /> const Btn = ({ className, primary, ...props }) => <button type="button" className={classnames( "btn", primary && "btn-primary", className )} {...props} />
      
      





これは、何が起こっているかを視覚化するのに役立ちます。

PrimaryBtn()

↳Btn({primary:true})

↳ボタン({className: "btn btn-primary"}、タイプ: "button"})

↳ '<button type = "button" class = "btn btn-primary">'



これらのコンポーネントを使用すると、同じ結果が返されます。



 <PrimaryBtn /> <Btn primary /> <button type="button" className="btn btn-primary" />
      
      





このアプローチは、特定のコンポーネントの特定のスタイルを分離するため、具体的なメリットをもたらします。



イベント切り替え



イベントハンドラを記述するとき、通常は関数の命名規則を使用します



 handle{eventName} handleClick(e) { /* do something */ }
      
      





複数のイベントを処理するコンポーネントの場合、この名前は不必要に繰り返される場合があります。 名前自体は、アクション/機能に単純に誘導するため、値を与えません。



 handleClick() { require("./actions/doStuff")(/* action dtes */) } handleMouseEnter() { this.setState({ hovered: true }) } handleMouseLeave() { this.setState({ hovered: false }) }
      
      





イベントタイプスイッチ(event.type)を使用して、すべてのイベントの簡単なハンドラーを作成しましょう。



 handleEvent({type}) { switch(type) { case "click": return require("./actions/doStuff")(/* action dates */) case "mouseenter": return this.setState({ hovered: true }) case "mouseenter": return this.setState({ hovered: false }) default: return console.warn(`No case for event type "${type}"`) } }
      
      





矢印関数を使用して関数の引数を直接呼び出すこともできます



 <div onClick={() => someImportedAction({ action: "DO_STUFF" })}
      
      





このような問題に遭遇するまで、パフォーマンスの最適化について心配する必要はありません。 真剣に、必要はありません。



*個人的には、コードに読みやすさを追加しないため、このアプローチは成功するとは思わない。 コンテキストに自動的にバインドされる関数でReact機能を使用することを好みます。 つまり、次の表記は不要になりました



 this.handleClick = this.handleClick.bind(this)
      
      





代わりに次の表記法が機能します



 handleClick = () => {…} //  handleClick() {...}
      
      





さらにどこかでそれが可能です



 onClick={this.handleClick}
      
      





この場合、ハンドラーが関数内でコンテキストを参照しても、コンテキスト(this)は失われません。 したがって、このような関数は、他のコンポーネントに小道具として簡単に渡して、それらを呼び出すことができます。



また、このような関数を子孫であるSimple Componentに渡す場合、親コンポーネントのevent.targetを介してこのコンポーネントのDOM要素へのリンクを取得できます。これは便利な場合があります。



 class SomeComponent extends React.Component { onButtonClick = (e) => { const button = e.target; // … } render() { <div> <Input … /> <Button clickHandler={this.onButtonClick} /> </div> } } const Button = ({clickHandler, …props}) => { const btnClickHandler = (e) => { // -  e.preventDefault() clickHandler(e) } return <button onClick={btnClickHandler}/> }
      
      





レイアウトコンポーネント



レイアウトコンポーネントは、静的DOM要素のようなものです。 ほとんどの場合、頻繁に更新されることはありません。



水平に2つのコンポーネントを含むコンポーネントを考えます。



 <HorizontalSplit leftSide={<SomeSmartComponent />} rightSide={<AnotherSmartComponent />} />
      
      





その作業を積極的に最適化できます。



Horizo​​ntalSplitは両方のコンポーネントの親コンポーネントになるため、それらの所有者になることはありません。 内部コンポーネントのライフサイクルを壊すことなく、決して更新されるべきではないと言えます。



 class HorizontalSplit extends React.Component { shouldComponentUpdate() { return false } render() { <FlexContainer> <div>{this.props.leftSide}</div> <div>{this.props.rightSide}</div> </FlexContainer> }
      
      





コンテナコンポーネント



「コンテナはデータを取得し、対応するサブコンポーネントをレンダリングします。 それだけです。」-ジェイソン・ボンタ



指定:CommentListコンポーネント。アプリケーションで数回使用されます。



 const CommentList = ({ comments }) => <ul> {comments.map(comment => <li>{comment.body}-{comment.author}</li> )} </ul>
      
      





データの受信とCommentListコンポーネントのレンダリングを行う新しいコンポーネントを作成できます



 class CommentListContainer extends React.Component { constructor() { super() this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: comments => this.setState({comments: comments}); }) } render() { return <CommentList comments={this.state.comments} /> } }
      
      





さまざまなアプリケーションコンテキスト用のさまざまなコンテナコンポーネントを作成できます。



高次コンポーネント



高階関数は、他の関数を引数として使用したり、関数を返したりできる関数です。 この定義ほど複雑ではありません。 では、高次コンポーネントとは何ですか?



既にコンテナコンポーネントを使用しています。これらは単なる関数にラップされたコンテナです。 簡単なGreetingコンポーネントから始めましょう。



 const Greeting = ({ name }) => { if (!name) { return <div>Connecting...</div> } return <div>Hi {name}!</div> }
      
      





props.nameを取得すると、データをレンダリングします。 それ以外の場合は、「接続しています...」と表示されます。 少し高次:



 const Connect = ComposedComponent => class extends React.Component { constructor() { super() this.state = { name: "" } } componentDidMount() { // this would fetch or connect to a store this.setState({ name: "Michael" }) } render() { return ( <ComposedComponent {...this.props} name={this.state.name} /> ) } }
      
      





これは、引数として渡したコンポーネント(ComposedComponent)をレンダリングするコンポーネントを返す関数です



次に、この関数でコンポートをラップします。



 const ConnectedMyComponent = Connect(Greeting)
      
      





これは非常に強力なテンプレートであるため、コンポーネントはデータを受信して​​、任意の数の単純なコンポーネントに配信できます。



リンク(すべて英語):






All Articles