React.js: Eine Komponente in eine andere einwickeln

187

Viele Vorlagensprachen haben "Slots" - oder "Yield" -Anweisungen, die eine Art Umkehrung der Steuerung ermöglichen, um eine Vorlage in eine andere zu verpacken.

Angular hat die Option "transclude" .

Rails hat eine Ertragserklärung . Wenn React.js eine Ertragsangabe hätte, würde dies folgendermaßen aussehen:

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

Gewünschte Ausgabe:

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

Leider hat React.js keine <yield/>. Wie definiere ich die Wrapper-Komponente, um die gleiche Ausgabe zu erzielen?

NVI
quelle

Antworten:

159

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

Dies ist auch als transclusionAngular bekannt.

childrenist eine spezielle Requisite in React und enthält, was sich in den Tags Ihrer Komponente befindet (hier <App name={name}/>ist drin Wrapper, also ist es daschildren

Beachten Sie, dass Sie nicht unbedingt verwenden müssen children, was für eine Komponente eindeutig ist, und Sie können auch normale Requisiten verwenden, wenn Sie möchten, oder Requisiten und Kinder mischen:

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

Dies ist für viele Anwendungsfälle einfach und in Ordnung, und ich würde dies für die meisten Consumer-Apps empfehlen.


Requisiten rendern

Es ist möglich, Renderfunktionen an eine Komponente zu übergeben. Dieses Muster wird im Allgemeinen aufgerufen render prop, und die childrenRequisite wird häufig verwendet, um diesen Rückruf bereitzustellen.

Dieses Muster ist nicht wirklich für das Layout gedacht. Die Wrapper-Komponente wird im Allgemeinen verwendet, um einen bestimmten Status zu halten, zu verwalten und in seine Renderfunktionen einzufügen.

Gegenbeispiel:

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

Sie können noch ausgefallener werden und sogar ein Objekt bereitstellen

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

Beachten Sie, dass Sie nicht unbedingt verwenden müssen children, es ist eine Frage des Geschmacks / der API.

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

Bis heute verwenden viele Bibliotheken Render-Requisiten (React-Kontext, React-Motion, Apollo ...), da diese API für Benutzer einfacher ist als für HOCs. React-Powerplug ist eine Sammlung einfacher Render-Prop-Komponenten. React-Adopt hilft Ihnen bei der Komposition.


Komponenten höherer Ordnung (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);

Eine Komponente höherer Ordnung / HOC ist im Allgemeinen eine Funktion, die eine Komponente übernimmt und eine neue Komponente zurückgibt.

Die Verwendung einer Komponente höherer Ordnung kann leistungsfähiger sein als die Verwendung von childrenoder render props, da der Wrapper das Rendering einen Schritt voraus kurzschließen kann shouldComponentUpdate.

Hier verwenden wir PureComponent. Wenn sich die WrappedAppNamensstütze beim erneuten Rendern der App im Laufe der Zeit nicht ändert, kann der Wrapper sagen: "Ich muss nicht rendern, da die Requisiten (eigentlich der Name) dieselben wie zuvor sind." Mit der childrenobigen basierten Lösung PureComponentist dies nicht der Fall , selbst wenn der Wrapper vorhanden ist , da das untergeordnete Element jedes Mal neu erstellt wird, wenn das übergeordnete Element gerendert wird. Dies bedeutet, dass der Wrapper wahrscheinlich immer neu gerendert wird, selbst wenn die umschlossene Komponente rein ist. Es gibt ein Babel-Plugin , das dazu beitragen kann, dies zu mildern und ein konstantes childrenElement über die Zeit sicherzustellen .


Fazit

Komponenten höherer Ordnung können Ihnen eine bessere Leistung bieten. Es ist nicht so kompliziert, aber auf den ersten Blick sieht es auf jeden Fall unfreundlich aus.

Migrieren Sie nach dem Lesen nicht Ihre gesamte Codebasis zu HOC. Denken Sie daran, dass Sie auf kritischen Pfaden Ihrer App aus Leistungsgründen möglicherweise HOCs anstelle von Laufzeit-Wrappern verwenden möchten, insbesondere wenn derselbe Wrapper häufig verwendet wird. Es lohnt sich, darüber nachzudenken, ihn zu einem HOC zu machen.

Redux verwendete zunächst einen Laufzeit-Wrapper <Connect>und wechselte später connect(options)(Comp)aus Leistungsgründen zu einem HOC (standardmäßig ist der Wrapper rein und verwendet shouldComponentUpdate). Dies ist die perfekte Illustration dessen, was ich in dieser Antwort hervorheben wollte.

Hinweis: Wenn eine Komponente über eine Render-Prop-API verfügt, ist es im Allgemeinen einfach, ein HOC darüber zu erstellen. Wenn Sie also ein lib-Autor sind, sollten Sie zuerst eine Render-Prop-API schreiben und schließlich eine HOC-Version anbieten. Dies ist, was Apollo mit der <Query>Render-Prop-Komponente und dem graphqlHOC, das sie verwendet, macht.

Persönlich benutze ich beide, aber im Zweifelsfall bevorzuge ich HOCs, weil:

  • Es ist idiomatischer, sie zu komponieren ( compose(hoc1,hoc2)(Comp)) als Requisiten zu rendern
  • Es kann mir bessere Leistungen geben
  • Ich bin mit dieser Art der Programmierung vertraut

Ich zögere nicht, HOC-Versionen meiner Lieblingswerkzeuge zu verwenden / zu erstellen:

  • React's Context.Consumercomp
  • Nicht angegeben Subscribe
  • mit graphqlHOC von Apollo anstelle von QueryRender Prop

Meiner Meinung nach machen Render-Requisiten den Code manchmal lesbarer, manchmal weniger ... Ich versuche, die pragmatischste Lösung gemäß den Einschränkungen zu verwenden, die ich habe. Manchmal ist Lesbarkeit wichtiger als Performances, manchmal nicht. Wählen Sie mit Bedacht und folgen Sie nicht dem Trend von 2018, alles in Render-Requisiten umzuwandeln.

Sebastien Lorber
quelle
1
Dieser Ansatz macht es auch einfach, die Requisiten-Do-Children-Komponente weiterzugeben (in diesem Fall Hallo). Ab React 0.14. * Ist die einzige Möglichkeit, Requisiten an untergeordnete Komponenten weiterzugeben, die Verwendung von React.createClone, was möglicherweise teuer ist.
Mukesh Soni
2
Frage: In der Antwort wird "bessere Leistung" erwähnt - was ich nicht verstehe: besser im Vergleich zu welcher anderen Lösung?
Philipp
1
HOCs können im Vergleich zu Laufzeit-Wrappern eine bessere Leistung aufweisen, da sie das Rendern früher kurzschließen können.
Sebastien Lorber
1
Danke dir! Es ist, als hätten Sie Worte aus meinem Monat genommen, aber Sie drücken sie mit größerem Talent aus 👍
MacKentoch
1
Dies ist eine viel bessere Antwort:] Danke!
Cullanrocks
31

Zusätzlich zu Sophies Antwort habe ich auch eine Verwendung beim Senden von untergeordneten Komponententypen gefunden, und zwar in etwa so:

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} />
    }
});
krs
quelle
2
Ich habe keine Dokumentation dazu gesehen delegate. Wie haben Sie sie gefunden?
NVI
4
Sie können einer Komponente beliebige Requisiten hinzufügen und sie nach Belieben benennen. Ich verwende die Datei this.props.delegate in Zeile 4, kann sie aber genauso gut anders benennen.
krs