web-dev-qa-db-ja.com

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

多くのテンプレート言語は "スロット"または "イールド"ステートメントを持っています。それはあるテンプレートを別のテンプレートの中に包むためにある種の制御の逆転をすることを可能にします。

Angularは "transclude"オプション を持っています。

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コンポーネントをどのように定義すればよいですか?

162
NVI

試してください:

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

詳しくは、ドキュメント内の Multiple Components:Children および Type of the Childrenの小道具 を参照してください。

215
Sophie Alpert

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);

これはAngularではtransclusionとしても知られています。

childrenはReactの特別な小道具で、あなたのコンポーネントのタグの中にあるものを含みます(ここで<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 prop と呼ばれ、childrenプロップはそのコールバックを提供するためによく使用されます。

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

反例:

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 context、React-motion、Apollo ...)を使用しています。なぜなら、人々はこのAPIをHOCよりも簡単に見つける傾向があるからです。 react-powerplug は単純なrender-propコンポーネントの集まりです。 反応 - 採用 あなたが作曲をするのを助けます。


高次コンポーネント(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);

Higher-Order Component/HOC は一般に、コンポーネントを受け取り新しいコンポーネントを返す関数です。

高次コンポーネントを使用すると、childrenまたはrender propsを使用するよりもパフォーマンスが向上します。これは、ラッパーがshouldComponentUpdateを使用して1段階先にレンダリングを短縮できるためです。

ここではPureComponentを使用しています。アプリを再レンダリングするときに、WrappedAppという名前の小道具が時間の経過とともに変化しない場合、ラッパーは「小道具(実際には、名前)が以前と同じなのでレンダリングする必要はありません」と言うことができます。上のchildrenベースの解決法では、ラッパーがPureComponentであっても、親要素がレンダリングされるたびに子要素が再作成されるため、そうではありません。つまり、ラップされたコンポーネントが純粋でもこれを軽減し、時間の経過とともに一定のchildren要素を確保するのを助けることができる babel plugin があります。


結論

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

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

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

コンポーネントにrender-prop APIがある場合、その上にHOCを作成するのは一般的に簡単です。そのため、もしあなたがlibの作者なら、最初にrender prop APIを書いて、最終的にHOCバージョンを提供するべきです。これがApolloが<Query> render-propコンポーネント、およびそれを使用するgraphql HOCに対して行うことです。

個人的には両方を使用していますが、疑問がある場合はHOCを好みます。

  • レンダープロップと比較して、それらを構成する方が賢い(compose(hoc1,hoc2)(Comp)
  • それは私に良いパフォーマンスを与えることができます
  • 私はこのスタイルのプログラミングに精通しています

私は私のお気に入りのツールのHOCバージョンを使用/作成することを躊躇しません。

  • ReactのContext.Consumer comp
  • 記載されていないのSubscribe
  • graphql render propの代わりにQueryをApolloのHOCを使用する

私の意見では、時々レンダリングプロップはコードをより読みやすくし、時にはより少なくします...私は私が持っている制約に従って最も実用的な解決策を使おうとします。読みやすさがパフォーマンスよりも重要な場合もあれば、そうでない場合もあります。賢明に選択し、すべてをレンダープロップに変換する2018年のトレンドに密接に従ってはいけません。

127

Sophieの答えに加えて、私は次のようなことをして、子コンポーネントタイプを送信することにも用途を見出しました。

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} />
    }
});
31
krs