React.js: envolviendo un componente en otro

187

Muchos lenguajes de plantilla tienen declaraciones de "ranuras" o "rendimiento", que permiten hacer algún tipo de inversión de control para envolver una plantilla dentro de otra.

Angular tiene la opción "transcluir" .

Rails tiene declaración de rendimiento . Si React.js tuviera una declaración de rendimiento, se vería así:

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

Salida deseada:

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

Por desgracia, React.js no tiene un <yield/>. ¿Cómo defino el componente Wrapper para lograr el mismo resultado?

NVI
fuente

Respuestas:

159

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

Esto también se conoce como transclusionen Angular.

childrenes un accesorio especial en React y contendrá lo que está dentro de las etiquetas de sus componentes (aquí <App name={name}/>está dentro Wrapper, por lo que es elchildren

Tenga en cuenta que no necesariamente necesita usar children, que es único para un componente, y también puede usar accesorios normales si lo desea, o mezclar accesorios y niños:

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

Esto es simple y está bien para muchos casos de uso, y lo recomendaría para la mayoría de las aplicaciones de consumo.


renderizar accesorios

Es posible pasar funciones de renderizado a un componente, este patrón generalmente se llama render prop, y el childrenaccesorio se usa a menudo para proporcionar esa devolución de llamada.

Este patrón no está destinado realmente para el diseño. El componente contenedor generalmente se usa para mantener y administrar algún estado e inyectarlo en sus funciones de representación.

Ejemplo de contador:

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

Puede ser aún más elegante e incluso proporcionar un objeto

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

Tenga en cuenta que no necesariamente necesita usar children, es una cuestión de gusto / API.

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

A partir de hoy, muchas bibliotecas están utilizando accesorios de renderizado (React context, React-motion, Apollo ...) porque las personas tienden a encontrar esta API más fácil que HOC. react-powerplug es una colección de componentes simples de render-prop. react-adopt te ayuda a hacer la composición.


Componentes de orden superior (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 de orden superior es generalmente una función que toma un componente y devuelve un nuevo componente.

Usar un componente de orden superior puede ser más eficaz que usar childreno render props, porque el contenedor puede tener la capacidad de cortocircuitar el renderizado un paso adelante shouldComponentUpdate.

Aquí lo estamos usando PureComponent. Al volver a representar la aplicación, si el WrappedAppnombre del accesorio no cambia con el tiempo, el contenedor tiene la capacidad de decir "No necesito renderizar porque los accesorios (en realidad, el nombre) son los mismos que antes". Con la childrensolución basada arriba, incluso si la envoltura es PureComponent, no es el caso porque el elemento hijo se recrea cada vez que se procesa el padre, lo que significa que la envoltura probablemente siempre se volverá a procesar, incluso si el componente envuelto es puro. Hay un complemento de babel que puede ayudar a mitigar esto y garantizar un childrenelemento constante a lo largo del tiempo.


Conclusión

Los componentes de orden superior pueden brindarle un mejor rendimiento. No es tan complicado, pero al principio parece poco amigable.

No migre toda su base de código a HOC después de leer esto. Solo recuerde que en las rutas críticas de su aplicación es posible que desee usar HOC en lugar de envoltorios de tiempo de ejecución por razones de rendimiento, particularmente si el mismo contenedor se usa muchas veces, vale la pena considerar convertirlo en un HOC.

Redux usó al principio un contenedor de tiempo de ejecución <Connect>y luego cambió a un HOC connect(options)(Comp)por razones de rendimiento (de manera predeterminada, el contenedor es puro y está en uso shouldComponentUpdate). Esta es la ilustración perfecta de lo que quería resaltar en esta respuesta.

Tenga en cuenta que si un componente tiene una API de render-prop, generalmente es fácil crear un HOC encima de él, por lo que si es un autor de lib, primero debe escribir una API de render prop y, finalmente, ofrecer una versión de HOC. Esto es lo que hace Apollo con el <Query>componente render-prop, y el graphqlHOC que lo usa.

Personalmente, uso ambos, pero en caso de duda prefiero los HOC porque:

  • Es más idiomático componerlos ( compose(hoc1,hoc2)(Comp)) en comparación con los accesorios de renderizado
  • Me puede dar mejores actuaciones
  • Estoy familiarizado con este estilo de programación.

No dudo en usar / crear versiones HOC de mis herramientas favoritas:

  • Comp de Context.Consumerreacción
  • Sin declarar Subscribe
  • usando graphqlHOC de Apollo en lugar de Queryrender prop

En mi opinión, a veces los accesorios de render hacen que el código sea más legible, a veces menos ... Intento usar la solución más pragmática de acuerdo con las restricciones que tengo. A veces, la legibilidad es más importante que las actuaciones, a veces no. Elija sabiamente y no siga la tendencia de 2018 de convertir todo en accesorios de renderizado.

Sebastien Lorber
fuente
1
Este enfoque también hace que sea fácil pasar los accesorios del componente hijos (en este caso, Hola). A partir de React 0.14. * En adelante, la única forma de pasar accesorios a los componentes secundarios sería usar React.createClone, que podría ser costoso.
Mukesh Soni
2
Pregunta: La respuesta menciona "mejor rendimiento", lo que no entiendo: ¿mejor que otra solución?
Philipp
1
Los HOC pueden tener un mejor rendimiento en comparación con los envoltorios de tiempo de ejecución, ya que pueden provocar un cortocircuito en el procesamiento anterior.
Sebastien Lorber el
1
¡Gracias! Es como si tomaras palabras de mi mes pero las expresas con mayor talento 👍
MacKentoch
1
Esta es una respuesta mucho mejor:] ¡Gracias!
cullanrocks
31

Además de la respuesta de Sophie, también he encontrado un uso en el envío de tipos de componentes secundarios, haciendo algo como esto:

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
fuente
2
No he visto ninguna documentación sobre delegate, ¿cómo la encontraste?
NVI
44
puede agregar los accesorios que desee a un componente y nombrarlos como desee, estoy usando this.props.delegate en la fila 4, pero podría llamarlo de otra manera.
krs