React.js: Emballage d'un composant dans un autre

187

De nombreux langages de modèles ont des instructions "slots" ou "yield", qui permettent de faire une sorte d'inversion de contrôle pour envelopper un modèle dans un autre.

Angular a l' option "transclude" .

Rails a une déclaration de rendement . Si React.js avait une instruction yield, cela ressemblerait à ceci:

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

Sortie désirée:

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

Hélas, React.js n'a pas de fichier <yield/>. Comment définir le composant Wrapper pour obtenir le même résultat?

NVI
la source

Réponses:

159

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

Ceci est également connu sous le nom transclusionde Angular.

childrenest un accessoire spécial dans React et contiendra ce qui est à l'intérieur des balises de votre composant (ici à l' <App name={name}/>intérieur Wrapper, donc c'est lechildren

Notez que vous n'avez pas nécessairement besoin d'utiliser children, ce qui est unique pour un composant, et vous pouvez également utiliser des accessoires normaux si vous le souhaitez, ou mélanger des accessoires et des enfants:

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

C'est simple et adapté à de nombreux cas d'utilisation, et je le recommande pour la plupart des applications grand public.


rendre les accessoires

Il est possible de passer des fonctions de rendu à un composant, ce modèle est généralement appelé render prop, et le childrenprop est souvent utilisé pour fournir ce rappel.

Ce modèle n'est pas vraiment destiné à la mise en page. Le composant wrapper est généralement utilisé pour contenir et gérer un état et l'injecter dans ses fonctions de rendu.

Exemple de compteur:

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

Vous pouvez obtenir encore plus de fantaisie et même fournir un objet

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

Notez que vous n'avez pas forcément besoin d'utiliser children, c'est une question de goût / API.

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

À ce jour, de nombreuses bibliothèques utilisent des accessoires de rendu (contexte React, React-motion, Apollo ...) car les gens ont tendance à trouver cette API plus facile que celle des HOC. react-powerplug est une collection de composants de rendu simples. react-adopt vous aide à faire de la composition.


Composants d'ordre supérieur (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 composant d'ordre supérieur / HOC est généralement une fonction qui prend un composant et renvoie un nouveau composant.

L'utilisation d'un composant d'ordre supérieur peut être plus performante que l'utilisation de childrenou render props, car le wrapper peut avoir la capacité de court-circuiter le rendu avec une longueur d'avance shouldComponentUpdate.

Ici, nous utilisons PureComponent. Lors du re-rendu de l'application, si le WrappedAppnom de prop ne change pas au fil du temps, le wrapper a la capacité de dire "Je n'ai pas besoin de rendre parce que les accessoires (en fait, le nom) sont les mêmes qu'avant". Avec la childrensolution basée ci-dessus, même si le wrapper est PureComponent, ce n'est pas le cas car l'élément children est recréé à chaque fois que le parent est rendu, ce qui signifie que le wrapper sera probablement toujours rendu, même si le composant enveloppé est pur. Il existe un plugin babel qui peut aider à atténuer cela et à garantir un childrenélément constant dans le temps.


Conclusion

Les composants d'ordre supérieur peuvent vous offrir de meilleures performances. Ce n'est pas si compliqué, mais cela semble certainement inamical au début.

Ne migrez pas toute votre base de code vers HOC après avoir lu ceci. N'oubliez pas que sur les chemins critiques de votre application, vous voudrez peut-être utiliser des HOC au lieu de wrappers d'exécution pour des raisons de performances, en particulier si le même wrapper est utilisé plusieurs fois, il vaut la peine d'en faire un HOC.

Redux a d'abord utilisé un wrapper d'exécution <Connect>et est ensuite passé à un HOC connect(options)(Comp)pour des raisons de performances (par défaut, le wrapper est pur et utilisé shouldComponentUpdate). C'est l'illustration parfaite de ce que je voulais mettre en évidence dans cette réponse.

Notez que si un composant a une API de rendu-prop, il est généralement facile de créer un HOC par-dessus, donc si vous êtes un auteur de lib, vous devez d'abord écrire une API de prop de rendu, et éventuellement proposer une version HOC. C'est ce que fait Apollo avec le <Query>composant render-prop, et le graphqlHOC qui l'utilise.

Personnellement, j'utilise les deux, mais en cas de doute je préfère les HOC car:

  • Il est plus idiomatique de les composer ( compose(hoc1,hoc2)(Comp)) par rapport aux accessoires de rendu
  • Cela peut me donner de meilleures performances
  • Je connais ce style de programmation

Je n'hésite pas à utiliser / créer des versions HOC de mes outils préférés:

  • De React Context.Consumercomp
  • Non déclaré Subscribe
  • en utilisant graphqlHOC of Apollo au lieu de Queryrender prop

A mon avis, parfois les accessoires de rendu rendent le code plus lisible, parfois moins ... J'essaye d'utiliser la solution la plus pragmatique en fonction des contraintes que j'ai. Parfois, la lisibilité est plus importante que les performances, parfois non. Choisissez judicieusement et ne suivez pas la tendance 2018 de tout convertir en accessoires de rendu.

Sébastien Lorber
la source
1
Cette approche facilite également la transmission des accessoires au composant do children (dans ce cas, Hello). À partir de React 0.14. *, La seule façon de passer des accessoires aux composants enfants serait d'utiliser React.createClone, ce qui pourrait être coûteux.
Mukesh Soni
2
Question: La réponse mentionne «de meilleures performances» - ce que je ne comprends pas: mieux par rapport à quelle autre solution?
Philipp
1
Les HOC peuvent avoir de meilleures performances par rapport aux wrappers d'exécution, car ils peuvent court-circuiter le rendu plus tôt.
Sebastien Lorber
1
Je vous remercie! C'est comme si vous aviez pris des mots de mon mois mais que vous les
exprimiez
1
C'est une bien meilleure réponse:] Merci!
cullanrocks
31

En plus de la réponse de Sophie, j'ai également trouvé une utilisation dans l'envoi de types de composants enfants, en faisant quelque chose comme ceci:

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
la source
2
Je n'ai vu aucune documentation sur delegate, comment l'avez-vous trouvée?
NVI
4
vous pouvez ajouter les accessoires que vous voulez à un composant et les nommer comme vous le souhaitez, en utilisant this.props.delegate à la ligne 4, mais vous pourriez tout aussi bien lui donner un autre nom.
krs