ES6 en React y resolviendo el binding del this
Publicado por Miguel Viera el 02/01/2017
Estás últimas semanas he estado aprendiendo React para un pet project que estoy haciendo con mis compañeros Ronny y Modesto.
Tiramos lo que teníamos de nuestra interfaz hecha con Polymer (que no era mucho tampoco) puesto que nos veíamos incapaces de avanzar de una forma constante ya que nos encontrábamos con problemas a cada momento que decidíamos sentarnos a programar.
Otro motivo para desechar Polymer es que testearlo se nos hacía un poco complicado. Además no nos terminaba de encajar el modelo que Google propone para testear el comportamiento de tu componente empleando la librería de Node.js llamada web-component-tester. Lo malo es que al ejecutar los tests, web-component-tester necesita lanzar una instancia de Webdriver para correr Google Chrome y probar la lógica que quieras probar. Esto nos complica también el hecho de que si quisieramos lanzar los test en un entorno de integración continua, no podríamos ni siquiera usando PhantomJS pues aún no soporta el estándar de Web Components en el que Polymer se basa.
Estas y varias otras razones (compatibilidad con múltiples navegadores, mayor cantidad de documentación y un maravilloso curso de introducción de Corey House en Pluralsight) nos hicieron decantarnos por React.js para fabricar la interfaz web de nuestra aplicación.
En este post quisiera hablar en concreto de dos decisiones que hemos tomado durante el desarrollo:
La primera fue usar ECMAScript 6 en el proyecto. Preferimos ES6 porque no tiene mucho sentido seguir usando ES5 a día de hoy, puesto que ES6 es el estándar.
De entre los approaches que hemos seguido para crear componentes en React, al final hemos optado por emplear las clases de ES6. Aunque no nos parece una solución completamente limpia y nos gusta más el enfoque de usar únicamente funciones en JavaScript, nos parece mejor que usar el React.createClass pasándole el objeto que contiene la definición de nuestras funciones dentro del componente o usar meramente composición. La razón es que abstraernos de la librería nos parecía demasiado esfuerzo para acabar con una solución demasiado compleja.
Aquí tenemos un ejemplo de componente que hemos creado con React. Como vemos es tan sencillo como crear una clase y extender de React.Component.
La decisión que tomamos tiene varias implicaciones, lo cual nos lleva a nuestra segunda decisión. Decidir cuál es el mecanismo para hacer el binding del this cuando la función this.handleChange es pasada como callback a un componente hijo.
Si nos fijamos en el render, al pasarle la función que tenemos asociada en nuestra clase al onChange, lo que ocurre básicamente es que JavaScript no liga el this que usa la función internamente al de la instancia de la clase que pasa esa función. Para resolver esta situación hemos encontrado diversas soluciones:
- Usar el React.createClass. Si empleamos la función que nos provee React para crear componentes, esta internamente hará toda la magia por nosotros. Usar el bind cuando pasemos nuestra función como callback al componente hijo: “onChange={this.handleChange.bind(this)}”
- Pasarle un Arrow Function con la llamada al método en onChange: “onChange={ value => { this.handleChange(value) }}
- Declarar handleChange dentro del constructor empleando un Arrow Function
Estas son las tres maneras más sencillas de resolver nuestro problema sin tener que recurrir a mecanismos externos. Sin embargo, ninguna de ellas nos gustaba, y en caso de tener que adoptar alguna de las tres últimas, preferiríamos optar por emplear React.createClass.
Buscando otras formas de resolver este problema, encontramos un mecanismo que está por implementar en la siguiente especificación del lenguaje (ES2016), que básicamente es el emplear un decorador de función para que te haga el binding automáticamente empleando la librería “autobind-decorator” disponible en NPM. El resultado final sería algo tal que así:
Esta opción tampoco nos acababa de convencer, dado que tenemos que estar importando en cada componente la librería de autobind y no nos parece un solución lo suficientemente limpia como para tenerla en consideración. Una cosa a tener en cuenta para usar este mecanismo, en caso de estar usando Babel 6 para transpilar, debes utilizar también el preset “babel-plugin-transform-decorators-legacy” para que funcione adecuadamente (todo está explicando en su Github de todas maneras).
La opción que al final elegimos fue la última: emplear una Arrow Function en una propiedad de la clase. El problema con el que nos encontramos al usar esta solución, es que la especificación actual del lenguaje no soporta esta característica. Hay dos formas de lograr que funcione. La primera es usar el plugin de Babel “transform-class-properties” y la segunda es establecer el preset “stage-2” de Babel instalando “babel-preset-stage-2” en NPM. Al final, todo quedaría de la siguiente forma:
Un último detalle a tener en cuenta al usar esta solución y el autobind, es que dado que nosotros usamos eslint, tenemos que configurarlo para que soporte ambas features. Para arreglarlo, basta con únicamente usar “babel-eslint” como parser del eslinter y añadir en nuestro .babelrc lo siguiente:
Y listo, con eso, ya estaría todo solucionado.
Esta alternativa es, desde nuestro punto de vista, la que menor ruido genera, y a nivel de legibilidad, la más simple. Al final, una simple Arrow Function conserva el binding del this en el enclosing scope, por lo que todo se hace forma automática y se asemeja a lo que ya estamos acostumbrados en ES6.
Cualquier clase de feedback será bien recibido. Aquí dejo unos enlaces que podrían ser de interés. Un saludo!
Publicado originalmente en el blog de Miguel Viera.