Back

/ 6 min read

¿Por qué NO usar HOC en tus aplicaciones React?

Skyscraper

La comunidad de React se está alejando de HOCs (componentes de orden superior) a favor de los render prop components (RPC). En su mayor parte, los componentes HOC y render prop resuelven el mismo problema. Sin embargo, los render prop components están ganando popularidad porque son más declarativos y flexibles que un HOC.

¿Qué es un HOC?

Caso de uso mas conocido de render prop components

import React from 'react'
import ReactDOM from 'react-dom'
import { Route } from 'react-router-dom'
// wrapping/composing
// You can spread routeProps to make them available to your rendered Component
function FadingRoute({ component: Component, ...rest }) {
return (
<Route
{...rest}
render={(routeProps) => (
<FadeIn>
<Component {...routeProps} />
</FadeIn>
)}
/>
)
}

Hay muchas cosas buenas acerca de los componentes de orden superior. Un HOC es una función que acepta un componente simple y devuelve un nuevo componente que se puede extender su funcionalidad. Los conceptos detrás de HOC son una combinación de la funcionalidad y la composición funcional de “mixins” (ahora en desuso de React). La idea es encapsular la funcionalidad común y hacerla reutilizable.

¿Mixins? Si no has oído hablar de los mixins es porque se supone que ya no debes usarlos. Hace mucho tiempo, el equipo de React decidió que los mixins se consideran obsoletos. En ese momento, le pidieron a la gente que considerara los componentes de orden superior como la solución.

Ahora la comunidad está impulsando los render prop components en su lugar. Antes de profundizar en los render prop components, comprendamos un poco cómo funciona HOC.

En general, el objetivo de un HOC es proporcionar props derivados a un componente secundario. Al igual que react-redux, esos props pueden ser valores o funciones simples.

El caso clásico: connect()

Quizás el HOC más familiar es la función connect() de react-redux. Con este HOC, el store de Redux de su aplicación se puede “conectar” a un componente normal. La función connect() proporciona una interfaz simple para derivar props del estado de Redux y pasar esos props al componente conectado.

Cada HOC proporciona al menos una de dos cosas al componente secundario:

  • props derivados
  • funciones tipo callback

En el contexto de connect(), esto se relaciona con las funciones mapStateToProps y mapDispatchToProps.

Punto clave: un HOC realmente no “encapsula” su componente secundario, sino que extiende su componente. Es una diferencia sutil pero importante.

Una de las razones para preferir los componentes de render prop es que no subvierten el árbol normal de componentes de React.

El problema: abusar de HOC

A pesar de todos los beneficios de un HOC, existen algunos problemas con el patrón. Muchos de los problemas del patrón mixins aún se aplican a HOC: puede hacer que el código sea más confuso. Un HOC bien diseñado como connect() hace que el código sea mejor. Pero hay formas de abusar del patrón y hacer que el código sea más difícil de entender.

Los problemas surgen cuando los desarrolladores ven HOC como la solución a todos los problemas.

Por ejemplo, no es una buena idea usar un HOC para hacer algo que puede hacer un componente normal. En el ejemplo de connect(), HOC es un ajuste natural. Pero, ¿y si añadimos un tipo diferente de funcionalidad?

A continuación, puede ver un abuso del patrón HOC. Estamos creando un nuevo MyThingRowComponent usando un row HOC. La clave a tener en cuenta es que row en realidad no pasa ningún prop al componente envuelto.

El row HOC se utiliza para envolver un MyThingComponent para que siempre esté contenido en un <div /> con los nombres de clase correctos. Puede ver que lo aplicamos de manera muy similar a la forma en que usamos connect. Pasamos alguna configuración y luego el componente que pretendemos extender.

// Bad: Using an HOC to provide a wrapper div
import row from 'utils/row'
import MyThing from './MyThing'
const MyThingRow = row({ backgroundColor: 'green' })(MyThing)
export default MyThingRow

¿Por qué no un row HOC?

El problema se vuelve más claro cuando observa el código del row HOC. Los componentes de orden superior rompen la forma normal en que JSX administra los componentes anidados; recuerde que un HOC crea un nuevo componente fusionado. Debido a la extraña naturaleza de los HOC, es importante usarlos solo cuando necesite props derivados.

En este caso, un row HOC rompe las reglas básicas de lo que debe proporcionar un HOC.

Cosas que rompen las reglas de HOC:

  • No proporcionar ningún prop derivado
  • No proporcionar ningún callback props

Confuso y restrictivo:

  • Crea un nuevo componente “encapsulado”, lo que complica el árbol de componentes
  • Solo admite un solo hijo
  • Requiere código de compatibilidad adicional para funcionar como un componente “normal”

Uso de un componente contenedor

¿Cómo aplicamos un componente contenedor en lugar de un HOC? Cuando nuestro “contenedor” es un componente regular, ¡tenemos múltiples opciones sobre cómo usarlo!

Un caso común sería utilizar manualmente <MyThing /> dentro del contenedor <Row />. Aquí puede ver que estamos usando ambos componentes y al final creamos uno nuevo <Somewhere /> componente.

El beneficio aquí es que podemos seguir dando al implementador el control total.

La desventaja de este enfoque es que debe hacerlo en todos los lugares que utilice <MyThing />.

// Option 1: manually wrap MyThing
import React from 'react'
import Row from 'utils/Row'
import MyThing from './MyThing'
const Somewhere = () => (
<div>
{/* ... */}
<Row backgroundColor='green'>
<MyThing name='hello' />
</Row>
</div>
)
export default Somewhere

Uso de render props

En el caso de nuestro componente Row, podemos usar JSX declarativo en lugar de usar un HOC. Esto funciona porque no necesitamos que los hijos de Row reciban props derivados. Las cosas se complican cuando necesitas que los hijos tengan que saber de los props derivados.

¿Cómo pasamos props derivados de un componente a sus hijos? La respuesta es utilizar render props.

En resumen, un componente render prop es una función que se utiliza para representar a sus elementos secundarios. En lugar de renderizar hijos normalmente, el componente render prop llama a la función render y usa el resultado como los hijos. Esto permite que el componente render prop proporcione props derivados a la función render.

¿Suena confuso? En la práctica, es muy similar a la forma en que funciona un HOC. Si alguna vez ha usado el componente de react-router, ¡ya has usado un render prop! De ahí surgió originalmente la idea.

Idea clave: el uso de render props le permite a su contenedor pasar props derivados a sus hijos.

Resumen

En resumen, no deberías usar HOCs en tus aplicaciones React porque…

  1. Crea un nuevo componente “encapsulado”, lo que complica el árbol de componentes y la extensibilidad del mismo
  2. Solo admite un hijo por uso
  3. Requiere código de compatibilidad adicional para funcionar como un componente “normal”
  4. Dificultad para pasar props derivados

Lo que me encanta del patrón Render Prop es que puede encapsular la lógica de un componente sin sacrificar la personalización y la simplicidad en el JSX.

Recursos útiles para aprender más

Si te quedaron algunas dudas o lagunas, te recomiendo estos recursos: