Pular para o conteúdo principal

Manager

Managers são singletons que lidam com efeitos colaterais globais. Uma espécie de useEffect() para o store de dados central.

Os managers padrão orquestram o comportamento assíncrono complexo que o Data Client fornece de fábrica. Eles podem ser facilmente configurados com getDefaultManagers() e estendidos com seus próprios Managers personalizados.

Managers devem implementar middleware, que os conecta ao fluxo de controle do store central. Além disso, cleanup() e init() se conectam ao ciclo de vida do store para comportamentos de configuração/encerramento.

type Dispatch = (action: ActionTypes) => Promise<void>;

type Middleware = (controller: Controller) => (next: Dispatch) => Dispatch;

interface Manager {
middleware: Middleware;
cleanup(): void;
init?: (state: State<any>) => void;
}

Ciclo de vida​

middleware​

middleware é muito parecido com um middleware do redux. A única diferença é que a função next() retorna uma Promise.

Essa promise resolve quando a atualização do reducer é confirmada (commit) ao usar o <DataProvider />. Isso é necessário porque a fase de commit é agendada de forma assíncrona. Isso permite criar managers que executam trabalho depois que o DOM é atualizado e também com o estado recém-computado.

Como o redux é totalmente síncrono, é preciso colocar um adaptador na frente dos middlewares no estilo do Reactive Data Client para garantir que eles possam consumir uma promise. Inversamente, os middlewares do redux precisam ser alterados para repassar promises.

Middlewares interceptam actions que são despachadas e, em seguida, podem também despachar suas próprias actions. Para saber mais sobre middlewares, veja a documentação do redux.

init(state)​

Chamado com o estado inicial depois que o provider é montado. Pode ser útil para executar, no início, uma configuração que depende de o estado realmente existir.

cleanup()​

Realiza a limpeza de quaisquer recursos pendentes depois que o manager não está mais em uso.

Adicionando managers ao Reactive Data Client​

Use a prop managers do DataProvider. Certifique-se de elevá-los ao nível do módulo ou envolvê-los em um useMemo() para garantir que não sejam recriados. Managers têm estado interno, então é importante não recriá-los constantemente.

index.tsx
import { DataProvider, getDefaultManagers } from '@data-client/react';
import { createRoot } from 'react-dom/client';
import App from './App';
import MyManager from './MyManager';

const managers = [...getDefaultManagers(), new MyManager()];

createRoot(document.body).render(
<DataProvider managers={managers}>
<App />
</DataProvider>,
);

Fluxo de controle​

Managers se integram ao store do DataProvider por meio de seus ciclos de vida e middleware. Eles orquestram fluxos de controle complexos interceptando e despachando actions, além de lerem o estado interno.

Fluxo flux do ManagerFluxo flux do Manager

O trabalho do middleware é despachar actions, responder a actions, ou ambos.

Despachando actions​

O Controller fornece dispatchers de actions com tipagem segura.

import type { Manager, Middleware } from '@data-client/react';
import CurrentTime from './CurrentTime';

export default class TimeManager implements Manager {
  declare protected intervalID?: ReturnType<typeof setInterval>;

  middleware: Middleware = controller => {
    this.intervalID = setInterval(() => {
      controller.set(CurrentTime, { id: 1 }, { id: 1, time: Date.now() });
    }, 1000);

    return next => async action => next(action);
  };

  cleanup() {
    clearInterval(this.intervalID);
  }
}

Lendo e consumindo actions​

actionTypes inclui todas as constantes para distinguir entre diferentes actions.

import type { Manager, Middleware } from '@data-client/react';
import { actionTypes } from '@data-client/react';

export default class LoggingManager implements Manager {
  middleware: Middleware = controller => next => async action => {
    switch (action.type) {
      case actionTypes.SET_RESPONSE:
        if (action.endpoint.sideEffect) {
          console.info(
            `${action.endpoint.name} ${JSON.stringify(action.response)}`,
          );
          // wait for state update to be committed
          await next(action);
          // get the data from the store, which may be merged with existing state
          const { data } = controller.getResponse(
            action.endpoint,
            ...action.args,
            controller.getState(),
          );
          console.info(`${action.endpoint.name} ${JSON.stringify(data)}`);
          return;
        }
      // actions must be explicitly passed to next middleware
      default:
        return next(action);
    }
  };

  cleanup() {}
}

Em blocos condicionais, o tipo da action é restringido (narrowing), incentivando o acesso seguro aos seus membros.

Caso queiramos 'tratar' uma determinada action, podemos 'consumi-la' não chamando next.

import type {
  Manager,
  Middleware,
  EntityInterface,
} from '@data-client/react';
import { actionTypes } from '@data-client/react';
import isEntity from './isEntity';

export default class CustomSubsManager implements Manager {
  declare protected entities: Record<string, EntityInterface>;

  middleware: Middleware = controller => next => async action => {
    switch (action.type) {
      case actionTypes.SUBSCRIBE:
      case actionTypes.UNSUBSCRIBE:
        const { schema } = action.endpoint;
        // only process registered entities
        if (schema && isEntity(schema) && schema.key in this.entities) {
          if (action.type === actionTypes.SUBSCRIBE) {
            this.subscribe(schema.key, action.args[0]?.product_id);
          } else {
            this.unsubscribe(schema.key, action.args[0]?.product_id);
          }

          // consume subscription if we use it
          return Promise.resolve();
        }
      default:
        return next(action);
    }
  };

  cleanup() {}

  subscribe(channel: string, product_id: string) {}
  unsubscribe(channel: string, product_id: string) {}
}

Ao usar return Promise.resolve(); em vez de chamar next(action), impedimos que os managers listados depois deste vejam essa action.

Tipos: FETCH, SET, SET_RESPONSE, RESET, SUBSCRIBE, UNSUBSCRIBE, INVALIDATE, INVALIDATEALL, EXPIREALL

Casos de uso​

Exemplos mínimos para casos de uso comuns de Manager: