Hace unos años que empezó React su andadura, cuando arrancó, recuerdo haberle echado un mal vistazo a la tecnología y haberla desechado. Los motivos principales de esta decisión:
HTML en el JavaScript ¿ Estamos locos?
No existe el binding two way, ¡Qué atraso!
Sólo podemos comunicarnos con componentes padres vía callbacks... ¡Vaya infierno de llamadas de ida y vuelta que se monta!
Lo que no entendía era como un equipo brillante de desarrolladores, podía haber parido esto y cómo más y más empresas que su negocio depende de tener un buen Front-End (Facebook, Netflix, Uber, Airbnb...) la estaban adoptando como estándar.
Tocaba volver a jugar con React y darle una mirada sin prejuicios.
Esto de meter HTML en el JavaScript ¿No es una guarrería?
Estamos acostumbrados a que cuando embebemos HTML en un JavaScript, lo hacemos metiéndolo en una cadena de texto (si somos espabilados usamos los backticks para tener multilínea), esto es algo malo ya que ese string no lleva ningún control, si nos equivocamos (que seguramente lo haremos) no veremos el fallo hasta que ejecutemos ¡Sorpresa!.
Otra cosa a la que estamos acostumbrados es a meterle "esteroides" a un HTML, es decir, le mezclamos unos tags de bindings embebidos en el HTML, es decir estamos metiendo JavaScript en el HTML, si nos fijamos, esto tampoco es algo muy bueno... si nos equivocamos al teclear un binding, volvemos a liarla con un castañazo de proporciones bíblicas cuando estamos ejecutando la aplicación (nota: hay frameworks como Angular en el que están trabajando para hacer herramientas que chequeen este HTML + bindings).
¿Y si montáramos el interfaz de usuario con JavaScript?
var ContactRowComponent = function (props) { var contact = props.contact || {}; return React.createElement('tr', null, React.createElement('td', null, contact.name), React.createElement('td', null, contact.phone), React.createElement('td', null, contact.email) ) };
Sin duda alguna con esta aproximación es más fácil para que tu IDE favorito o herramientas encuentren errores que hayamos cometido al teclear el interfaz de usuario, pero... es algo lejos de ser cómodo de codificar, ¿ No sería más fácil teclear directamente el HTML y que una herramienta tradujera a ese galimatias de CreateElement? ¡ Ahí tenemos el JSX!
Tecleamos este código en JavaScript
var ContactRowComponent = function (props) { var contact = props.contact || {}; return ( <tr> <td>{contact.name}</td> <td>{contact.phone}</td> <td>{contact.email}</td> </tr> ); };
Y el internamente lo traduce a:
var ContactPropTypes = App.PropTypes.ContactPropTypes; var ContactRowComponent = function (props) { var contact = props.contact || {}; return React.createElement('tr', null, React.createElement('td', null, contact.name), React.createElement('td', null, contact.phone), React.createElement('td', null, contact.email) ) };
Con lo que tenemos azucar sintáctico y control de errores. Si le damos una vuelta de tuerca más y metemos por ejemplo TypeScript (TSX) podemos tener hasta mensajes de error cuando bindeamos a una propiedad un valor con un tipo equivocado, y pueden aparecer estos errores directamente en tiempo de edición.
Vale eso está muy bien pero...¿ Dónde está el 'separation of concerns'?
Aquí viene la parte más interesante, un componente de React es puro interfaz de usuario, ¿ Qué hago con mi lógica de negocio o por ejemplo, mis llamadas a una API REST? Me las llevo fuera, a módulos / ficheros / clases js que no tienen nada que ver con mi UI, ¿ Qué obtengo con esto? Clases y componentes más pequeños que hacen una cosa y una sola cosa, que además son fáciles de testear por separado.
Lo que haríamos mal:
Componente Sphaguetti
class MembersTable extends React.Component { constructor(props) { super(props); // set initial state this.state = { members: [] }; } // Standard react lifecycle function: // https://facebook.github.io/react/docs/component-specs.html componentWillMount() { fetch(gitHubMembersUrl) .then(response => this.checkStatus(response)) .then(response => this.parseJSON(response)) .then(data => this.resolveMembers(data)); } checkStatus(response) { if (!(response.status >= 200 && response.status < 300)) { const error = new Error(response.statusText); throw error; } return Promise.resolve(response); } parseJSON(response) { return response.json(); } resolveMembers(data) { const members = data.map((gitHubMember) => { const member = new MemberEntity(); member.id = gitHubMember.id; member.login = gitHubMember.login; member.avatar_url = gitHubMember.avatar_url; return member; }); this.setState({ members }); } } render() { /// ... } }
Una aproximación de como podríamos separar esto:
Componente
class MembersTable extends React.Component { constructor(props) { super(props); // set initial state this.state = { members: [] }; } // Standard react lifecycle function: // https://facebook.github.io/react/docs/component-specs.html componentWillMount() { memberAPI.getAllMembers().then(members => this.setState({ members }) ); } render() { /// ... } }
API Rest:
class MemberAPI { // Just return a copy of the mock data getAllMembers() { const gitHubMembersUrl = 'https://api.github.com/orgs/lemoncode/members'; return fetch(gitHubMembersUrl) .then(response => this.checkStatus(response)) .then(response => this.parseJSON(response)) .then(data => this.resolveMembers(data)); } checkStatus(response) { if (!(response.status >= 200 && response.status < 300)) { const error = new Error(response.statusText); throw error; } return Promise.resolve(response); } parseJSON(response) { return response.json(); } resolveMembers(data) { const members = data.map((gitHubMember) => { const member = new MemberEntity(); member.id = gitHubMember.id; member.login = gitHubMember.login; member.avatar_url = gitHubMember.avatar_url; return member; }); return Promise.resolve(members); } } const memberAPI = new MemberAPI(); export default memberAPI;
Esto empieza a sonarme bien... pero lo que no entiendo es ¿por qué no soporta el binding two way? ¿ Es una limitación de la tecnología?
La primera vez que descrubrí este tipo de enlaces fue con WPF / Silverlight, recuerdo que era uno de los factores diferenciales de esta tecnología, ... ¡ la de trucos de magía que hicimos con este tipo de enlace a datos! Era algo tan bueno que hasta Google copia la idea en Angular 1 con su ng-model.
Con el paso del tiempo y el uso / abuso de este tipo de enlace, nos dimos cuenta de que en escenarios de complejidad media o alta se convierten en un auténtico quebradero de cabeza:
- Si rompemos en subcomponente tenemos que un componente descendiente que puede estar cuatro niveles más abajo y actualiza los datos a un componente raíz sin avisarle, a quema ropa.
- Por otro lado esto lleva a escenario muy dificiles de depurar, ¿ quién actualizó qué y cuándo? Si A actualiza a B, B actualiza a C y C actualiza a A....¡fiesta!
- Como último punto hablemos de rendimiento, el tener una "magia" que está todo el rato mirando si hay cambios en el UI o cambios en las variables para actualizarlas en un sentido u otro penaliza mucho en rendimiento.
- Los que hayáis trabajado con Angular 1 seguramente habréis abusado de $watch, y en algunas ocasiones generado bugs difíciles de seguir y tenido problemas de rendimiento.
Esto nos hace volver a un flujo de datos unidireccional
¿ Qué ventajas tiene esto?
- Un componente sabe en todo momento cuando le actualizan los datos.
- Sólo tenemos una fuente de verdad.
- Si manejamos estructuras de datos inmutables el flujo de actualización puede ser muy muy rápido (en vez de comparar si ha cambiado el contenido de cada objeto y subobjeto, sólo tiene que comparar la dirección de memoria del mismo).
Pero no es oro todo lo que reluce...
Burbujear callbacks puede llevarnos al "callback hell", para evitar esto tenemos propuestas como las de Redux que hace el desarrollo mucho más mantenible.
¿ De rendimiento cómo va?
Como una moto, además de usar el flujo unidireccional, React monta un DOM Virtual que hace que las actualizaciones / interacciones con el interfaz de usuario sean muy rápidas.
¿ Y para integrarlo en una aplicación antigua?
En otros frameworks o librerías te ves con la tesitura del "todo o nada". Con React puedes ir reemplazando trozos de tu aplicación antigua por componentes React. Para ponértelo fácil en Lemoncode estamos trabajando en un set de ejemplos sobre cómo integrar esta tecnología en proyectos antiguos.
Suena muy interesante, ¿Dónde puedo comprobar que hay grandes empresas utilizando esta tecnología?
Sólo tenemos que irnos a GitHub ,youtube o blogs corporativos y ver todas las aportaciones que han hecho, aquí van unos cuantos:
Airbnb:
https://github.com/airbnb/enzyme
https://github.com/airbnb/javascript/tree/master/react
https://github.com/airbnb/react-dates
https://facebook.github.io/react/
Uber
https://github.com/uber/react-vis
https://github.com/uber/react-map-gl
Microsoft
https://github.com/OfficeDev/office-ui-fabric-react
Netflix
https://www.youtube.com/watch?v=5sETJs2_jwo
https://www.youtube.com/watch?v=AslncyG8whg
Atlassian
https://developer.atlassian.com/blog/2015/07/react-is-an-exciting-future/
https://developer.atlassian.com/blog/2015/02/rebuilding-hipchat-with-react/
Además, existen un buen número de alternativas basadas en React: inferno, preact, rax.
¿ Y cómo aplica esto en un proyecto de envergadura media o grande?
En proyectos de cierta complejidad (es decir en lo que se convierten la mayoría de proyectos "fáciles" con el paso del tiempo), lo ideal es complementar React con un patrón, en este caso la variante de Flux: Redux. Esto lo cubriremos en otro post.
Esto suena bien tengo ganas de probarlo ¿ Por dónde puedo empezar?
Existen varias opciones para arrancarte, puedes tirar de Pluralsight o Egghead, o multitud de material que hay disponible.
Nosotros ofrecemos un curso online en español de 6 horas de duración que te permitirá darte una primera zambullida con React. El curso tiene un enfoque 100% práctico, y sólo cuesta 29 €.
Conclusión
Parece que React ha llegado para quedarse una temporada y ha sido aceptado por la industria, ¿Se convertirá en el nuevo JQuery o Angular 1?
¿Tú que opinas?