Usando propriedades de classes privadas para fortalecer a digitação no texto

É isso que adoro no texto datilografado, porque me impede de espancar bobagens. Meça o comprimento de um valor numérico, etc. A princípio, é claro, cuspi, indignado por estar sendo incomodado com todo tipo de formalidades estúpidas. Mas então eu me envolvi, me apaixonei ainda mais. Bem, no sentido de um pouco mais estrito. Ativei a opção strictNullChecks no projeto e passei três dias corrigindo os erros que surgiram. E então ele se alegrou com satisfação, observando como a refatoração é fácil e irrestrita agora.





Mas então você quer algo ainda mais. E aqui o texto datilografado precisa explicar quais restrições você impõe a si mesmo, e você delega a ele a responsabilidade de monitorar o cumprimento dessas restrições. Vamos, quebre-me completamente.





Exemplo 1

Algum tempo atrás, fui capturado pela ideia de usar o react como um mecanismo de templates no servidor. Capturado, é claro, pela possibilidade de digitação. Sim, tem todos os tipos de pug, bigode e tudo o mais. Mas o desenvolvedor deve ter em mente se ele se esqueceu de expandir o argumento passado para o modelo com novos campos. (Se não for assim, corrija-me. Mas, em geral, eu não me importo - graças a Deus eu não tenho que lidar com a geração de modelos pela natureza do meu trabalho. E um exemplo sobre outra coisa).





E aqui podemos digitar normalmente os adereços passados ​​para o componente e obter as dicas IDE apropriadas ao editar o modelo. Mas isso está dentro do componente. Agora, vamos ter certeza de que não transferimos nenhum esquerdismo para este componente.





import { createElement, FunctionComponent, ComponentClass } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';

export class Rendered<P> extends String {
  constructor(component: FunctionComponent<P> | ComponentClass<P>, props: P) {
    super('<!DOCTYPE html>' + renderToStaticMarkup(
      createElement(component, props),
    ));
  }
}
      
      



Agora, se tentarmos transferir adereços do pedido para o componente do usuário, seremos imediatamente alertados desse mal-entendido. Frio? Frio.





Mas isso é na hora da geração do html. Como vão as coisas com seu uso posterior? Porque o resultado de instanciar Rendered é apenas uma string, então o texto digitado não funcionará, por exemplo, com a seguinte construção:





const html: Rendered<SomeProps> = 'Typescript cannot into space';
      
      



Conseqüentemente, se escrevermos algo assim:





@Get()
public index(): Rendered<IHelloWorld> {
  return new Rendered(HelloWorldComponent, helloWorldProps);
}
      
      



isso não garante de forma alguma que o resultado da compilação do HelloWorldComponent será retornado desse método .





, :)





export class Rendered<P> extends String {
	_props: P;
	constructor(component: FunctionComponent<P> | ComponentClass<P>, props: P)
...
      
      



'cannot into space' , _props. . - . - _props, js , .. "" .









Object.assign('cannot into space', {_props: 42})
      
      



, . .





export class Rendered<P> extends String {
  // @ts-ignore -       noUnusedParameters
  private readonly _props: P;
  constructor(component: FunctionComponent<P> | ComponentClass<P>, props: P)
...
      
      



Object.assign , .. Rendered



_props , .





, , , . , , . .





2

, , , . - -. . .





. , . .





, -, .





ApiResponse. - , .





export interface IApiResponse {
	readonly scenarioSuccess: boolean;
	readonly systemSuccess: boolean;
	readonly result: string | null;
	readonly error: string | null;
	readonly payload: string | null;
}

export class ApiResponse implements IApiResponse {
	constructor(
		public readonly scenarioSuccess: boolean,
		public readonly systemSuccess: boolean,
		public readonly result: string | null = null,
		public readonly error: string | null = null,
		public readonly payload: string | null = null,
	) {}
}
      
      



scenarioSuccess true. , ( ) - scenarioSuccess false. - systemSuccess false. / result/error. . , scenarioSuccess true error.





, ApiResponse , :





export class ScenarioSuccessResponse extends ApiResponse {
  constructor(result: string, payload: string | null = null) {
    super(true, true, result, null, payload);
  }
}
      
      



.





- ApiResponse, " " , . .





const SECRET_SYMBOL = Symbol('SECRET_SYMBOL');

export abstract class ApiResponse implements IApiResponse {
  // @ts-ignore
  private readonly [SECRET_SYMBOL]: unknown;
  
  constructor(
    public readonly scenarioSuccess: boolean,
    public readonly systemSuccess:   boolean,
    public readonly result:  string | null = null,
    public readonly error:   string | null = null,
    public readonly payload: string | null = null,
  ) {}
}
      
      



Rendered



_props, , , . "" . . ( , ?)





. , , any. .





Aqui você também pode notar que a comunicação entre os componentes do sistema deve ser desacoplada por meio de interfaces. Mas é bem possível que o lado receptor prescreva que espera um IApiResponse , mas o serviço da camada lógica do domínio é assim, deixe-o retornar uma implementação específica de ApiResponse .





Nós vamos ...

Espero que este material seja interessante para você. Para alguns, essa abordagem pode parecer redundante, e não incentivo a todos que adicionem urgentemente esses "guardas" a seus projetos. Mas espero que você tenha encontrado algo para refletir em meu artigo.





Obrigado pelo seu tempo. Eu ficaria feliz em receber críticas construtivas.








All Articles