Você está na página 1de 52

Universidad Mayor de “San Simón”

Facultad de Ciencias y Tecnología

Diseño de Aplicaciones Web basadas en Arquitectura de

Capas usando la Librería Redux


Elaborado por: Miguel Angel López Peredo

Carrera: Ingeniería de Sistemas

Tutor: Ing. Edson Ariel Terceros Torrico

Fecha: Septiembre de 2018

Cochabamba – Bolivia
2

A mi madre y a mi hijo, con mucho

amor y cariño les dedico todo mi

esfuerzo y trabajo puesto para la

realización de esta monografía


3

Agradecimientos:

A Dios porque está conmigo en cada paso que


doy, cuidándome y dándome fortaleza para
continuar.

A mi madre, quien a lo largo de mi vida ha


velado por mi bienestar y educación siendo mi
apoyo en todo momento.

A Mi hijo, por ser mi motor de continua


superación.

A mi hermana, por su apoyo incondicional que


me ha ayudado y llevado hasta donde estoy
ahora.

A mis hermanos, por sus consejos y


experiencias.

A Sigrid, por su confianza para lograr un


objetivo más en mi vida.

A Claudia Ossio, por su constante apoyo en el


Diplomado.

Al Ing. Edson Terceros, por su paciencia y


comprensión.

A mis amigos, por sus enseñanzas y consejos


para seguir superándome.
4

TABLA DE CONTENIDO
Resumen.......................................................................................................................................... 8

Introducción .................................................................................................................................. 10

1. Generalidades..................................................................................................................... 12

1.1 Antecedentes Generales.............................................................................................. 12

1.2 Antecedentes Específicos ........................................................................................... 13

2. Metodología ....................................................................................................................... 14

3. Subtemas ............................................................................................................................ 14

3.1 Angular (Capas, Servicios y Componentes ................................................................ 14

3.1.1 Módulos............................................................................................................... 16

3.1.2 Componentes ........................................................................................................... 17

3.1.3 Plantillas, directivas y enlace de datos................................................................... 17

3.1.4 Servicios e inyección de dependencia ..................................................................... 18

3.1.5 Enrutamiento........................................................................................................... 19

3.2 Modelo Vista Vista Modelo (MVVM) ............................................................................. 20

3.2.1 Modelo .................................................................................................................... 22

3.2.2 Vista ........................................................................................................................ 23

3.2.3 Vista Modelo ........................................................................................................... 24

3.3 Flux ................................................................................................................................... 25


5

3.3.1 Vista ........................................................................................................................ 27

3.3.2 Almacén................................................................................................................... 27

3.3.3 Acciones .................................................................................................................. 27

3.3.4 Despachador ........................................................................................................... 28

3.3.5 Implementaciones de Flux ...................................................................................... 29

3.4 Redux ................................................................................................................................ 29

3.4.1 Arquitectura Redux y sus Principales Características ........................................... 32

3.4.2 Principios de Redux ................................................................................................ 37

3.5 NgRx ................................................................................................................................. 40

3.6 Caso Práctico .................................................................................................................... 42

3.6.1 Instalación............................................................................................................... 42

3.6.2 Creación del Modelo de Datos ............................................................................... 42

3.6.3 Creación de las Acciones ........................................................................................ 42

3.6.4 Creación de los Reductores .................................................................................... 43

3.6.5 Creación del App State ........................................................................................... 44

3.6.6 Importar el Almacén Ngrx ...................................................................................... 44

3.6.7 Creación del Componente Customer ...................................................................... 45

3.6.8 Creación del Componente Customer Details ......................................................... 46


6

3.6.9 Creación del Componente Customer List ............................................................... 47

3.6.10 Importación de los Componentes al App Component............................................. 48

3.6.11 Vista de Interfaz de Usuario ................................................................................... 49

Conclusiones ................................................................................................................................. 50

Bibliografía ................................................................................................................................... 52
7

TABLA DE CUADROS, GRAFICOS Y FIGURAS

Figura 1: Descripción de la arquitectura MVVM. ........................................................................ 22

Figura 2: Arquitectura del Patrón Flux. ........................................................................................ 26

Figura 3: Comparativa de Componentes con y sin Redux. ........................................................... 32

Figura 4: Patrón Redux y sus componentes. ................................................................................. 33

Figura 5: Ejemplo de Objeto de Acción. ...................................................................................... 34

Figura 6: Ejemplo de una función que crea un objeto de Acción. ................................................ 34

Figura 7: Implementación simple de un Reductor. ....................................................................... 36

Figura 8: Visualización del código de un despachador. ............................................................... 38

Figura 9: Declaración de cambios con funciones puras................................................................ 39

Figura 10: Interfaz de Usuario Customers usando Ngrx y Angular. ............................................ 49


8

RESUMEN

Los datos de las aplicaciones web fluyen en una sola dirección, permitiendo tener mayor control

y haciendo fácil la corrección y detección de errores; sin embargo, uno de los principales

problemas, es el flujo de datos bidireccional, donde un cambio en la interfaz dispara múltiples

eventos que afectan a múltiples vistas generando efectos en cascada, haciendo muy difícil

depurar y resolver un problema.

El resultado de la presente monografía ayudará a la implementación y el uso del patrón Redux de

arquitectura Flux para gestionar el flujo de datos en la capa de presentación de aplicaciones

empresariales que tienden a crecer exponencialmente.

Una aplicación a gran escala, tiende a que la gestión del estado de la aplicación pueda

convertirse en un problema a medida que esta crece. El estado de la aplicación incluye respuestas

del servidor, datos en caché y datos que aún no se han conservado en el servidor.

Redux predice las mutaciones de estado, sin perder las ventajas de rendimiento Asíncrono, lo

hace imponiendo ciertas restricciones sobre cómo pueden ocurrir las actualizaciones, estas

restricciones hacen aplicaciones predecibles y fáciles de probar.

Las principales características de arquitectura utilizando el patrón Redux son:


9

● Acciones: son objetos planos de Java, con al menos una propiedad que indica el tipo de

acción.

● Creador de acciones: son funciones que devuelven una acción

● Reductores: es una función que recibe el estado actual y devuelve un nuevo estado.

● Almacenes: reciben acciones para pasarlas por los middlewares, para luego pasarlas por

reductores que calculan el nuevo estado y lo guardan.

Igualmente, Redux se basa en tres principios fundamentales: única fuente de verdad, el estado es

solo de lectura y los cambios se hacen en funciones puras.

Aplicando Redux para el desarrollo de una aplicación, permitirá que la misma pueda ser

escalable.
10

INTRODUCCIÓN

En un principio la web estaba compuesta por páginas estáticas y documentos; entre otros, que el

usuario utilizaba para consulta y descarga, sin embargo, conforme al mayor uso del internet el

uso de la web tuvo que ir transformándose a las necesidades de un número creciente de usuarios;

de esta forma el siguiente paso fue la creación de páginas dinámicas que pudieran mostrar

información dinámica, es decir generando la misma a partir de los datos de petición.

En el año 1998, Netscape Navigator anuncia la nueva tecnología JavaScript, la cual permite que

el contenido de una página web cambie de forma dinámica, si bien se aceptaban scripts en

Netscape desde 1997, JavaScript vino a revolucionar el uso de las aplicaciones web1.

A finales de la década de los 90, las aplicaciones web fueron creciendo exponencialmente, sobre

todo para usos empresariales, por lo que las aplicaciones web de hoy en día son mucho más

grandes, lo cual implica un incremento de la complejidad. Esa complejidad hace que tareas como

la comunicación entre distintos componentes de la interfaz y la gestión del estado sigan un flujo

de datos complejo que por el tamaño se hace muy difícil de manejar.

En general los datos de una aplicación web fluyen en una sola dirección, lo cual permite tener

más control de la aplicación, haciendo fácil detectar, corregir errores y lograr llevar la aplicación

a un estado anterior o siguiente sin muchas complicaciones.

1
Ver https://www.sutori.com/story/historia-de-las-aplicaciones-web .
11

Sin embargo, uno de los principales problemas, es el flujo de datos bidireccional, donde un

cambio en la interfaz dispara múltiples eventos que afectan a múltiples vistas generando efectos

en cascada, haciendo muy difícil depurar y resolver un problema.

Por ejemplo, el problema se presenta cuando una aplicación web se vuelve muy grande y la

complejidad del mismo puede volverse exponencial cada vez que se intenta adicionar una nueva

característica, haciendo que el código sea “frágil e impredecible”.

Una solución a este problema es el uso del patrón Redux, que ayuda a la gestión del estado de la

aplicación y estado de cada uno de los componentes. Al hacer uso de este concepto, se deja el

control del estado en un único objeto llamado almacén, que es a su vez el único capaz de

modificarlo.
12

1. Generalidades

1.1 Antecedentes Generales

Flux es una arquitectura diseñada por Facebook junto con una librería para vistas. Se enfoca en

crear flujos de datos explícitos y entendibles, lo cual hace más sencillo seguir los cambios en el

estado de la aplicación y por ende los errores más fáciles de encontrar y corregir.

Para comprender mejor la arquitectura Flux, se compara con MVC o Modelo-Vista-Controlador,

uno de los patrones más utilizados en el desarrollo de aplicaciones. En MVC el controlador es

responsable de coordinar los cambios en uno o más modelos, lo hace mediante llamadas a

métodos en los modelos. Cuando los modelos cambian, se notifican las vistas, las cuales a su vez

leen los nuevos datos del modelo y se actualizan de acuerdo a esos cambios para que el usuario

pueda ver los nuevos datos o estado.

Conforme la aplicación crece y se agregan más controladores, modelos y vistas las dependencias

se vuelven más complejas.

Cuando el usuario interactúa con la interfaz gráfica, múltiples ramas de código son ejecutadas y

encontrar errores o correr pruebas sobre el estado de la aplicación, se vuelve una tarea muy

difícil, en la que básicamente se tiene que tratar de adivinar en cuál de todos los puntos se

encuentra el error. En los peores casos, una interacción causa actualizaciones adicionales en

cascada haciendo la tarea aún más dura.


13

Flux evita este diseño en favor de un flujo de datos en una sola dirección. Todas las interacciones

dentro de una vista llaman un “creador de acciones” que a su vez llama al “despachador” de tipo

Singleton, que es responsable de emitir un evento de tipo “acción” al cual los “almacenes” se

pueden subscribir. Estos “almacenes” responden a la acción y se auto actualizan.

Flux evita este diseño en favor de un flujo de datos en una sola dirección. Todas las

interacciones dentro de una vista llaman un “creador de acciones” que a su vez llama el

“despachador” de tipo Singleton que es responsable de emitir un evento de tipo “acción” al cual

los “almacenes” se pueden subscribir. Estos “almacenes” responden a la acción y se auto

actualizan.

1.2 Antecedentes Específicos

Redux, es un patrón de arquitectura de datos que permite manipular el estado de la aplicación de

una forma predecible. Está creado para disminuir el número de relaciones entre los componentes

de la aplicación y conservar un flujo de datos asequible.

Su creación comenzó en la Comunidad de React para mejorar Flux, paso a convertirse en un

patrón transversal que se adapta a cualquier tipo de librería o framework del lado del cliente.

Con tan solo 2 KB, Redux es una librería muy liviana y se puede ejecutar a la perfección por el

motor de JavaScript. Para tener una idea de su repercusión, empresas como Netflix lo han

adoptado para sus proyectos.


14

2. Metodología

A continuación, se describen los métodos de investigación a ser utilizados:

● Método Bibliográfico, debido a que se realizara la lectura, compilación de libros,

documentación oficial relacionados al tema de estudio.

● Método Analítico, debido a que se procederá a revisar y analizar ordenadamente

documentos relacionados al tema de estudio, para la redacción del presente trabajo.

● Método Empírico o de Campo, debido a que se utilizaran ejemplos de aplicación o

experiencias observadas.

3. Subtemas

Las aplicaciones web mejoran su mantenibilidad y organización de arquitectura cuando se aplica

la arquitectura de separación de capas y se mejora el flujo de datos de la aplicación mediante el

patrón de arquitectura Flux con su librería Redux.

3.1 Angular (Capas, Servicios y Componentes

Los módulos permiten organizar funcionalidad de los subsistemas, dentro de los cuales existen

componentes cuyo fin es la reutilización y separación de intereses que modelan componentes de

la interfaz, los cuales tienen plantillas asociadas que ayudan a separar la lógica del negocio de

vista de la presentación del DOM, estos componentes interactúan con lógica del negocio

mediante servicios que se encargan de realizar dicha interacción, son las principales

características de Angular.
15

Angular es una plataforma y un Framework para crear aplicaciones cliente en HTML y

TypeScript. Angular está escrito en TypeScript. Implementa funcionalidad central y opcional

como un conjunto de bibliotecas de TypeScript que importa a sus aplicaciones. (Angular, 2018)

Los bloques de construcción básicos de una aplicación angular son NgModules, que

proporcionan un contexto de compilación para los componentes. NgModules recopila código

relacionado en conjuntos funcionales; una aplicación angular está definida por un conjunto de

NgModules. Una aplicación siempre tiene al menos un módulo raíz que habilita el arranque, y

generalmente tiene muchos más módulos de características.

Los componentes definen vistas, que son conjuntos de elementos de pantalla que Angular puede

elegir y modificar según la lógica y los datos del sistema.

Los componentes usan servicios que proporcionan una funcionalidad específica, que no está

directamente relacionada con las vistas. Los proveedores de servicios pueden inyectarse en

componentes como dependencias, haciendo que su código sea modular, reutilizable y eficiente.

Tanto los componentes, como los servicios son simplemente clases, con decoradores que marcan

su tipo y proporcionan metadatos que le dicen a Angular cómo usarlos.


16

Los metadatos de una clase de componente lo asocian con una plantilla que define una vista. Una

plantilla combina HTML común con directivas angulares y marcado de enlace que permiten a

Angular modificar el HTML antes de representarlo para su visualización.

Los metadatos de una clase de servicio proporcionan la información que Angular necesita para

ponerla a disposición de los componentes a través de la inyección de dependencia (DI).

Los componentes de una aplicación generalmente definen muchas vistas, organizadas

jerárquicamente; Angular proporciona el servicio enrutador para ayudar a definir rutas de

navegación entre vistas a su vez el enrutador proporciona sofisticadas capacidades de navegación

en el navegador.

3.1.1 Módulos

Los NgModules difieren y complementan los módulos de JavaScript (ES2015). Un NgModule

declara un contexto de compilación para un conjunto de componentes que está dedicado a un

dominio de aplicación, un flujo de trabajo o un conjunto de capacidades estrechamente

relacionadas. Un NgModule puede asociar sus componentes con código relacionado, como

servicios, para formar unidades funcionales. (Angular, 2018)

Cada aplicación angular tiene un módulo raíz, denominado de manera convencional AppModule,

que proporciona el mecanismo de arranque que inicia la aplicación. Una aplicación generalmente

contiene muchos módulos funcionales.


17

Al igual que los módulos de JavaScript, NgModules puede importar funcionalidades de otros

NgModules y permitir que otros NgModules exporten y utilicen su propia funcionalidad.

Organizar el código en distintos módulos funcionales ayuda a gestionar el desarrollo de

aplicaciones complejas y al diseño para su reutilización. Además, esta técnica permite

aprovechar la carga diferida, es decir, cargar módulos bajo demanda, para minimizar la cantidad

de código que se debe cargar al inicio.

3.1.2 Componentes

Cada aplicación angular tiene al menos un componente, el componente raíz que conecta una

jerarquía de componentes con el modelo de objetos del documento de página (DOM) (Angular,

2018).

Cada componente define una clase que contiene datos y lógica de la aplicación, y está asociada a

una plantilla HTML que define una vista que se mostrará en un entorno objetivo.

El decorador @Component () identifica la clase inmediatamente debajo de él como un

componente y proporciona la plantilla y los metadatos específicos de los componentes

relacionados.

3.1.3 Plantillas, directivas y enlace de datos

Una plantilla combina HTML con marcado angular que puede modificar elementos HTML antes

de que se muestren, las directivas de plantilla proporcionan lógica de programa, y el marcado


18

vinculante conecta los datos de su aplicación y el DOM. (Angular, 2018) Hay dos tipos de enlace

de datos:

● El enlace de eventos, que permite que su aplicación responda a la entrada del usuario en

el entorno objetivo, actualizando los datos de su aplicación.

● El enlace de propiedad, que permite interpolar valores que se computan a partir de los

datos de su aplicación en el HTML.

Antes de que se muestre una vista, Angular evalúa las directivas y resuelve la sintaxis de enlace

en la plantilla para modificar los elementos HTML y el DOM, según los datos y la lógica de su

programa. Angular admite el enlace de datos bidireccional, lo que significa que los cambios en el

DOM, como las opciones del usuario, también se reflejan en los datos de su programa.

Sus plantillas pueden usar tuberías para mejorar la experiencia del usuario al transformar los

valores para su visualización. Por ejemplo, el uso de tuberías para mostrar fechas y valores de

moneda que sean apropiados para la configuración regional de un usuario. Angular proporciona

tuberías predefinidas para transformaciones comunes, y también puede definir sus propias

tuberías.

3.1.4 Servicios e inyección de dependencia

Para los datos o la lógica que no está asociada a una vista específica y que se desea compartir

entre los componentes, crea una clase de servicio. Una definición de clase de servicio va

precedida inmediatamente por el decorador @Injectable ().


19

El decorador proporciona los metadatos que permiten que su servicio se inyecte en los

componentes del cliente como una dependencia.

La inyección de dependencia (DI) le permite mantener sus clases de componentes delegadas y

eficientes. No obtienen datos del servidor, validan la entrada del usuario o inician sesión

directamente en la consola; delegan tales tareas a los servicios. (Angular, 2018)

3.1.5 Enrutamiento

El enrutamiento en Angular (Router NgModule) proporciona un servicio que le permite definir

una ruta de navegación entre los diferentes estados de aplicación y ver jerarquías en su

aplicación. (Angular, 2018).

Se basa en las convenciones de navegación del navegador:

● Ingresar una URL en la barra de direcciones y el navegador navega a la página

correspondiente.

● Hacer clic en los enlaces en la página y el navegador navega a una nueva página.

● Hacer clic en los botones para retroceder y avanzar del navegador y el navegador navega

hacia atrás y hacia adelante por el historial de las páginas que se visitó.

El enrutador mapea rutas de tipo URL a vistas en lugar de páginas, cuando un usuario realiza una

acción, como hacer clic en un enlace, que cargaría una página nueva en el navegador, el

enrutador intercepta el comportamiento del navegador y muestra u oculta las jerarquías de vista.

Si el enrutador determina que el estado actual de la aplicación requiere una funcionalidad

particular, y que el módulo que lo define no se ha cargado, el enrutador puede cargar el módulo a

demanda.
20

El enrutador interpreta una URL de enlace según las reglas de navegación de la vista de su

aplicación y el estado de los datos. Se puede navegar a nuevas vistas cuando el usuario hace clic

en un botón o selecciona desde un cuadro desplegable, o en respuesta a algún otro estímulo de

cualquier fuente. El enrutador registra la actividad en el historial del navegador, por lo que los

botones para avanzar y retroceder también funcionan.

Para definir las reglas de navegación, se procede a asociar las rutas de navegación con sus

componentes. Una ruta utiliza una sintaxis similar a una URL que integra los datos de su

programa, de forma muy parecida a como la sintaxis de la plantilla integra sus vistas con los

datos de su programa. A continuación, puede aplicar la lógica del programa para elegir qué vistas

mostrar u ocultar, en respuesta a la entrada del usuario y sus propias reglas de acceso.

3.2 Modelo Vista Vista Modelo (MVVM)

Las mejoras que aporta el patrón MVVM se basan en la separación de las interfaces de usuario

de la lógica de negocio y sus principales componentes son: modelo vista vista modelo.

Modelo Vista Vista Modelo MVVM, es un patrón arquitectónico basado en MVC y MVP, que

intenta separar más claramente el desarrollo de las interfaces de usuario (UI) de la lógica de

negocios y el comportamiento en una aplicación., como se puede apreciar en la Figura 1. Con

este fin, muchas implementaciones de este patrón hacen uso de enlaces de datos declarativos

para permitir una separación del trabajo en Vistas desde otras capas. Esto facilita la interfaz de

usuario y el trabajo de desarrollo que ocurre casi simultáneamente dentro de la misma base de
21

código. Los desarrolladores de UI escriben enlaces a la Vista Modelo dentro de su marcado de

documento (HTML), donde los desarrolladores que trabajan en la lógica de la aplicación

mantienen el Modelo y el Modelo de Vista. (Microsoft, 2018)

MVVM fue originalmente definido por Microsoft para su uso con Windows Presentation

Foundation (WPF) y Silverlight, habiendo sido anunciado oficialmente en 2005 por John

Grossman en una publicación de blog sobre Avalon (el nombre clave de WPF). También

encontró cierta popularidad en la comunidad de Adobe Flex como alternativa al simple uso de

MVC. Antes de que Microsoft adoptara el nombre de MVVM, sin embargo, hubo un

movimiento en la comunidad para pasar de MVP a MVPM: Modelo Vista Presentación Modelo.

(Microsoft, 2018)

En los últimos años, MVVM se ha implementado en JavaScript en forma de marcos estructurales

como KnockoutJS, Kendo MVVM y Knockback.js, con una respuesta positiva general de la

comunidad.
22

Fuente: (Montemagno, 2018)

Figura 1: Descripción de la arquitectura MVVM.

A continuación, se describen los componentes de MVVM:

3.2.1 Modelo

Al igual que con otros miembros de la familia MV *, el Modelo en MVVM representa

información o datos específicos del dominio con los que nuestra aplicación estará trabajando. Un

ejemplo típico de datos específicos del dominio puede ser una cuenta de usuario (por ejemplo,

nombre, avatar, correo electrónico) o una pista de música (por ejemplo, título, año, álbum).

Los modelos contienen información, pero generalmente no manejan el comportamiento. No

formatean la información ni influyen en cómo aparecen los datos en el navegador, ya que no es


23

su responsabilidad. En cambio, el formato de los datos es manejado por la Vista, mientras que el

comportamiento de la lógica de negocio debe ser encapsulada en otra capa que interactúa con el

Modelo: Vista Modelo. La única excepción a esta regla tiende a ser la validación y se considera

aceptable que los Modelos validen los datos que se utilizan para definir o actualizar modelos

existentes. (Microsoft, 2018)

3.2.2 Vista

Al igual que con MVC, la Vista es la única parte de la aplicación con la que los usuarios

realmente interactúan. Son una interfaz de usuario interactiva que representa el estado de un

Vista Modelo. En este sentido, la vista se considera activa en lugar de pasiva, pero esto también

es cierto para las vistas en MVC y MVP.

En MVC, MVP y MVVM una vista también puede ser pasiva. Una vista pasiva solo muestra una

pantalla y no acepta ninguna entrada del usuario, dicha vista también puede no tener un

conocimiento real de los modelos en nuestra aplicación y podría ser manipulada por un

presentador. La vista activa de MVVM contiene enlaces de datos, eventos y comportamientos

que requieren una comprensión del Vista Modelo. Aunque estos comportamientos se pueden

asignar a las propiedades, la Vista sigue siendo responsable de manejar los eventos desde el

Vista Modelo. (Garofalo, 2011, pp. 151,152).

Es importante recordar que la Vista no es responsable por el manejo del estado; lo mantiene

sincronizado con la Vista Modelo.


24

3.2.3 Vista Modelo

Vista Modelo, puede considerarse un Controlador especializado que actúa como un convertidor

de datos. Cambia la información del Modelo a información de Vista, pasando comandos de la

Vista al Modelo. Por ejemplo, imaginemos que tenemos un modelo que contiene un atributo de

fecha en formato Unix (por ejemplo, 1333832407), en lugar de que nuestros modelos conozcan

la fecha de visualización del usuario (p. Ej., 04/07/2012 a las 5:00 p.m.), donde sería necesario

convertir el atributo a su formato de visualización, nuestro modelo simplemente contiene el

formato sin formato de los datos, nuestra vista contiene la fecha formateada y nuestro modelo de

vista actúa como intermediario entre los dos.

En este sentido, Vista Modelo podría considerarse más un Modelo que una Vista, pero maneja la

mayor parte de la lógica de visualización de la Vista. Vista Modelo también puede exponer

métodos para ayudar a mantener el estado de la Vista, actualizar el modelo basado en la acción

en una Vista y desencadenar eventos en la Vista.

En resumen, Vista Modelo se encuentra detrás de nuestra capa de interfaz de usuario, expone los

datos que necesita una Vista (de un Modelo) y se puede ver como la fuente a la que van nuestras

Vistas para los datos y las acciones.

Nuestras Vistas manejan sus propios eventos de interfaz de usuario, asignándolos a la Vista

Modelo según sea necesario, los modelos y atributos en la Vista Modelo se sincronizan y

actualizan a través de enlace de datos bidireccional. Vista Modelo no solo expone los atributos
25

del Modelo, sino también el acceso a otros métodos y funciones, como la validación. Los

disparadores (desencadenadores de datos) también nos permiten reaccionar aún más a los

cambios en el estado de nuestros atributos de modelo; si bien puede parecer que Vista Modelo es

completamente responsable del Modelo en MVVM, hay algunas sutilezas con esta relación que

vale la pena mencionar. Vista Modelo puede exponer los atributos de un Modelo o Modelos a

efectos de enlace de datos y también puede contener interfaces para recuperar y manipular

propiedades expuestas en la vista. (Garofalo, 2011, pp. 161,162).

3.3 Flux

Flux, es la arquitectura de aplicaciones que se usa para construir aplicaciones web del lado del

cliente. Utilizando un flujo de datos unidireccional. Los datos de una aplicación con la

arquitectura flux, fluyen en una sola dirección, lo cual nos permite tener más control de nuestra

aplicación haciendo fácil detectar y corregir errores, el concepto fue creado para mejorar los

puntos débiles y la dificultad de controlar los cambios en la capa de presentación del patrón

MVC (o MVVM).

El patrón MVC para la capa de presentación, empezó a ser necesario cuando las aplicaciones

web empezaron a hacerse más grandes y con librerías como jQuery o el propio Vanilla

JavaScript se hacían difíciles de manejar. Por eso nacieron frameworks como Backbone, Ember

y/o Angular. (Dinkevich, 2017, pp. 6-8)

Todos ellos siguen una arquitectura Modelo-Vista-Controlador, o algo parecido (MVVM, MV*).

Haciendo más sencillo el manejo de datos en aplicaciones web con cierto grado de complejidad.
26

Flux nació desde Facebook por un problema que se les presentaba al tener una comunicación

bidireccional entre los modelos y los controladores, haciéndoles muy difícil poder depurar y

rastrear errores.

Flux propone una arquitectura en la que el flujo de datos es unidireccional, descrito en la Figura

2. Los datos viajan desde la vista por medio de acciones y llegan a un Store desde el cual se

actualizará la vista de nuevo.

Fuente: (Facebook, 2018)

Figura 2: Arquitectura del Patrón Flux.

Teniendo un único camino, y un sitio donde se almacena el estado de la aplicación, es más

sencillo depurar errores y saber que está pasando en cada momento.

Flux es un patrón de diseño o forma de "arquitecturar" una aplicación web, en concreto la forma

en la que se manejan los datos o estado de la aplicación (datos de un usuario, datos recogidos a
27

partir de un API REST o WebService, etc.), no se trata de una librería ni framework. (Bugl,

2017, p. 57).

Al igual que un patrón MVC está formado por un Modelo, una Vista y un Controlador, en Flux

tenemos distintos actores:

3.3.1 Vista

La vista serían los componentes web, ya sean construidos nativamente, con Polymer, Angular,

React.

3.3.2 Almacén

Almacén, sería lo más parecido al modelo de la aplicación. Guarda los datos/estado de la

aplicación y en Flux puede haber varias.

No hay métodos en el almacén que permitan modificar los datos en ella, eso se hace a través de

despachadores y acciones.

3.3.3 Acciones

Una acción es simplemente un objeto JavaScript que indica una intención de realizar algo y que

lleva datos asociados si es necesario. (Bugl, 2017, p. 74)

Por ejemplo, si tenemos una aplicación tipo carrito de la compra, y añadimos un ítem al carrito,

la acción que representaría esto sería:

{
28

type: 'ADD_ITEM',

item: item

3.3.4 Despachador

Las acciones como la anterior son enviadas a un despachador que se encarga de dispararla o

propagarla hasta el almacén. La vista es la que se encarga de enviar las acciones al despachador.

Un despachador no es más que un mediador entre el almacén o almacenes y las acciones. Sirve

para desacoplar la Store de la vista, ya que así no es necesario conocer que Store maneja una

acción concreta. (Bugl, 2017, pp. 102,103)

En Resumen, el patrón Flux sigue el siguiente recorrido:

● La vista, mediante un evento envía una acción con la intención de realizar un cambio en

el estado

● La acción contiene el tipo y los datos (si los hubiere) y es enviada al despachador.

● El despachador propaga la acción al Store y se procesa en orden de llegada.

● El almacén recibe la acción y dependiendo del tipo recibido, actualiza el estado y notifica

a las vistas de ese cambio.

● La vista recibe la notificación y se actualiza con los cambios.

● Todo en un único sentido.


29

3.3.5 Implementaciones de Flux

Existen numerosas implementaciones de este patrón de desarrollo en JavaScript. Muchas de ellas

ya no están actualizadas y poco a poco una de ellas se fue posicionando como referencia.

Se cita las siguientes librerías:

● Reflux

● McFly

● Marty.js

● Delorean

● Fluxxor

● Lux.js

● Fluxible

● Omniscent

● Redux

3.4 Redux
Redux, es una librería que implementa el patrón de diseño Flux, que se encarga de emitir

actualizaciones de estado en respuesta a acciones. En lugar de modificar directamente el estado,

la modificación se maneja a través de objetos sencillos llamados acciones. Luego se escribe una

función especial llamada reductor para decidir cómo cada acción transforma el estado de toda la

aplicación.
30

Una aplicación a gran escala, tiende a que la gestión del estado de la aplicación pueda

convertirse en un problema a medida que esta crece. El estado de la aplicación incluye respuestas

del servidor, datos en caché y datos que aún no se han conservado en el servidor. (Redux, 2018)

Además, el estado de la interfaz de usuario (UI) aumenta constantemente en complejidad, por

ejemplo, hoy en día; el enrutamiento a menudo se implementa en el cliente para no actualizar el

navegador y volver a cargar toda la aplicación.

El enrutamiento del lado del cliente es bueno para el rendimiento, pero significa que el cliente

debe tratar con más estados (en comparación con el uso del enrutamiento del lado del servidor).

Los conflictos e inconsistencias en estos diversos tipos de estado pueden ser difícil de tratar.

Gestionar todos estos estados se hace complicado y, si no se gestiona correctamente, el estado de

la aplicación puede crecer rápidamente fuera de control.

Como si todo esto no fuera lo suficientemente malo, nuevos requisitos, como actualizaciones

optimistas y la representación del lado del servidor, se vuelven necesarios, para poder

mantenerse al día con el aumento las demandas de rendimiento.

Este estado es difícil de tratar, porque se mezcla dos conceptos que pueden ser muy

impredecibles cuando se juntan:

● Asincronicidad, significa que los cambios pueden suceder en cualquier momento, de

forma asincrónica. Por ejemplo, un usuario presiona un botón que causa una solicitud del
31

servidor y puede no saber cuándo responde el servidor y, por motivos de rendimiento, no

queremos esperar la respuesta. Aquí es donde entra en juego la asincronía; actuamos en

una respuesta cada vez que ocurre, pero es impredecible cuando esto sucederá. (Bugl,

2017, p. 40)

● Mutación, significa cualquier cambio en el estado de la aplicación, como almacenar el

resultado de la respuesta del servidor en nuestro estado de aplicación o navegando a una

nueva página con el cliente, el enrutamiento, cambiaría el valor de la ruta actual, mutando

la aplicación estado. (Bugl, 2017, p. 40)

Redux intenta predecir las mutaciones de estado, sin perder las ventajas de rendimiento de

Asincronicidad, lo hace imponiendo ciertas restricciones sobre cómo pueden ocurrir las

actualizaciones, estas restricciones hacen aplicaciones predecibles y fáciles de probar. (Redux,

2018).

Una vez que un componente necesita compartir el estado con otro componente, con el que no

tiene una relación padre-hijo, las cosas comienzan a complicarse. En la Figura 3, se puede

visualizar ese problema. Una vez que un componente inicia un cambio de estado, este cambio

debe propagarse a todos los demás componentes que dependen de los datos modificados.
32

Fuente: (Redux, 2018)

Figura 3: Comparativa de Componentes con y sin Redux.

3.4.1 Arquitectura Redux y sus Principales Características

El siguiente grafico muestra los principales elementos de la arquitectura Redux, que ayudan a la

vez a comprender sus principales características.


33

Fuente: (Share, 2018)

Figura 4: Patrón Redux y sus componentes.

3.4.1.1Acciones (Actions)

Las Acciones son POJOs (Plain Old JavaScript Objects) con al menos una propiedad que indica

el tipo de acción y de ser necesario, otras propiedades indicando cualquier otro dato necesario

para efectuar la acción. Ver Figura 5. Normalmente se usa el formato definido en el Flux

Standard Action (FSA). (Marc Garreau, 2018, p. 8)


34

Fuente: (Dinkevich, 2017, p. 10)

Figura 5: Ejemplo de Objeto de Acción.

3.4.1.2Creador de Acciones (Action Creators)

Estas son simplemente funciones que pueden o no recibir parámetros y devuelven una acción (un

POJO), es muy buena idea, para evitar problemas de consistencia, programar una función por

cada tipo de acción y usarlas en vez de armar nuestros objetos a mano. Ver Figura 6. (Dinkevich,

2017, p. 504).

Fuente: (Dinkevich, 2017, p. 10)

Figura 6: Ejemplo de una función que crea un objeto de Acción.


35

Debido a que normalmente son funciones puras son fáciles de testear. Luego de ejecutar nuestra

función, para poder despachar la acción, es simplemente llamar a la función dispatch().

3.4.1.3Reductores (Reducers)

Un reductor es una función pura que recibe el estado actual y una acción y devuelve el nuevo

estado. Como se describe en la Figura 7. (Marc Garreau, 2018, p. 9)

Es muy importante que se mantengan como funciones puras. Algunas cosas que no se debería

hacer en un reductor son:

● Modificar sus argumentos directamente (lo correcto es crear una copia)

● Realizar acciones con efectos secundarios como llamadas a una API o cambiar rutas

● Ejecutar funciones no puras como Date.now() o Math.random().

Que se mantengan puros, quiere decir que pasándole los mismos parámetros debería siempre

devolver el mismo resultado.


36

Fuente: (Dinkevich, 2017, p. 11)

Figura 7: Implementación simple de un Reductor.

3.4.1.4Almacenes (Store)

A diferencia de muchas otras implementaciones de Flux, Redux tiene un único almacén que

contiene la aplicación información, pero sin lógica de usuario. El papel del almacén es recibir

acciones, pasarlas a través de todos los middlewares registrados, y luego usar reductores para

calcular un nuevo estado y guardarlo. Cuando recibe una acción que causa un cambio en el

estado, el almacén tienda notificará a todos los oyentes registrados, que se ha hecho un cambio al

estado. Esto permitirá varias partes del sistema, como la interfaz de usuario, para actualizarse de

acuerdo con el nuevo estado (Dinkevich, 2017, p. 99).

En resumen, las responsabilidades del Almacén se listan a continuación:

● Almacenar el estado global de la aplicación

● Dar acceso al estado mediante store.getState()


37

● Permitir que el estado se actualice mediante store.dispatch()

● Registrar listeners mediante store.subscribe(listener)

3.4.2 Principios de Redux

Redux puede ser descrito en tres principios fundamentales:

3.4.2.1Única Fuente de la Verdad

El estado de toda la aplicación está almacenado en un árbol guardado en un único almacén.

(Noring, 2018, p. 208)

Esto hace fácil crear aplicaciones universales, ya que el estado en el servidor puede ser

serializado y enviado al cliente sin ningún esfuerzo extra. Como un único árbol de estado

también hace más fácil depurar una aplicación; igualmente permite mantener el estado de la

aplicación en desarrollo, para un ciclo de desarrollo más veloz. Algunas funcionalidades que

históricamente fueron difíciles de implementar como, por ejemplo: Deshacer/Rehacer, se

vuelven triviales si todo tu estado se guarda en un solo árbol.

3.4.2.2 El Estado es Solo de Lectura

La única forma de modificar el estado es emitiendo una acción, un objeto describiendo que

ocurrió.
38

Esto te asegura que ni tu vista ni callbacks de red van a modificar el estado directamente, como

se muestra en la Figura 8. (Noring, 2018, p. 208).

En vez de eso, expresan un intento de modificar el estado, ya que todas las modificaciones están

centralizadas y suceden en un orden estricto, no hay que preocuparse por una carrera entre las

acciones. y como las acciones son objetos planos, pueden ser registrados, serializados y

almacenados para volver a ejecutarlos por cuestiones de depuración y pruebas.

Fuente: Elaboración Propia

Figura 8: Visualización del código de un despachador.

3.4.2.3 Los Cambios se Realizan con Funciones Puras

Para especificar como el árbol de estado es transformado por las acciones, se utilizan reductores

puros. (Noring, 2018, p. 210)

Los reductores son funciones puras que toman el estado anterior y una acción, y devuelven un

nuevo estado, como se puede apreciar en la Figura 9. Se debe devolver un nuevo objeto de

estado en vez de modificar el anterior. Inicialmente se puede empezar con un único reductor, y
39

mientras la aplicación crece, dividir en varios reductores pequeños que manejen partes

específicas del árbol de estado. Ya que los reductores son funciones puras, se puede controlar el

orden en que se ejecutan, pasar datos adicionales, o incluso hacer reductores reusables para

tareas comunes como paginación.

Fuente: Elaboración Propia

Figura 9: Declaración de cambios con funciones puras.


40

A diferencia de Flux, en Redux no existe el concepto de despachador. Esto es porque se basa en

funciones puras en vez de emisores de eventos, y las funciones puras son fáciles de componer y

no necesitan entidades adicionales para controlarlas.

Flux siempre fue descripto como (estado, acción) => estado. En ese sentido, Redux es una

verdadera arquitectura Flux, pero más simple gracias a las funciones puras.

Otra diferencia importante con Flux es que Redux asume que nunca se modifican los datos, se

puede usar objetos planos o arreglos para el estado y está perfecto, pero modificarlos dentro de

los reductores no es recomendable. Siempre se debería devolver un nuevo objeto, lo cual es

simple con la propuesta del operador spread, o con librerías como Inmutable.

Mientras que técnicamente es posible escribir reductores impuros que modifican los datos por

razones de rendimiento, no es aconsejable realizar dicha práctica. Además, características como

inmutabilidad no parecen causar problemas de rendimiento en aplicaciones reales, ya que, se

demostró que incluso se llega a perder la posibilidad de usar asignación en objetos, debido a que

sabes exactamente qué cambio gracias a los reductores puros. (Noring, 2018, p. 216)

3.5 NgRx

NgRx, es una colección de bibliotecas reactivas para angular, que contiene una implementación

de reducción y muchas otras bibliotecas útiles al ser una implementación de Redux hecha para

Angular, los conceptos como almacenes, creadores de acciones, acciones, selectores y reductores
41

son bien utilizados. Es una excelente manera de administrar el estado utilizando el patrón Redux

y mantener su aplicación escalable.

NgRx ayuda a crear aplicaciones más sólidas organizando mejor el estado de su aplicación, en

otras palabras, los datos de la aplicación. Sin una solución de administración del estado, tratar

con datos complejos en múltiples componentes puede ser bastante difícil de manejar y probar.

(Noring, 2018, pp. 238,239)

NgRx está compuesto de las siguientes partes:

● @ ngrx / store: este es el núcleo que contiene una forma para que podamos mantener el

estado y despachar acciones.

● @ngrx/effects, maneja los efectos secundarios como, por ejemplo, las solicitudes de

AJAX.

● @ ngrx / router-store: Asegura que podamos integrar NgRx con Angular routing.

● @ngrx/store-devtools: Instala una herramienta que nos da la oportunidad de depurar

NgRx, por ejemplo, dándonos un viaje en el tiempo depuración de la funcionalidad.

● @ngrx/entity: esta es una biblioteca que nos ayuda a administrar las colecciones de

registros.

● @ngrx/schematics: esta es una biblioteca de andamios que lo ayuda cuando usa NgRx.
42

3.6 Caso Práctico

El presente ejemplo muestra cómo definir la creación y eliminación de usuarios, haciendo uso

del Redux con Angular.

Pasos:

3.6.1 Instalación

Instalar ngrx/store con el siguiente con el siguiente comando: npm install @ngrx/store.

3.6.2 Creación del Modelo de Datos

export class Customer {


id: string;
name: string;
age: number;
active: boolean;

constructor(id?: string, name?: string, age?: number, active?: boolean) {


this.id = id;
this.name = name;
this.age = age;
this.active = active;
}
}

app/customers/models/customer.ts

3.6.3 Creación de las Acciones

app/actions/customer.actions.ts

import { Injectable } from '@angular/core';


import { Action } from '@ngrx/store';
import { Customer } from '../customers/models/customer';

export const CREATE_CUSTOMER = 'Customer_Create';


export const DELETE_CUSTOMER = 'Customer_Delete';

export class CreateCustomer implements Action {


43

readonly type = CREATE_CUSTOMER;

constructor(public payload: Customer) { }


}

export class DeleteCustomer implements Action {


readonly type = DELETE_CUSTOMER;

constructor(public id: string) { }


}

export type Actions = CreateCustomer | DeleteCustomer;

3.6.4 Creación de los Reductores

app/reducers/customer.reducer.ts

import { Customer } from '../customers/models/customer';


import { Actions, CREATE_CUSTOMER, DELETE_CUSTOMER } from '../actions/customer.actions';

const initialState: Customer = {


id: '1',
name: 'Andrien',
age: 27,
active: true
};

export function reducer(


state: Customer[] = [initialState],
action: Actions) {

switch (action.type) {
case CREATE_CUSTOMER:
return [...state, action.payload];

case DELETE_CUSTOMER:
return state.filter(({ id }) => id !== action.id);

default:
return state;
}
}
44

3.6.5 Creación del App State

app/app.state.ts

import { Customer } from './customers/models/customer';

export interface AppState {


readonly customer: Customer[];
}

3.6.6 Importar el Almacén Ngrx

app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';


import { NgModule } from '@angular/core';

import { StoreModule } from '@ngrx/store';


import { reducer } from './reducers/customer.reducer';

import { AppComponent } from './app.component';


import { CreateCustomerComponent } from './customers/create-customer/create-
customer.component';
import { CustomersListComponent } from './customers/customers-list/customers-list.component';
import { CustomerDetailsComponent } from './customers/customer-details/customer-
details.component';

@NgModule({
declarations: [
AppComponent,
CreateCustomerComponent,
CustomersListComponent,
CustomerDetailsComponent
],
imports: [
BrowserModule,
StoreModule.forRoot({
customer: reducer
})
],
providers: [],
bootstrap: [AppComponent]
})
45

export class AppModule { }

3.6.7 Creación del Componente Customer

customers/create-customer/create-customer.component.ts

import { Component, OnInit } from '@angular/core';


import { Store } from '@ngrx/store';
import { AppState } from '../../app.state';
import { CreateCustomer } from '../../actions/customer.actions';

@Component({
selector: 'app-create-customer',
templateUrl: './create-customer.component.html',
styleUrls: ['./create-customer.component.css']
})
export class CreateCustomerComponent implements OnInit {

constructor(private store: Store<AppState>) { }

ngOnInit() {
}

saveCustomer(id, name, age) {


this.store.dispatch(new CreateCustomer(
{
id: id,
name: name,
age: age,
active: false
}
));
}
}

customers/create-customer/create-customer.component.html

<div style="max-width:300px;">
<h3>Create Customers</h3>
<div class="form-group">
<input class="form-control" type="text" placeholder="id" #id>
</div>
<div class="form-group">
<input class="form-control" type="text" placeholder="name" #name>
46

</div>
<div class="form-group">
<input class="form-control" type="number" placeholder="age" #age>
</div>
<button class="btn btn-success" (click)="saveCustomer(id.value,name.value,age.value)">Save
Customer</button>
</div>

3.6.8 Creación del Componente Customer Details

customers/customer-details/customer-details.component.ts

import { Component, OnInit, Input } from '@angular/core';


import { Customer } from '../models/customer';
import { Store } from '@ngrx/store';
import { AppState } from '../../app.state';
import { DeleteCustomer } from '../../actions/customer.actions';

@Component({
selector: 'app-customer-details',
templateUrl: './customer-details.component.html',
styleUrls: ['./customer-details.component.css']
})
export class CustomerDetailsComponent implements OnInit {

@Input() customer: Customer;

constructor(private store: Store<AppState>) { }

ngOnInit() {
}

removeCustomer(id) {
this.store.dispatch(new DeleteCustomer(id));
}
}

customers/customer-details/customer-details.component.html

<div *ngIf="customer">
<div>
<label>Name: </label> {{customer.name}}
</div>
<div>
47

<label>Age: </label> {{customer.age}}


</div>
<span class="button is-small btn-danger" (click)='removeCustomer(customer.id)'>Delete</span>
<hr/>
</div>

3.6.9 Creación del Componente Customer List

customers/customers-list/customers-list.component.ts

import { Component, OnInit } from '@angular/core';


import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';

import { Customer } from '../models/customer';


import { AppState } from '../../app.state';

@Component({
selector: 'app-customers-list',
templateUrl: './customers-list.component.html',
styleUrls: ['./customers-list.component.css']
})
export class CustomersListComponent implements OnInit {

customers: Observable<Customer[]>;

constructor(private store: Store<AppState>) {


this.customers = store.select('customer');
}

ngOnInit() {
}

customers/customers-list/customers-list.component.html

<div *ngIf="customers">
<h3>Customers</h3>
<div *ngFor="let customer of customers | async">
<app-customer-details [customer]='customer'></app-customer-details>
</div>
48

</div>

3.6.10 Importación de los Componentes al App Component

app/app.component.ts

import { Component } from '@angular/core';


@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'grokonez';
description = 'NgRx Example';
}
app/app.component.html

<div class="container">
<div style="color: blue;">
<h1>{{title}}</h1>
<h3>{{description}}</h3>
</div>
<div class="row">
<div class="col-sm-4">
<app-create-customer></app-create-customer>
</div>
<div class="col-sm-4">
<app-customers-list></app-customers-list>
</div>
</div>
</div>
49

3.6.11 Vista de Interfaz de Usuario

Fuente: Elaboración Propia

Figura 10: Interfaz de Usuario Customers usando Ngrx y Angular.


50

CONCLUSIONES

A través de este trabajo de investigación se puede llegar a las siguientes conclusiones:

● Cuando se construyen aplicaciones web empresariales, que tienden a crecer, es

recomendable utilizar una arquitectura por capas con un patrón de arquitectura Flux, ya

que hace más fácil su mantenibilidad.

● Igualmente, usar el patrón Redux permite que la aplicación se comporte de forma

predecible – a pesar de un crecimiento exponencial de flujo de datos - haciendo que

minimice los errores de actualizaciones de estado.

● En cuanto al uso de los módulos de Angular en el desarrollo de aplicaciones web permite

una organización lógica del negocio que se pueden reutilizar y que se pueden comunicar

con diferentes servicios.

● Se pudo ver que el uso del patrón MVVM es bastante útil, ya que se basa en la separación

de las interfaces de usuario. Ayuda a la simplificación de las tareas de desarrollo y

mantenimiento del software escrito a través de la división de capas.

● Al usar la arquitectura Flux, los datos fluyen en una sola dirección permitiendo tener

mayor control de la aplicación y como se dijo anteriormente ayuda a la detección de

errores en la capa de presentación

● Utilizando funciones puras a través de Redux, se obtiene una sustancial mejora, en lugar

de modificar directamente el estado y utilizar las acciones, éstas transforman el estado de

toda la aplicación simultáneamente sin tener que cambiar el estado en cada una de las

vistas, simplificando así el flujo de la aplicación.


51

● El uso de la librería NgRx es útil, al ser una implementación de Redux hecha para

Angular, los conceptos como almacenes, creadores de acciones, acciones, selectores y

reductores son bien utilizados. Se logro la creación de aplicaciones más sólidas

organizando mejor el estado de las mismas.


52

BIBLIOGRAFÍA

Referencias

[1] Bugl, D. (2017). Learning Redux. Packt Publishing Ltd.

[2] Dinkevich, I. G. (2017). The Complete Redux Book. Lean Publishing.

[3] Garofalo, R. (2011). Building Enterprise Applications with Windows Presentation

Foundation and the Model View ViewModel . Microsoft Press.

[4] Marc Garreau, W. F. (2018). Redux in Action. Manning Publications Co.

[5] Noring, C. (2018). Architecting Angular Applications with Redux, RxJS, and NgRx. Packt

Publishing Ltd.

Sitios Web

[6] Angular. (2018, septiembre 29). Angular - Architecture Overview. Retrieved from

https://angular.io/guide/architecture

[7] Microsoft. (2018, Septiembre 29). Retrieved from https://docs.microsoft.com/en-

us/xamarin/xamarin-forms/enterprise-application-patterns/mvvm

[8] Redux. (2018, Septiembre 29). Redux Org. Retrieved from https://redux.js.org/

Você também pode gostar