web-dev-qa-db-fra.com

React.js: Envelopper un composant dans un autre

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

Angular a option "transclude" .

Rails a déclaration de rendement . Si React.js avait une déclaration de rendement, elle 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 <yield/>. Comment définir le composant Wrapper pour obtenir le même résultat?

164
NVI

Essayer:

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

Voir Plusieurs composants: enfants et Type des accessoires enfants dans la documentation pour plus d'informations.

221
Sophie Alpert

Utiliser 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 de transclusion in Angular.

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

Notez que vous n'avez pas nécessairement besoin d'utiliser children, qui est unique pour un composant, et vous pouvez aussi 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);
_

Ceci est simple et convient à de nombreux cas d'utilisation, et je le recommanderais à la plupart des applications grand public.


rendre les accessoires

Il est possible de transmettre des fonctions de rendu à un composant. Ce modèle est généralement appelé render prop , et la propriété children est souvent utilisée 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 conserver et gérer un état et l'injecter dans ses fonctions de rendu.

Contre exemple:

_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 nécessairement 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, etc.), car les utilisateurs ont tendance à trouver cette API plus facile que les HOC. react-powerplug est une collection de composants simples de rendu-prop. réagit-adopte vous aide à composer.


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 retourne un nouveau composant.

L'utilisation d'un composant d'ordre supérieur peut être plus performante que d'utiliser children ou _render props_, car le wrapper peut avoir la possibilité de court-circuiter le rendu d'un pas en avant avec shouldComponentUpdate.

Ici, nous utilisons PureComponent. Lors du re-rendu de l'application, si la propriété WrappedApp nom ne change pas au fil du temps, le wrapper a la possibilité de dire "Je n'ai pas besoin de restituer car les accessoires (en réalité, le nom) sont les mêmes qu'auparavant." ". Avec la solution ci-dessus basée sur children, 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 toujours toujours rendu, même si le composant encapsulé est pur. Il existe un plugin babel qui peut aider à atténuer cela et à assurer un élément constant children au fil du temps.


Conclusion

Les composants d'ordre supérieur peuvent vous donner de meilleures performances. Ce n'est pas si compliqué, mais au premier abord, ça a l'air désagréable.

Après avoir lu ceci, ne migrez pas toute votre base de code vers HOC. N'oubliez pas que sur les chemins critiques de votre application, vous pouvez utiliser des HOC plutôt que des wrappers d'exécution pour des raisons de performances, en particulier si le même wrapper est souvent utilisé, cela 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 utilise shouldComponentUpdate). C’est l’illustration parfaite de ce que je voulais souligner dans cette réponse.

Notez que si un composant a une API render-prop, il est généralement facile de créer un HOC par dessus, donc si vous êtes un auteur de bibliothèque, vous devez d'abord écrire une API de prop, puis éventuellement proposer une version HOC. C’est ce que fait Apollo avec le composant _<Query>_ render-prop et le graphql HOC 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)) que de rendre les accessoires
  • 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:

  • React's _Context.Consumer_ comp
  • Unstated's Subscribe
  • en utilisant graphql HOC of Apollo au lieu de Query render prop

À mon avis, rendre parfois les accessoires rendent le code plus lisible, parfois moins ... J'essaie 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 de 2018 qui consiste à tout convertir en accessoires de rendu.

131
Sebastien Lorber

En plus de la réponse de Sophie, j'ai également trouvé une utilité dans l'envoi de types de composants enfants, en procédant comme suit:

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