React.js Ver tutorial de administración de estado

Publicado: 2022-03-11

Uno de los problemas más grandes y comunes en el desarrollo web front-end es la gestión del estado. Los desarrolladores front-end independientes como yo estamos constantemente enfocados en mantener el objeto de estado sincronizado con su vista y la representación DOM. Los usuarios pueden interactuar con la aplicación de muchas maneras y es una gran tarea proporcionar una transición limpia de un estado a otro.

Con este tutorial de React.js, obtenga más información sobre la administración del estado de vista.

Un poco de historia

No hace mucho tiempo, las aplicaciones web tenían un flujo de datos mucho más simple. El navegador enviaría una solicitud al servidor, toda la lógica de la aplicación se ejecutaría en el servidor y se enviaría una vista HTML completa al navegador para presentarla al usuario. Las acciones posteriores del usuario (como clics, envíos de formularios, etc.) desencadenarían el mismo flujo nuevamente. Las aplicaciones no tenían que preocuparse por el estado del usuario y cada vista podía regenerarse enviando una nueva solicitud al servidor.

Sin embargo, las aplicaciones web crecieron en complejidad y las demandas de los usuarios de UI/UX también estaban avanzando. Recargar toda la página, cuando solo cambia una parte, era ineficaz y lento. Necesitábamos una interacción rápida, ágil y receptiva con un impacto inmediato en la interfaz de usuario.

JavaScript vino al rescate. Los desarrolladores comenzaron a escribir cantidades significativas de código que se ejecutaba en el navegador antes de que se enviara una solicitud al servidor. jQuery también trajo avances significativos al desarrollo web front-end, ya que proporcionó capacidades listas para usar simples y efectivas, como validación del lado del cliente, ventanas modales, mensajes de alerta, animaciones e incluso actualizaciones de página parciales basadas en Ajax.

Comprender la complejidad

Echemos un vistazo a un ejemplo simple de evaluación de la seguridad de una contraseña. Si la contraseña es correcta, el cuadro de entrada debe tener un borde verde y debe mostrar un mensaje agradable. Si la contraseña es débil, el cuadro de entrada debe tener un borde rojo y debe mostrar un mensaje de advertencia. También podemos mostrar una cara sonriente cuando una contraseña es lo suficientemente segura.

El siguiente código demuestra cómo se podría hacer esto mediante la manipulación de DOM. Hay muchos "si" aquí, y el código no es muy fácil de leer.

 if (hasInputBorder()) { removeInputBorder(); } if (text.length === 0) { if (hasMessage()) { removeMessage(); } if (hasSmiley()) { removeSmiley(); } } else { var strength = getPasswordStrength(text); if (!hasInputBorder()) { addInputBorder(); } var color = (strength == 'weak' ? 'red' : 'green'); setInputBorderColor(color); var message = (strength == 'weak' ? "Password is weak" : "That's what I call a password!"); if (hasMessage()) { setMessageText(message); } else { addMessageWithText(message); } if (strength == 'weak') { if (hasSmiley()) { removeSmiley(); } } else { if (!hasSmiley()) { addSmiley(); } } }

Como se muestra arriba, primero debemos verificar si el usuario proporcionó alguna contraseña y manejar el caso en el que el campo de contraseña está vacío. Y en todos los casos, debemos asegurarnos de que todos los elementos DOM relacionados se actualicen correctamente. Esto incluye el mensaje, el borde y la cara sonriente.

Nuestro campo de contraseña puede estar en uno de tres estados: vacío, débil o fuerte. Y como se señaló, tenemos tres elementos DOM diferentes que se ven afectados por el estado del campo de contraseña. Manejar todas las combinaciones y asegurarse de que nuestra vista se muestre correctamente aumenta la complejidad ciclomática incluso para una pieza de código simple como esta.

El DOM funciona en modo retenido , lo que significa que solo recuerda el estado actual. Para modificar nuestra vista, debemos proporcionar instrucciones para cada elemento DOM y programar la transición.

Codificar transiciones en lugar de estados puede ser complejo. La cantidad de bifurcaciones y verificaciones que debemos realizar en nuestro código crece exponencialmente con la cantidad de estados de vista que administrar.

En nuestro ejemplo, definimos tres estados de vista, lo que nos dio 3 * 2 = 6 transiciones. En general, dados N estados, tenemos N * (N - 1) = N^2 - N transiciones que necesitaríamos modelar. Solo piense en la mayor complejidad si agregamos un cuarto estado a nuestro ejemplo.

Normalmente hay demasiado código relacionado con el modelado de las transiciones . Sería mucho mejor si pudiéramos definir nuestros estados de vista y no preocuparnos por todos los detalles de la transición de un estado a otro.

Reduciendo la Complejidad

Suponiendo que pudiéramos declarar el estado de la vista en función del estado del modelo, en lugar de codificar explícitamente la transición de un estado a otro, podríamos tener algo como esto:

 var strength = getPasswordStrength(text); if (text.length == 0) { return div(input({type: 'password', value: text})); } else if (strength == 'weak') { return div( input({type: 'password', value: text, borderColor: 'red'}), span({}, "Weak") ); } else { return div( input({type: 'password', value: text, borderColor: 'green'}), span({}, "That's what I call a password!"), img({class: 'icon-smiley'}) ); }

Aquí tenemos tres ramas simples de código, que representan los tres estados posibles de nuestra aplicación. Solo devolvemos la especificación de la vista en cada rama, según el estado del modelo. Se elimina todo el código de manipulación DOM; solo proporcionamos la información sobre lo que queremos, y no cómo llegar allí.

Si bien este enfoque reduce significativamente la complejidad del código, también asume que hay alguien o algo más que se encarga de la manipulación real del DOM en nuestro nombre.

Aquí es donde entra en juego React. React se asegurará de que el estado de una vista se gestione y actualice inmediatamente en función del estado del modelo de datos subyacente.

Reacción

React es una biblioteca de JavaScript creada por Facebook. Está diseñado para manejar la parte de la interfaz de usuario de las aplicaciones web. Puede pensar en ello como la V en la arquitectura MVC. Está muy enfocado. No hace suposiciones sobre el resto de su pila de tecnología y no maneja nada más que componentes de representación. No proporciona ningún mecanismo de enrutamiento, modelos u otras características que generalmente se incluyen en marcos más grandes. Por lo tanto, puede mezclarlo y usarlo con cualquier otra biblioteca o marco que desee.

React nos permite definir las interfaces de usuario como árboles de componentes compuestos. Un desarrollador de React define esos componentes especificando una función de representación que describe el componente, dado el estado de entrada. Esa función debe ser pura (es decir, no debe tener ningún efecto secundario ni depender de otra cosa que no sea su entrada explícita).

La función de representación devuelve una descripción de la vista, que React llama DOM virtual . Piense en ello como un objeto JavaScript correspondiente al elemento DOM representado.

Cuando cambia el estado del componente, simplemente se vuelve a representar a sí mismo y a todos sus elementos secundarios, devolviendo un nuevo DOM virtual .

Además, React no hará un simple reemplazo de HTML al pasar de un estado a otro. Encontrará la diferencia entre el estado anterior y el nuevo, y calculará el conjunto más efectivo de operaciones DOM para ejecutar una transición.

Incluso sin tener en cuenta el rendimiento, la reducción de la complejidad del código es significativa y nos permite centrar nuestros esfuerzos en las partes más exclusivas y complejas de nuestra aplicación.

Para ser un poco más concretos, así es como se haría nuestro ejemplo de tutorial usando React para administrar los estados de vista.

NOTA: El siguiente ejemplo de código está escrito en el preprocesador JSX, que es una forma común de escribir una interfaz de usuario basada en React.

 function getPasswordStrength(text) { // Some code that calculates the strength given the password text. } var PasswordWithStrength = React.createClass({ getInitialState: function() { return {value: ''}; }, render: function() { var strength = getPasswordStrength(this.state.value); if (this.state.value.length == 0) { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} /> </div>; } else if (strength == 'weak') { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid red'} } /> <span style={{color: 'red'}}>Weak!</span> </div>; } else { return <div> <input type="password" value={this.state.value} onChange={this.handleInputChange} style={ {border: '1px solid green'} } /> <span style={{color: 'green'}}>That's what I call a password!</span> <Emoji value="smiley" /> </div>; } }, handleInputChange: function(ev) { this.setState({value: ev.target.value}); } }); React.render(<PasswordWithStrength />, document.body);

El componente Emoji que se representa cuando la seguridad de la contraseña es correcta con <Emoji value="smiley" /> es simplemente otro componente personalizado (al igual que PasswordWithStrength ). Se define así:

 var Emoji = React.createClass({ render: function() { var emojiSrc = this.props.value + '.png'; return <img src={emojiSrc}></img>; } });

React.js frente a otros

Sin embargo, para ser justos, existen otros marcos JavaScript del lado del cliente (como Ember, Angular, Knockout y otros) que también resolvieron el problema de la administración del estado de vista e incluso le agregaron más funciones. Entonces, ¿por qué querrías usar React en lugar de cualquier otro marco?

Hay dos ventajas clave que tiene React, en comparación con la mayoría de las otras bibliotecas.

Sin vinculación de datos

Algunos de los otros marcos alternativos utilizan el enlace de datos para asignar elementos DOM a propiedades de estado y mantenerlos sincronizados al observar los cambios de propiedad. Este enfoque permite renderizar la vista una vez, y cada cambio desencadena solo modificaciones de los elementos DOM afectados. Otras alternativas usan verificación sucia ; es decir, en lugar de observar los cambios de propiedades individuales, simplemente realizan una diferencia entre el estado anterior y el nuevo. React es más similar al último enfoque pero, en lugar de comparar estados, compara las representaciones de vista.

React no tiene enlace de datos. Se espera que un desarrollador llame al método setState o vuelva a renderizar el componente superior cuando se cambia el estado. Abarca un flujo unidireccional, desde el estado hasta la vista.

Este concepto es fácil de adoptar ya que los desarrolladores generalmente no piensan en el enlace de datos. El foco está en la representación visual de los datos. Por lo tanto, no necesita pensar en las propiedades dependientes, el formato, el enlace de etiquetas HTML especiales, etc. Con React, simplemente vuelve a renderizar el componente cuando cambia el modelo.

Para comprender la diferencia en la gestión del estado de vista aquí, comparemos Ember y React . Crearemos una person objeto que mostrará el nombre completo en mayúsculas. Después de dos segundos, simularemos el cambio y actualizaremos la vista.

 // EXAMPLE USING EMBER App = Ember.Application.create(); App.Person = Ember.Object.extend({ firstName: null, lastName: null, fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); }.property('firstName', 'lastName') }); var person = App.Person.create({ firstName: "John", lastName: "Doe" }); Ember.Handlebars.helper('upcase', function(value) { return value.toUpperCase(); }); App.IndexRoute = Ember.Route.extend({ model: function () { return person; } }); setTimeout(function() { person.set('firstName', 'Harry'); }, 2000); // Templates: <script type="text/x-handlebars"> <h2>Welcome to Ember.js</h2> {{outlet}} </script> <script type="text/x-handlebars" data-template-name="index"> The current user is: {{upcase model.fullName}} </script>

Creamos un objeto con las firstName , lastName y fullName . Dado que Ember está observando cambios en las propiedades, tuvimos que especificar que fullName depende de firstName y lastName . Para hacer esto, agregamos .property('firstName', 'lastName') cuando definimos el fullName .

Después de dos segundos, person.set('firstName', 'Harry'); es ejecutado. Esto desencadenó la actualización de la vista y su vinculación.

Ahora hagamos lo mismo en React.

 // EXAMPLE USING REACT var CurrentUser = React.createClass({ render: function() { return <div>The current user is: {this.props.user.fullName().toUpperCase()}</div>; } }); var person = { firstName: 'John', lastName: 'Doe', fullName: function() { return this.firstName + ' ' + this.lastName; } }; var currentUser = React.render(<CurrentUser user={person}/>, document.body); setTimeout(function() { person.firstName = 'Harry'; currentUser.setProps({user: person}); }, 2000);

Aunque el código de Ember es simple y fácil de leer, es obvio que React gana en simplicidad. La person es un objeto simple de JavaScript, con el fullName simplemente siendo una función.

Sin plantillas

Cada marco alternativo tiene una forma diferente de manejar las plantillas. Algunos de ellos usan cadenas que se compilan en JavaScript, mientras que otros usan elementos DOM directamente. La mayoría de ellos utilizan etiquetas y atributos HTML personalizados que luego se "compilan" en HTML.

Las plantillas no forman parte del código JavaScript. Debido a esto, cada alternativa necesita una forma personalizada de representar operaciones comunes, condicionales, iteraciones, llamadas a funciones, etc. Todas terminan creando un nuevo pseudolenguaje que los desarrolladores deben aprender.

No hay plantillas en React, todo es simplemente JavaScript antiguo.

React usa todo el poder de JavaScript para generar la vista. El método de renderizado del componente es una función de JavaScript.

JSX está disponible como un preprocesador que convierte la "sintaxis similar a HTML" en JavaScript normal, pero JSX es opcional y puede usar JavaScript estándar sin ningún preprocesador. También puede aprovechar las herramientas de JavaScript existentes. Linters, preprocesadores, anotaciones de tipo, minificación, eliminación de código muerto, etc.

Usemos nuevamente un ejemplo concreto para comparar React con uno de los marcos alternativos para la administración del estado de vista.

El siguiente tutorial es un ejemplo del uso de AngularJS para enumerar hashtags y el recuento de tweets para cada uno de ellos. La lista se ordena por conteo y se muestra un mensaje si no hay hashtags.

 <!-- EXAMPLE USING ANGULAR --> <div ng-controller="MyCtrl"> <ul ng-show="hashTags.length > 0"> <li ng-repeat="hashTag in hashTags | orderBy:'tweetCount':true"> {{hashTag.name}} - {{hashTag.tweetCount}} tweets </li> </ul> <span ng-show="hashTags.length == 0">No hashtags found!</span> </div>

Para poder hacer esta lista, un desarrollador debe conocer las AngularJS directives , ng-show y ng-repeat . Luego necesita aprender sobre los AngularJS filters para comprender orderBy . ¡Mucho trabajo para algo tan simple como generar una lista!

Ahora consideremos el ejemplo de React que hace lo mismo:

 // EXAMPLE USING REACT function byTweetCountDesc(h1, h2) { return h2.tweetCount - h1.tweetCount; } //... render: function() { if (this.state.hashTags.length > 0) { var comps = this.state.hashTags.sort(byTweetCountDesc).map(function(hashTag, index) { return <li key={index}> {hashTag.name} - {hashTag.tweetCount} tweets </li>; }); return <ul>{comps}</ul>; } else { return <span>No hashtags found!</span> } }

Incluso si usamos el enfoque "más avanzado" y JSX, todos los desarrolladores web con una comprensión básica de JavaScript pueden leer fácilmente el código anterior y comprender lo que hace. La verificación condicional estándar usando if , la iteración usando map() y un 'sort()' estándar es algo natural para cualquier desarrollador, por lo que no hay sintaxis adicional ni otros conceptos que aprender.

Conclusión

La conclusión principal de este tutorial de React.js es el hecho de que React le permite concentrarse en la administración del estado de vista real en lugar de en las transiciones, lo que simplifica su trabajo y su aplicación.

La curva de aprendizaje para adoptar React es bastante trivial. No hay que dominar un lenguaje de plantillas personalizado, no hay que preocuparse por el enlace de datos, y todo se reduce a las funciones de JavaScript que describen los elementos de la interfaz de usuario.

Para obtener más información sobre cómo simplificar el código de su aplicación con React, eche un vistazo a esta charla de Steven Luscher, Decomplexifying Code with React.

Aquí hay algunas lecturas adicionales para cualquiera que quiera dar el siguiente paso y comenzar a usar React:

  • http://jlongster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome