React.js: avvolgere un componente in un altro


187

Molti linguaggi template hanno istruzioni "slot" o "yield", che consentono di fare una sorta di inversione di controllo per avvolgere un template all'interno di un altro.

Angolare ha opzione "transclude" .

Rails ha una dichiarazione di rendimento . Se React.js avesse una dichiarazione di rendimento, sarebbe simile al seguente:

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

Uscita desiderata:

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

Purtroppo, React.js non ha a <yield/>. Come definisco il componente Wrapper per ottenere lo stesso output?


Risposte:



159

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

Questo è anche noto come transclusionin Angolare.

childrenè un oggetto speciale in React e conterrà ciò che è all'interno dei tag del tuo componente (qui <App name={name}/>è dentro Wrapper, quindi è ilchildren

Nota che non devi necessariamente usare children, che è unico per un componente, e puoi usare anche oggetti di scena normali se vuoi, o mescolare oggetti di scena e bambini:

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

Questo è semplice e va bene per molti casi d'uso, e lo consiglierei per la maggior parte delle app di consumo.


rendering oggetti di scena

È possibile passare le funzioni di rendering a un componente, questo modello viene generalmente chiamato render prope l' childrenelica viene spesso utilizzata per fornire quel callback.

Questo modello non è pensato per il layout. Il componente wrapper viene generalmente utilizzato per conservare e gestire alcuni stati e iniettarli nelle sue funzioni di rendering.

Contro esempio:

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

Puoi avere ancora più fantasia e persino fornire un oggetto

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

Nota che non è necessario utilizzare necessariamente children, è una questione di gusti / API.

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

Ad oggi, molte biblioteche stanno utilizzando oggetti di rendering (contesto React, React-motion, Apollo ...) perché le persone tendono a trovare questa API più facile di quella HOC. reagire-powerplug è una raccolta di semplici componenti render-prop. reagire adotta ti aiuta a fare composizione.


Componenti di ordine superiore (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);

Un componente / HOC di ordine superiore è generalmente una funzione che accetta un componente e restituisce un nuovo componente.

L'uso di un componente di ordine superiore può essere più efficace dell'uso childreno render props, poiché il wrapper può avere la possibilità di cortocircuitare il rendering con un passo avanti shouldComponentUpdate.

Qui stiamo usando PureComponent. Quando si WrappedAppesegue nuovamente il rendering dell'app, se il nome prop non cambia nel tempo, il wrapper ha la capacità di dire "Non ho bisogno di render perché i puntelli (in realtà, il nome) sono gli stessi di prima". Con la childrensoluzione di base di cui sopra, anche se lo è il wrapper PureComponent, non è così perché l'elemento children viene ricreato ogni volta che viene eseguito il rendering del genitore, il che significa che probabilmente il wrapper eseguirà nuovamente il rendering, anche se il componente wrapping è puro. C'è un plug-in babel che può aiutare a mitigarlo e garantire un childrenelemento costante nel tempo.


Conclusione

I componenti di ordine superiore possono offrirti prestazioni migliori. Non è così complicato ma all'inizio sembra sicuramente ostile.

Non migrare l'intera base di codice in HOC dopo aver letto questo. Ricorda solo che su percorsi critici della tua app potresti voler usare HOC invece dei wrapper di runtime per motivi di prestazioni, in particolare se lo stesso wrapper viene usato molte volte vale la pena considerare di renderlo un HOC.

Redux ha inizialmente utilizzato un wrapper di runtime <Connect>e successivamente è passato a un HOC connect(options)(Comp)per motivi di prestazioni (per impostazione predefinita, il wrapper è puro e utilizzabile shouldComponentUpdate). Questa è la perfetta illustrazione di ciò che volevo evidenziare in questa risposta.

Nota se un componente ha un'API render-prop, in genere è facile creare un HOC sopra di esso, quindi se sei un autore lib, dovresti scrivere prima un'API di rendering prop e infine offrire una versione HOC. Questo è ciò che Apollo fa con il <Query>componente render-prop e l' graphqlHOC che lo utilizza.

Personalmente, utilizzo entrambi, ma in caso di dubbio preferisco gli HOC perché:

  • È più idiomatico comporli ( compose(hoc1,hoc2)(Comp)) rispetto al rendering di oggetti di scena
  • Mi può dare prestazioni migliori
  • Conosco questo stile di programmazione

Non esito a utilizzare / creare versioni HOC dei miei strumenti preferiti:

  • Reagire di Context.Consumercomp
  • di unstated Subscribe
  • usando graphqlHOC di Apollo invece di Queryrender prop

Secondo me, a volte render props rende il codice più leggibile, a volte meno ... Cerco di usare la soluzione più pragmatica secondo i vincoli che ho. A volte la leggibilità è più importante delle prestazioni, a volte no. Scegli saggiamente e non seguire in modo vincolante la tendenza del 2018 di convertire tutto in render-props.


1
Questo approccio semplifica anche il passaggio degli oggetti di scena nel componente per bambini (in questo caso Ciao). Da React 0.14. * In poi l'unico modo per passare oggetti di scena ai componenti per bambini sarebbe usare React.createClone, che potrebbe essere costoso.
Mukesh Soni,

2
Domanda: la risposta menziona "prestazioni migliori" - cosa non capisco: meglio rispetto a quale altra soluzione?
Philipp

1
Gli HOC possono avere prestazioni migliori rispetto ai wrapper di runtime, poiché possono cortocircuitare il rendering in precedenza.
Sebastien Lorber,

1
Grazie! È come se avessi preso parole dal mio mese ma le esprimessi con maggior talento 👍
MacKentoch,

1
Questa è una risposta molto migliore:] Grazie!
Cullanrocks,

31

Oltre alla risposta di Sophie, ho anche trovato utile l'invio di tipi di componenti figlio, facendo qualcosa del genere:

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
Non ho visto alcuna documentazione su delegate, come l'hai trovata?
NVI

4
puoi aggiungere qualsiasi elemento di scena tu voglia a un componente e nominarli come vuoi, sto usando this.props.delegate alla riga 4 ma potresti anche nominarlo come altro.
Kr
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.