Componentes de orden superior en ReactJS

Estamos en la versión 0.13 de ReactJS y uno de los cambios de diseño más importantes tiene que ver con ajustarse todo lo posible a ES6. Esto implica, entre otras cosas, el uso de clases para crear componentes; es decir, que ya no podemos usar mixins como estábamos acostumbrados y que, por lo tanto, tendremos que empezar a usar componentes de orden superior en su lugar.

Si queremos explicar como suplir el vacío que ha dejado la falta de soporte de mixins en la nueva versión de ReactJS, deberemos empezar por el principio y hacernos la pregunta obvia:

¿Qué eran los mixins en ReactJS?

A efectos prácticos, un mixin era la combinación de dos objetos. Uno de ellos era tu componente y el otro un fragmento de funcionalidad compartida por varios componentes. Para simplificar las cosas, Facebook nos proporcionó, en versiones anteriores del framework, un "azucarillo sintáctico" que llevaba a cabo esta tarea. Este azucarillo era una propiedad especial con la que contaban todos los componentes de ReactJS, a la cual podemos pasarle un array de objetos que serán "mergeados" con el prototype de nuestro componente. Un ejemplo práctico de tal operación la podéis ver en el siguente ejemplo:

const myMixin = {  
    mySharedbehaviour: function(){
        // Make same shared behaviour between component
    }
};
const MyComponent = React.createClass({

    mixins: [myMixin],

    render(){
        return( <h1>Hello World!</h1> );
    }
});

En este ejemplo podemos apreciar el mecanismo del que hablábamos antes. Todos los objetos que están en el array mixins van a sufrir una operación conocida como mixin funcional. Por lo tanto, van a ser integrados en el prototipo del componente; es decir, dentro de mySharedbehaviour this representa a MyComponent. Llegados a este punto, si queremos reemplazar los mixins tal y como los hemos presentado, debemos entender mejor qué es un mixin funcional.

Mixin Funcional

Concepto originario de la programación funcional, un mixin funcional es la operación por la cual una función toma otra función (estrictamente hablando, el constructor de una clase es una función) y la combina con otro objeto. Un ejemplo básico de este concepto en acción es el siguiente:

class Person{  
  constructor({name = 'Carlos', surname = 'Villuendas'} = {}){
    this._name = name;
    this._surname = surname;
  }

  get name(){ return this._name }
  get surname(){ return this._surname }
}

const makeNice = function( Target ){  
    Object.assign( Target.prototype, { 
        greet(){
            return `Hi, my name is ${this.name} ${this.surname}`
        }
    } );
}

console.log( new Person({name: 'Pepe', surname: 'Luis'}).greet() )

// Hi, my name is Pepe Luis

Aquí hemos realizado un mixin funcional pasando como parámetro el constructor de nuestra clase Person para agregarle la capacidad de saludar. Como puedes imaginar, replicando esta misma operación podemos compartir la habilidad de saludar entre varias clases, sin llegar a repetir el código en cada una de ellas. Te recomiendo que leas detenidamente el artículo de @raganwald Functional Mixins in ECMAScript 2015 para poder profundizar en este concepto. Aunque esta técnica puede ser de gran utilidad en muchos escenarios, padece de una gran limitación: la pérdida de control sobre nuestro código. Con el mixin funcional agregas un comportamiento a TODAS las instancias de la clase Persona, tanto las creadas en el pasado como las que vayas a crear en el futuro. Esto es sinónimo de indeterminación en nuestro código, algo a evitar en la medida de lo posible. Justamente por este problema y las consecuencias que puede acarrear, debemos mejorar la forma de hacer mixin funcional para lograr que los cambios sean lo más localizados posibles. Esta hablidad la conseguimos mediante composición funcional.

Composición Funcional

La composición funcional viene a suplir esta importante limitación del mixin funcional que, aplicado a clases, tiene un ámbito de intervención demasiado amplio. La base de la composición funcional es la herencia de clases. Tendremos una función que toma el constructor de una clase y que devuelve OTRA clase que hereda de la primera, pero que además incluye una nueva serie de habilidades. La gran ventaja de esta solución es que, a diferencia del mixin funcional, solo las instancias de la nueva clase van a tener las nuevas habilidades, sin afectar las instancias de la clase original. Tal vez puedas verlo más claro con este ejemplo:

class Person(){ ... };

const makeCool = function( Target ){  
  return class extends Target{
    beCool(){
      return `Sure, I am ${this.name} and I am NodeJS developer`;
    }
  }  
}

const CoolPerson = makeCool(Person);

console.log(new CoolPerson().beCool()  );  
console.log(new Person().beCool()  ); 

// Sure, I am Carlos and I am NodeJS developer
// (intermediate value).beCool is not a function <- ERROR !!

Como puedes observar, la clase Person no se ve en absoluto afectada por la función makeCool y solo las instancias de la clase CoolPerson tienen las nuevas habilidades. Así, hemos conseguido compartir "habilidades" sin afectar al código que no está bajo nuestro control.

Ahora que sabemos lo que eran los antiguos mixins en ReactJS, cómo funcionaban y cómo replicarlos en nuestras propias clases, nos toca aplicar el mismo razonamiento a las clases ES6.

Componentes de ReactJS como clases

Como he dicho antes, ahora estamos con la versión 0.13 de ReactJS. Como parte del esfuerzo por mantenerse lo más próximo a ES6 como sea posible, ahora los componentes se definen como clases que heredan de React.Component.

Ahora bien, lo que en programación funcional llamamos "composición funcional" no es otra cosa que un decorador en ES6. Si observamos detenidamente la función makeCool en el ejemplo de la sección anterior, veremos las similitudes que hay entre esa función y un decorador de ES6 a nivel de constructor de clase.

Por lo tanto, deberíamos poder aplicar un decorador sobre el constructor de nuestro componente para realizar sobre él una composición funcional:

JS Bin on jsbin.com

Estos componentes a los que se les aplica una composición funcional reciben el nombre de componentes de orden superior.

Así que ya sabes. Cuando tengas cierto comportamiento que quieras compartir entre varios componentes, te propongo que lo extraigas a un decorador de clase y luego decores los constructores de tus componentes de ReactJS con él.

Para que te sirva de inspiración te voy a dejar un repo con unos cuantos de estos decoradores sobre componentes.

Si te ha gustado este post, difunde la palabra. Tampoco dudes en dejar comentarios u observaciones. ¡Gracias! :)

Suscríbete a mi lista de correo

* Campos obligatorios