Componentes de Orden Superior (Higher Order Components) en ReactJS

Hace tiempo escribí un post sobre cómo utilizar mixins en ReactJS para cargar datos de forma asíncrona. Como pasa con casi cualquier librería de Javascript, un año después los mixins ya no son la forma recomendada de hacer las cosas y ni siquiera están soportados si usas la sintaxis de clases de ES2016.

De todas las alternativas que hay para conseguir implementar lo que antes se hacía con mixins, hay una especialmente interesante: los componentes de orden superior o higher order components. Incluso si no usas ReactJS, la idea que hay detrás te puede resultar útil, por lo que te recomiendo que eches un vistazo a las dos primeras partes de este post.

Funciones de orden superior

El calificativo «de orden superior» se aplica a muchos conceptos. Probablemente te suenen las funciones de orden superior o los tipos de orden superior. Intuitivamente, podríamos decir que algo es «de orden superior» si es capaz de operar sobre cosas de su misma categoría, poniéndose por tanto por encima de ellas.

Un tipo de orden superior es, simplificando bastante, un «tipo de tipos». Por ejemplo, List<T> en C# sería un tipo cuyos elementos son tipos de listas, y como instancias de ese tipo tendríamos los tipos List<int> o List<string>.

Una función de orden superior es una función capaz de «trabajar» con funciones. Una función que puede recibir funciones como parámetro o devolverlas como resultado.

Los ejemplos típicos serían el método Enumerable.Select de LINQ en .NET o el método Array.map de Javascript, que reciben funciones como parámetros, pero también podemos tenemos funciones que creen nuevas funciones:

// Función para construir funciones de validación
const maxLength = function(max) {
  return function(value) {
    return value.length <= max;
  };
};

// Función de validación concreta
const isValid = maxLength(4);

isValid('Paco'), // true
isValid('Marcelo'); // false

Hasta aquí, nada raro. Es algo que usamos a diario sin reparar en ello, excepto tal vez el último caso que es un poco menos frecuente y, a la vez, más interesante porque...

En Javascript, las clases son funciones

Cuando en Javascript creamos una "clase", aunque usemos la sintaxis de ES2015, en realidad sólo estamos creando una función, por lo que podemos aplicar las mismas ideas que veíamos antes:

// "Clase" para representar coches
const Car = function(brand) {
  this.brand = brand;
};

// Función para crear nuevas clases a partir de otras
// y añadirles una propiedad color
const addColor = function(Class, color) {
  return function() {
    Class.apply(this, arguments);
    this.color = color;
  };
};

// Clase de coches negros
const BlackCar = addColor(Car, 'black');

// Instanciamos un coche negro
const kitt = new BackCar('Pontiac');

kitt.brand === 'Pontiac'
kitt.color === 'black'

Lo interesante no es que le hayamos añadido una propiedad a un objeto (eso se puede hacer de mil forma en Javacript), sino que hemos creado una nueva "clase" BlackCar a partir de una clase que ya existía.

Además, esa lógica de añadir colores a clases que ya existen es completamente reutilizable:

const Food = function(calories) {
  this.calories = calories;
};

var RedFood = addColor(Food, 'red'); 

const tomato = new RedFood(18);
tomato.calories === 18
tomato.color === 'red'

Puesto que la sintaxis de clases de ES2015 no es más que syntactic sugar sobre funciones constructoras, podemos implementar prácticamente lo mismo usando "clases":

class Car {
  constructor(brand) {
    this.brand = brand;
  }
}

const addColor = function(Class, color) {
  return class extends Class {
    constructor(...args) {
      super(...args);
      this.color = color;
    }
  };
}

const BlackCar = addColor(Car, 'black');

const kitt = new BackCar('Pontiac');

kitt.brand === 'Pontiac'
kitt.color === 'black'

Si quieres profundizar en esta idea, te recomiendo encarecidamente que leas este post de Eduard Tomàs sobre clases como ciudadanos de primer orden en Javascript.

Componentes de orden superior (Higher Order Components)

Partiendo de lo que llevamos viendo todo el post y de que los componentes en ReactJS no son más que "clases", está más o menos claro por dónde van los tiros. La idea es poder construir dinámicamente clases de componentes a partir de otros componentes para añadirles comportamiento.

Y ojo, que aquí lo importante es lo de clases de componentes. En ReactJS es normal crear instancias de componentes que están formados por otros componentes, pero ahora lo que haremos es crear un nuevo tipo de componente que podamos instanciar las veces que queramos.

Vamos a ver un ejemplo muy simple.

Imagina que tienes varios componentes que pueden actuar como elementos de un formulario: TextBox, ComboBox, DateTimePicker, etc. Cada uno encapsula el elemento HTML de bajo nivel correspondiente, pero además le añade cierto comportammiento para facilitar su uso, clases css propias de tu aplicación, etc.

Ahora suponte que a veces quieres usar los componentes con su correspondiente <label>, pero en otras ocasiones no lo necesitas y prefieres dejar un simple placeholder. Una forma sencilla de conseguir esto es crear nuevo componente Label que encapsule cada uno de los componentes anteriores:

<Label text='Nombre:'>
  <TextBox onChange={this.updateName} />
</Label>
<Label text='Fecha:'>
  <DateTimePicker onChange={this.updateDate} />
</Label>

Lo malo es que nos obliga a escribir mucho cada vez que queremos añadir una etiqueta a un control. Una alternativa es utilizar componentes de orden superior:

function addLabel(Control) {
  return class extends ReactJS.Component {
    render() {
      return <label>
               {this.props.label}
               <Control {...this.props}/>
             </label>;
    }
  }
}

const LabeledTextBox = addLabel(TextBox);
const LabeledDateTime = addLabel(DateTimePicker);

// El ejemplo anterior quedaría
<LabeledTextBox label='Nombre:' onChange={this.updateName}/>
<LabeledDateTime label='Fecha:' onChange={this.updateDate}/>

La función addLabel nos permite crear nuevos tipos de componentes a partir de tipos que ya teníamos. En este caso, podemos añadir una etiqueta a cualquiera de los controles que ya habíamos definido previamente.

Insisto, es importante resaltar que LabeledTextBox no es una instancia de un componente, si una clase (una función constructora). Por eso podemos usarla con la sintaxis JSX como si fuese una clase definida normalmente.

Con esta técnica podemos simular bastantes de las cosas que hacíamos antes con los mixins, porque podemos encapsular componentes existentes en nuevos componentes que aporten el comportamiento necesario. Por ejemplo, aquí podéis encontrar el escenario típico de carga asíncrona de datos.

Resumen

Javascript es un lenguaje que tiene un montón de pegas. Eso es indudable. Pero también es un lenguaje muy flexible que se basa en un par de ideas muy sencillas sobre las que se pueden construir cosas más entretenidas.

Su forma de implementar las "clases" a partir de funciones constructoras hace que podamos aprovechar muchas técnicas típicas de programación funcional sobre esa clases, proporcionándonos herramientas que no encontramos en otros lenguajes.

Estamos acostumbrados a usar los componentes de ReactJS a través de JSX y es fácil que se nos olvide que, en el fondo, son simples objetos y funciones de Javascript, y podemos usar todas las técnicas habituales para jugar con ellos. Entre ellas, tenemos los componentes de orden superior son una buena forma de encapsular comportamiento de una manera reutilizable y componible.