Skip to content

undefinedschool/notes-redux

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 

Repository files navigation

Redux

Contenido


Intro

Redux es una librería (biblioteca) que funciona como State Container o State Manager (nos permite manejar el estado de los componentes) para nuestras aplicaciones, que funciona independientemente del framework o librería que utilicemos para manejar la vista[1].

Lo que hace básicamente Redux es proveernos de un gran objeto que contiene el estado de cada componente de nuestra aplicación (nos permite lidiar con el estado de toda la aplicación de una forma centralizada), al cual cada componente puede acceder.

Redux nos garantiza que este objeto es inmutable, es decir, no va a cambiar, ya que no modifica el state actual cuando se produce algún cambio, sino que retorna una nueva copia de este objeto con los cambios requeridos, haciendo que el state de nuestra aplicación sea más fácil de manejar, entender, debuggear y predecible[2][3].

👉 En este sentido, Redux se comporta como una función pura, según el paradigma funcional.

↑ Ir al inicio

Por qué usar Redux?

Recordemos que React ya nos provee una forma de manejar el state de cada componente, de forma local.

De hecho, la mayoría de las veces, probablemente no necesitemos una solución como Redux, ya que podemos estar agregando complejidad innecesaria.

Qué beneficio nos trae entonces utilizar Redux por sobre el local state?

Levantar el state en el árbol de componentes funciona en casos simples, pero en aplicaciones más complejas podemos terminar encontrándonos moviendo el state continuamente entre componentes, a través de las props, dificultando el mantenimiento y seguimiento del flujo de datos en nuestra aplicación.

Un mejor enfoque podría ser utilizar un store externo, global (un gran objeto que funcione algo así como una variable global, al que cualquier componente pueda acceder, con la diferencia de que en este caso, es inmutable) y esto es justamente lo que propone Redux.

Por lo tanto, sólo deberíamos usar Redux si manejar el state local de los componentes de React se vuelve lo suficientemente tedioso[4].

↑ Ir al inicio

Beneficios

Algunas de las ventajas que implica utilizar Redux incluyen

  • manejo centralizado del state.
  • flujo de datos unidireccional, transparente y predecible.
  • más fácil de debuggear.
  • más fácil de testear.
  • posibilidad de rehacer/deshacer cambios y movernos entre diferentes states a través del tiempo (time travelling).
  • state inmutable.
  • ecosistema bastante desarrollado y maduro (mucha documentación, extensiones, herramientas de desarrollo, etc).

↑ Ir al inicio

Conceptos

Inmutabilidad

El estado completo de la aplicación se encuentra representado por un gran objeto de JavaScript, conocido como state o state tree.

Decimos que el state es inmutable porque se trata de un objeto de sólo lectura: no se puede modificar directamente, sino sólo a través del dispatch de una acción.

↑ Ir al inicio

Actions

Una acción es un objeto JavaScript que describe un cambio y cuenta con la información mínima necesaria para representarlo (es minimal).

👉 El único requisito de este tipo de objetos es tener una propiedad type, cuyo valor suele ser un string.

Ejemplo sólo con type

{ type: 'CLICKED_SIDEBAR' };

Ejemplo con más propiedades

{
  type: 'SELECTED_USER', 
  userId: 232 
};

👉 En Redux, la única forma de actualizar el state es disparando una acción, la cual se pasa al reducer, que se encargará de generar un nuevo state.

↑ Ir al inicio

Utilizar Action types constantes

Un Action type puede definirse como un simple string, pero se recomienda utilizar constantes y modularizarlos, para de esta forma poder reutilizarlos y evitar errores de tipeo.

const ADD_ITEM = 'ADD_ITEM';
const action = { 
  type: ADD_ITEM, 
  title: 'Third item' 
};

Ejemplo importando Actions

import { ADD_ITEM, REMOVE_ITEM } from './actions';

↑ Ir al inicio

Action creators

Son funciones que crean acciones (por lo tanto siempre retornan un objeto).

function addItem(t) {
  return { 
    type: ADD_ITEM,     
    title: t  
  } 
};

Generalmente ejecutamos Action creators junto con el dispatch

dispatch(addItem('Water bottle'));

También pueden combinarse, definiendo un Action dispatcher

function dispatchAddItem(i) {
  dispatch(addItem(i));
}

dispatchAddItem('Water bottle');

👉 Estas funciones van a despachar acciones cada vez que querramos realizar cambios al state.

↑ Ir al inicio

Reducer

Cuando una acción se dispara, el state de la aplicación debe cambiar y son los reducers quienes se encargan de realizar esta tarea.

Un reducer es una función pura que recibe un state (el actual) y una acción y retorna un nuevo estado, basándose únicamente en estos parámetros. Por lo tanto, si no hay acción, retornamos el mismo estado.

Reducer

👉 En una función pura, el output depende únicamente del input y dado el mismo input, genera el mismo output, sin modificarlo ni depender de ningún otro factor. Además, no tiene side-effects, es decir, la función no modifica el entorno externo de ninguna forma (no muta los argumentos que recibe ni accede a variables por fuera de su scope). Esto hace que el comportamiento de la función sea predecible y por lo tanto, más fácil de razonar, debuggear y testear.

Un reducer retorna un objeto (state) nuevo que reemplaza al anterior, basándose únicamente en el state previo y las actions generadas.

⚠️ Al tratarse un reducer de una función pura, no debería

  • mutar (modificar) sus argumentos.
  • mutar el estado (debe crear uno nuevo).
  • generar algún tipo de side-effect.
  • invocar funciones que no sean puras, es decir, funciones cuyo output depende no sólo del input sino también de otros factores.

👉 Dado que el estado de una aplicación compleja puede ser, valga la redundancia, complejo, suele haber múltiples reducers, para diferentes tipos de acciones y un reducer principal (root) que los combina. Para hacer esto último, Redux nos provee la función combineReducers.

↑ Ir al inicio

Reducers y Actions

Los reducers no se llaman directamente, sino que primero debe dispararse una acción, que va a ser interceptada y procesada por un reducer.

👉 En Redux, las acciones son simplemente instrucciones que le dicen al Reducer cómo modificar el estado actual.

Estas acciones vienen en forma de objeto, siempre con una propiedad type (que por convención es un string en UPPER_SNAKE_CASE) y un payload opcional, para indicar nuevos datos que queremos agregar al state.

Por ejemplo, si queremos indicarle al reducer que agregue a 'Jimmy' a una lista de nombres del state, podemos hacer

{
  type: 'ADD_NAME',
  payload: 'Jimmy'
}

↑ Ir al inicio

Store

El Store es un objeto de JavaScript con las siguientes características:

  • contiene el state (entero) de la aplicación.
  • permite acceder (leer) el state a través del método getState().
  • permite actualizar el state a través del método dispatch().
  • permite suscribirse (o cancelar la suscripción) a cambios del state a través de un listener, con el método subscribe().
  • hay 1 solo store por aplicación.

👉 Notar que Store y state no son lo mismo: el state está conformado por los datos con los que opera nuestra aplicación, mientras que el Store contiene al state y nos provee de ciertos métodos (una API) para interactuar con este.

Ejemplo de Store:

import { createStore } from 'redux';

const store = createStore(firstReducer);

// we can now dispatch actions and get state from the store

const initialState = store.getState();

console.log(initialState); // this will be an array

store.dispatch(addName('Elie'));
store.dispatch(addName('Matt'));
store.dispatch(addName('Tim'));

console.log(store.getState()); // this object will have an array with three values

store.dispatch(removeName('Elie'));

console.log(store.getState()); // this array will have 2 values

Podemos inicializar el store con datos pre-existentes (por ejemplo que vienen del backend), pasándole un parámetro extra

const store = createStore(listManager, preexistingState);

↑ Ir al inicio

Accediendo al state

store.getState();

↑ Ir al inicio

Actualizando el state

store.dispatch(addItem('Something'));

↑ Ir al inicio

Escuchando cambios en el state

const unsubscribe = store.subscribe(() => const newState = store.getState());

También podemos cancelar la suscripción

unsubscribe();

↑ Ir al inicio

Flujo de datos unidireccional (one-way data flow)

Al igual que en React, en Redux el flujo de datos es siempre unidireccional.

Redux Flow

El flujo o lifecycle en Redux entonces es el siguiente:

  1. Invocamos el método dispatch() del Store, pasándole una acción que representa un cambio en el state: store.dispatch(action).
  2. El Store se encarga de pasarle la acción al reducer (e invocar a este último), generando así el nuevo estado.
  3. Puede haber un Reducer principal que combine el output de múltiples funciones reducers.
  4. El Store actualiza el state y le avisa a todos los listeners suscriptos.

↑ Ir al inicio

Redux vs React Local State

En React, el state

  • es local (por lo tanto, descentralizado), propio de cada componente.
  • si necesitamos compartir el state de un componente con otros, se pasa por props (siempre hacia child components).
  • es mutable.

En Redux, el state

  • es global (por lo tanto, centralizado) y está contenido en el Store.
  • si un componente necesita tener acceso a algún valor del state, puede simplemente suscribirse al Store para obtenerlo, sin tener que pasar props innecesariamente entre componentes.
  • es inmutable.

👉 Algo importante a tener en cuenta es que utilizar Redux no implica pasar a manejar todo el state con esta solución. Necesitamos determinar qué partes del state pasarán a estar almacenadas en el Store de Redux y qué partes continuarán en el state local de cada React Component.

↑ Ir al inicio

Redux Developer Tools

Tener un único store inmutable nos permite acceder a features como time traveling, hot module reloading y simplifica el debugging.

Como el Store está al tanto de las acciones que se disparan (por el dispatch), podemos ver los cambios en el state y revertirlos (aka time travelling)! No sólo eso, también podemos reproducir y repetir series de acciones que suceden en nuestra aplicación, a través de las Developer Tools para Redux.

👉 Descargar Redux Developer Tools para Chrome

👉 Descargar Redux Developer Tools para Firefox

Además de instalar la extensión, tenemos que agregar el siguiente código a nuestra aplicación

import { createStore, compose } from 'redux';
import rootReducer from './reducers';

const store = createStore(rootReducer, compose(
  typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : (f) => f
));

export default store;

Algunas de las acciones de time travelling que podemos realizar incluyen

  • commit: toma cualquier cambio que le hagamos al state y lo setea como el estado inicial. Podemos hacer esto tantas veces como querramos.
  • revert: retroceder a un estado anterior (un commit previo).
  • reset: deshacer todos los commits y volver al state original.

↑ Ir al inicio

React Redux

(WIP)

↑ Ir al inicio

Redux vs Context API

(WIP)

↑ Ir al inicio

Redux vs Hooks

(WIP)

↑ Ir al inicio

Redux Toolkit

(WIP)

👉 Ver Redux Toolkit.

↑ Ir al inicio

Testing

[notas aparte]

(WIP)

↑ Ir al inicio


1 Si bien se popularizó mucho su uso junto con React, Redux puede utilizarse con Angular, Vue, Svelte o cualquier otro framework o librería de frontend que maneje el concepto de state (incluso con HTML/JS vanilla!).

2 Si estamos usando React, es conveniente utilizar React Redux para simplificar la integración.

3 Es por esto último que se define como un contenedor predecible del estado de aplicaciones JavaScript.

4 Actualmente existe Context API como una solución más simple a este problema.

Releases

No releases published

Packages