C'est ce que j'aime dans la dactylographie parce que cela m'empêche de donner des fessées. Mesurez la longueur d'une valeur numérique, etc. Au début, bien sûr, j'ai craché, indigné d'être harcelé par toutes sortes de formalités stupides. Mais ensuite je me suis impliqué, je suis tombé amoureux plus fort. Eh bien, dans le sens d'un peu plus strict. J'ai activé l'option strictNullChecks dans le projet et j'ai passé trois jours à corriger les erreurs qui se sont produites. Et puis il s'est réjoui avec satisfaction, notant à quel point le refactoring est désormais facile et sans contrainte.
Mais alors vous voulez quelque chose d'encore plus. Et ici, le tapuscrit doit expliquer quelles restrictions vous vous imposez, et vous lui déléguez la responsabilité de surveiller le respect de ces restrictions. Allez, brise-moi complètement.
Exemple 1
Il y a quelque temps, j'ai été captivé par l'idée d'utiliser react comme moteur de création de modèles sur le serveur. Capturé bien sûr par la possibilité de taper. Oui, il y a toutes sortes de carlin, de moustache et quoi d'autre. Mais le développeur doit se rappeler s'il a oublié d'étendre l'argument passé au modèle avec de nouveaux champs. (Si ce n'est pas le cas, corrigez-moi. Mais en général, je m'en fiche - Dieu merci, je n'ai pas à faire face à la génération de modèles par la nature de mon travail. Et un exemple sur autre chose).
Et ici, nous pouvons normalement taper les accessoires passés au composant, et obtenir les conseils IDE appropriés lors de l'édition du modèle. Mais c'est à l'intérieur du composant. Assurons-nous maintenant que nous n'avons pas transféré de gauchisme à ce composant.
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),
));
}
}
Désormais, si nous essayons de transférer les accessoires de la commande vers le composant utilisateur, nous serons immédiatement alertés de ce malentendu. Frais? Frais.
Mais c'est au moment de la génération html. Comment vont les choses avec son utilisation ultérieure? Parce que le résultat de l'instanciation de Rendered est juste une chaîne, alors le typographie ne jurera pas, par exemple, avec la construction suivante:
const html: Rendered<SomeProps> = 'Typescript cannot into space';
En conséquence, si nous écrivons quelque chose comme ceci:
@Get()
public index(): Rendered<IHelloWorld> {
return new Rendered(HelloWorldComponent, helloWorldProps);
}
cela ne garantit en aucun cas que le résultat de la compilation de HelloWorldComponent sera renvoyé à partir de cette méthode .
, :)
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. .
Ici, vous pouvez également remarquer que la communication entre les composants du système doit être découplée via des interfaces. Mais il est tout à fait possible pour le côté récepteur de prescrire qu'il attend une IApiResponse , mais le service de la couche logique de domaine l'est, qu'il renvoie une implémentation spécifique d' ApiResponse .
Bien ...
J'espère que ce matériel vous a intéressé. Pour certains, cette approche peut sembler redondante, et je n’exhorte pas tout le monde à ajouter d’urgence de tels «gardes» à leurs projets. Mais j'espère que vous avez trouvé matière à réflexion dans mon article.
Merci pour votre temps. Je serais heureux de recevoir des critiques constructives.