React.js:あるコンポーネントを別のコンポーネントにラップする


187

多くのテンプレート言語には、「スロット」または「yield」ステートメントがあります。これにより、ある種の制御を逆にして、あるテンプレートを別のテンプレート内にラップすることができます。

Angularには「トランスクルード」オプションがあります

Railsにはyieldステートメントがあります。React.jsにyieldステートメントがある場合、次のようになります。

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          <yield/>
        after
      </div>
    );
  }
});

var Main = React.createClass({
  render: function() {
    return (
      <Wrapper><h1>content</h1></Wrapper>
    );
  }
});

望ましい出力:

<div class="wrapper">
  before
    <h1>content</h1>
  after
</div>

残念ながら、React.jsにはがありません<yield/>。同じ出力を実現するためにWrapperコンポーネントをどのように定義しますか?


回答:



159

使用する children

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = ({name}) => (
  <Wrapper>
    <App name={name}/>
  </Wrapper>
);

render(<WrappedApp name="toto"/>,node);

これはtransclusionAngular としても知られています。

childrenReactの特別な小道具であり、コンポーネントのタグ内にあるものが含まれます(ここに<App name={name}/>があるWrapperため、children

childrenコンポーネントに固有のを必ずしも使用する必要はなく、必要に応じて通常の小道具を使用したり、小道具と子を組み合わせたりすることができます。

const AppLayout = ({header,footer,children}) => (
  <div className="app">
    <div className="header">{header}</div>
    <div className="body">{children}</div>
    <div className="footer">{footer}</div>
  </div>
);

const appElement = (
  <AppLayout 
    header={<div>header</div>}
    footer={<div>footer</div>}
  >
    <div>body</div>
  </AppLayout>
);

render(appElement,node);

これは多くのユースケースでシンプルで問題なく、ほとんどのコンシューマーアプリに推奨します。


小道具をレンダリングする

レンダー関数をコンポーネントに渡すことができます。このパターンは一般的にと呼ばれrender propchildrenプロップはそのコールバックを提供するためによく使用されます。

このパターンは実際にはレイアウト用ではありません。ラッパーコンポーネントは、通常、いくつかの状態を保持および管理し、そのレンダリング関数に挿入するために使用されます。

カウンターの例:

const Counter = () => (
  <State initial={0}>
    {(val, set) => (
      <div onClick={() => set(val + 1)}>  
        clicked {val} times
      </div>
    )}
  </State>
); 

あなたはさらに豪華になり、オブジェクトを提供することさえできます

<Promise promise={somePromise}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise>

必ずしも使用する必要はありませんchildren。これは好み/ APIの問題です。

<Promise 
  promise={somePromise}
  renderLoading={() => <div>...</div>}
  renderSuccess={(data) => <div>{data.something}</div>}
  renderError={(e) => <div>{e.message}</div>}
/>

今日、多くのライブラリがレンダープロップ(Reactコンテキスト、React-motion、Apollo ...)を使用しています。これは、このAPIがHOCよりも簡単である傾向があるためです。react-powerplugはシンプルなrender-propコンポーネントのコレクションです。react-adoptは構成を行うのに役立ちます。


高次コンポーネント(HOC)。

const wrapHOC = (WrappedComponent) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

render(<WrappedApp name="toto"/>,node);

アン高次コンポーネント/ HOC一般的に部品を取り、新しいコンポーネントを返す関数です。

高次コンポーネントを使用すると、ラッパーにを使用してレンダリングを短絡させることができるため、childrenまたはを使用するよりもパフォーマンスが向上render propsshouldComponentUpdateます。

ここではを使用していPureComponentます。アプリを再レンダリングするときに、WrappedApp名前プロップが時間とともに変化しない場合、ラッパーは「プロップ(実際には名前)は以前と同じであるため、レンダリングする必要がない」と言うことができます。children上記のベースのソリューションではPureComponent、ラッパーがであっても、親がレンダリングされるたびに子要素が再作成されるため、ラップされたコンポーネントが純粋であっても、ラッパーは常に再レンダリングされる可能性があります。これを軽減し、長期にわたって一定の要素を確保できるバベルプラグインがありchildrenます。


結論

高次コンポーネントを使用すると、パフォーマンスが向上します。それほど複雑ではありませんが、最初は不親切に見えます。

これを読んだ後、コードベース全体をHOCに移行しないでください。アプリのクリティカルパスでは、パフォーマンス上の理由からランタイムラッパーの代わりにHOCを使用することをお勧めします。特に、同じラッパーが何度も使用される場合は、HOCにすることを検討する価値があります。

Reduxは最初にランタイムラッパー<Connect>を使用しconnect(options)(Comp)、パフォーマンス上の理由からHOCに後で切り替えました(デフォルトでは、ラッパーは純粋で使用されていますshouldComponentUpdate)。これは、私がこの回答で強調したかったことの完璧な例です。

コンポーネントにrender-prop APIが含まれている場合、通常、その上にHOCを作成するのは簡単です。そのため、ライブラリ作成者は、最初にrender prop APIを記述し、最終的にHOCバージョンを提供する必要があります。これは、Apolloが<Query>render-propコンポーネントとgraphqlそれを使用するHOCで行うことです。

個人的には両方を使用していますが、疑わしい場合は次の理由でHOCを使用します。

  • compose(hoc1,hoc2)(Comp)小道具をレンダリングするよりも、それらを構成するほうが慣用的です()
  • それは私に良いパフォーマンスを与えることができます
  • 私はこのスタイルのプログラミングに精通しています

私はお気に入りのツールのHOCバージョンを使用/作成することをためらっていません。

  • ReactのContext.Consumerコンプ
  • 記載されていない Subscribe
  • レンダープロップのgraphql代わりにアポロのHOCを使用Query

私の意見では、レンダープロップによってコードが読みやすくなる場合がありますが、場合によってはコードが読みにくくなることがあります...自分の制約に従って、最も実用的なソリューションを使用しようとします。パフォーマンスよりも読みやすさが重要な場合もあれば、そうでない場合もあります。賢明に選択し、すべてをレンダープロップに変換する2018年のトレンドに拘束されないでください。


1
このアプローチにより、小道具を子コンポーネント(この場合はHello)に簡単に渡すことができます。React 0.14。*​​以降、小道具を子コンポーネントに渡す唯一の方法は、高額になる可能性があるReact.createCloneを使用することです。
Mukesh Soni

2
質問:答えは「より良いパフォーマンス」に言及しています-私が理解していないこと:他のどのソリューションと比較して良いですか?
フィリップ

1
HOCはレンダリングを早期に短絡できるため、ランタイムラッパーと比較してパフォーマンスが向上します。
Sebastien Lorber

1
ありがとうございました!👍あなたは私の月から単語を取ったみたいですが、あなたは大きな才能でそれらを表現する
MacKentoch

1
これははるかに良い答えです:]ありがとう!
cullanrocks

31

ソフィーの答えに加えて、次のような子コンポーネントタイプを送信することにも用途があります。

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});

2
に関するドキュメントは見たことがありませんdelegate。どうやって見つけましたか?
NVI 2014年

4
コンポーネントに必要な小道具を追加して、必要に応じて名前を付けることができます。行4でthis.props.delegateを使用しますが、別の名前を付けることもできます。
krs
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.