Redux
June 24, 2019 · View on GitHub
Redux no hace rápido lo simple, si no mantenible lo complejo.
Principios de Redux
- Single Source Of Truth
- Cada pieza de información se almacena en un único lugar (STORE), independientemente de dónde sea creada, modificada o requerida.
- Read Only State
- La información será de sólo lectura y sólo podrá modificarse a través de conductos oficiales (ACTIONS).
- Changes By Pure Functions
- Para especificar como el árbol de estado es transformado por las acciones, se utilizan funciones puras* (REDUCERS).
- Los cambios tienen que poder ser replicados, cancelados y auditados.
Funciones puras:
- Retornan el mismo resultado para la misma entrada.
- No dependen del entorno ni de condiciones azarosas; sólo de sus argumentos.
- Sin efectos colaterales ni modificaciones del valor de sus parámetros u otras variables del entorno.
Elementos de Redux
-
Store
- Despacha acciones de mutado
- Mantiene el estado y controla su acceso en modo sólo lectura.
-
State
- Objeto en memoria que contiene la única copia válida de la información.
- Representa el valor o el estado del store en un momento determinado.
//reducers/user.store.ts
import { User } from "app/model/User";
export interface UserState {
items: User[],
newItem: User,
selectedItem: User,
lastId: number
}
- Actions
- Objetos con un tipo predefinido y un potencial argumento llamado payload.
- Representan todas las posibles mutaciones que pueden afectar al estado del store.
//reducers/user.actions.ts
import { User } from "app/model/User";
export enum UserActionTypes {
LoadUsers = '[USER]_Load',
AddUser = '[USER]_Add',
DeleteUser = '[USER]_Delete',
}
export interface UserAction {
readonly type: string;
readonly payload?: any;
}
export class LoadUsers implements UserAction {
readonly type = UserActionTypes:LoadUsers;
constructor(public readonly payload: User[]) {}
}
export class AddUser implements UserAction {
readonly type = UserActionTypes:AddUser;
constructor(public readonly payload: User) {}
}
export class DeleteUser implements UserAction {
readonly type = UserActionTypes:DeleteUser;
constructor(public readonly payload: User) {}
}
export type UserActions = LoadUsers | AddUser | DeleteUser;
- Reducers
- Son funciones puras*, las únicas que pueden mutar el estado.
- Reciben el estado actual (STATE) y una acción (ACTION).
- Clonan el estado, realizan los cambios oportunos y devuelven el estado mutado.
//reducers/user.reducer.ts
import { UserState } from "./user.store";
import { Userction } from "./user.actions";
export const initialUserState: UserState = {
items: [],
newItem: null,
selectedItem: null,
lastId: 20
};
export function reducer(state = initialUserState, action: UserAction): UserState {
let newstate = {...state};
switch (action.type) {
case '[USER]_Load':
newstate.items = action.payload;
break;
case '[USER]_Add':
newstate.items = newstate.items.slice(0); //Una forma de clonar arrays
newstate.items.push(action.payload);
newstate.lastId = newstate.lastId +1;
break;
case '[USER]_Delete':
let user = action.payload;
newstate.items = newstate.items.filter(function(el) { return el.id != user.id; });
break;
default:
return state;
}
return newstate;
}
- Service
- La pieza que une todo el puzzle.
- Almacena el state, pero solamente lo modificará a través del reducer.
- Ofrecerá a los componentes (o a otros servicios) un observable del estado.
- Ofrece un método dispatch, única forma de modificar el state.
- Solamente se modificará el state si se invoca el método dispatch con una de las actions oficiales.
import { Injectable } from '@angular/core';
import { Subject, Observable, BehaviorSubject } from 'rxjs';
import { UserState } from '../reducers/user.store';
import { initialUserState, reducer } from '../reducers/user.reducer';
import { UserAction } from '../reducers/user.actions';
@Injectable({
providedIn: 'root'
})
export class ReduxUserStateService {
private state: UserState = initialUserState;
private state$ = new BehaviorSubject<any>(this.state);
constructor() {}
public dispatch = (action: UserAction) => {
this.state = reducer(this.state, action);
this.state$.next(this.getSnapshot());
};
public getSnapshot = () => {
return { ...this.state };
};
public select$ = () => this.state$.asObservable();
}
Ejemplo resuelto en el módulo redux.