Skip to content

Latest commit

History

History
254 lines (182 loc) 路 6.77 KB

File metadata and controls

254 lines (182 loc) 路 6.77 KB
sidebar_position
2

Store Reactivo

Utilidad

Esta librer铆a nos permitir谩 actualizar los objetos de dominio desde nuestra Capa de aplicaci贸n implementando interfaces de nuestros Domain services

Instalaci贸n

Este store puedes instalarlo de la siguiente forma:

npm i --save @codescouts/store

Dependencias

Actualizar nuestra UI desde una capa de aplicaci贸n

Partiremos del ejemplo que plantea el template que construimos 馃憞

npx create-react-app my-app --template @codescouts/clean-architecture-template

Partiendo de este ejemplo tenemos un caso de uso en la Capa de aplicaci贸n que agrega un log, y posteriormente necesitamos actualizar la UI para que el usuario vea los logs que se van agregando, 驴Cierto?.

Cuando invocamos el execute del caso de uso, podremos utilizar objetos de dominio, pero de vez en cuando, nos interesar谩 actualizar tu UI.

Si inyectamos un DomainService utilizando nuestra librer铆a, no solo podremos acceder a los objetos all铆 guardados, sino que cada vez que lo actualicemos, tambi茅n lo har谩 la UI.

Nuestro caso de uso

import { IEventDispatcher } from "@codescouts/events";

import { Log } from "@domain/model/Log";
import { LoggerService } from "@domain/services/LoggerService";

export class TestUseCase {
    constructor(private readonly loggerService: LoggerService) {

    }

    public execute(message: string) {
        const log = new Log(message);

        this.loggerService.update(log);
    }
}

Nuestro Domain Service

import { Log } from "../model/Log";

export interface LoggerService {
    logs: Log[],
    save: (log: Log) => void;
}

Nuestro Componente React

Veamos el siguiente componente de UI que mostrar谩 los logs

import styles from "./Logs.module.css";
import { useHomeViewModel } from "../useHomeViewModel";

export const Logs = () => {
  const { logs, test, input } = useHomeViewModel();

  return (
    <div className={styles.log}>
      <div className={styles.logContainer}>
        <input
          className={styles.text}
          type="text"
          ref={input}
          placeholder="Write log"
          onKeyDown={(e) => {
            if (e.key === "Enter") {
              test();
            }
          }}
        />
        <button className={styles.addLog} onClick={test}>
          Add Log
        </button>
      </div>
//highlight-start
      <ul>
        {logs.map((log) => (
          <li key={log.when}>{log.format()}</li>
        ))}
      </ul>
//highlight-end
    </div>
  );
};

:::note nota Como puedes observar en la l铆nea 27, estamos utilizando la instancia de la clase Log. Veamos su implementaci贸n: :::

export class Log {
    public readonly when: number;

    constructor(public readonly message: string) {
        this.when = new Date().getTime()
    }

    public format() {
        return `${this.message} - ${new Date(this.when).toDateString()}`;
    }
}

Si recordamos lo que plantea esta arquitectura aqu铆 Clean architecture, veremos que utilizamos un ViewModel como componente que desacopla la UI de su comportamiento. Y ser谩 algo as铆 馃憞

import { useCallback, useRef } from "react";

import { TestUseCase } from "@application/test-use-case";
import { useLogger } from "@infrastructure/services";

export const useHomeViewModel = () => {
    const input = useRef<HTMLInputElement>(null);
    //highlight-start
    const loggerService = useLogger();
    const testUseCase = new TestUseCase(loggerService);
    //highlight-end

    const test = useCallback(() => {
        if (!input.current!.value) return;

        testUseCase.execute(input.current!.value);
    }, [testUseCase])

    return { logs, test, input }
}

Ahora, y como implementar铆amos el useLogger como implementaci贸n de nuestro DomainService, veamoslo 馃憞

Implementaci贸n de Domain Service

import { create } from "@codescouts/store";

import { Log } from "@domain/model";
import { LoggerService } from "@domain/services";

export const useLogger = create<LoggerService>((set) => ({
    logs: [],
    save: (log: Log) => set((state) => ({ logs: [...state.logs, log] }))
})).withPersist(Log)
   .build();

Como vemos aqu铆, este store, recibe en su Generic el objeto que quieres implementar, y al construirlo recibe un setter, este mismo permitir谩 actualizar el objeto.

Persistir el store

Nuestra librer铆a soporta el guardado y la restauraci贸n de los objetos de dominio incluso cuando el usuario refresca el navegador.

Para ello, lo que tienes que hacer es utilizar el m茅todo withPersist y pasarle como argumento la referencia de tu objeto.

Si deseas guardar tus objetos en localstorage para restaurar los objetos, debes invocar la funcion withPersist, y como argumento referenciar tu clase.

export const useLogger = create<LoggerService>((set) => ({
    logs: [],
    save: (log: Log) => set((state) => ({ logs: [...state.logs, log] }))
}))
//highlight-start
   .withPersist(Log)
//highlight-end
   .build();

:::note nota Nuestro store se encargar谩 de instanciar nuevamente la clase y todas sus relaciones, para que puedas disponer de todo su comportamiento nuevamente. :::

Restaurar el estado

class Foo {
  inner: Bar;

  public constructor(){
    this.inner = new Bar()
  }

  foo() {

  }
}

Nuestro store es capaz de no solo restaurar la instancia de la clase Foo sino tambi茅n restaurar谩 la instancia de la clase Bar.

Al finalizar debes ejecutar el build, para que cree el estado seg煤n tu configuraci贸n.

Tambien tienes disponible este store para Svelte, mantiene tambien la misma api.

npm i --save @codescouts/svelte-store

Instanciar el caso de uso con Inyecci贸n de Dependencias

Puedes ver la documentaci贸n aqu铆: 驴C贸mo inyectar las dependencias?

Una vez que configures el inyector de dependencias, ser谩 as铆

import { useCallback, useRef } from "react";
import { useResolve } from "@codescouts/ui";

import { TestUseCase } from "@application/test-use-case";
import { useLogger } from "@infrastructure/services";

export const useHomeViewModel = () => {
    const input = useRef<HTMLInputElement>(null);
    const { logs } = useLogger();
    //highlight-start
    const testUseCase = useResolve(TestUseCase);
    //highlight-end

    const test = useCallback(() => {
        if (!input.current!.value) return;

        testUseCase.execute(input.current!.value);

        input.current!.value = "";
        input.current!.focus();
    }, [testUseCase])

    return { logs, test, input }
}