Mode strict en TypeScript: description des indicateurs, exemples

- Le drapeau de restriction comprend les drapeaux suivants:


--strictNullChecks
--alwaysStrict
--noImplicitAny
--noImplicitThis
--strictBindCallApply
--strictFunctionTypes
--strictPropertyInitialization

Nous donnons des exemples et essayons de comprendre en un seul endroit ce que tout cela signifie.

// I. --strictNullChecks


Le fameux problème avec NPE (exception de pointeur nul, erreur d'un milliard de dollars) dans le contexte de TS.
Par défaut, dans TS, tous les types sont Nullable et cela signifie que nous pouvons passer «non défini» | "Null" où tout autre type est attendu (même une primitive):

const bar1: { foo: number } = undefined;
const bar2: { foo: number } = null;
const bar3: number = null;
const bar4: string = null;

Des exemples plus intéressants sont un appel de méthode, qui peut ne pas être

declare var smth: { optionalMethod?(): string; };
smth.optionalMethod();

Cela implique également que nous ne pouvons pas retourner «indéfini» | «Null» où cela n'est clairement pas prévu

function getIt(): { data: number } {
  // Type 'undefined' is not assignable to type '{ data: number; }'
  return undefined;
}
getIt().data;
 

Nous devrons indiquer explicitement que "non défini" peut revenir et seulement après cela nous obtenons une erreur

function getIt(): { data: number } | undefined {
  return undefined;
}
// “Object is possibly 'undefined'”
getIt().data;
 

Et en bonus - des opérations plus sûres, où il peut n'y avoir aucun résultat, il y aura une erreur avec le drapeau activé et vous devrez vérifier explicitement que «trouver» a trouvé quelque chose:

// Object is possibly 'undefined'
[{ name: 'John', age: 4 }]
 .find(el => el.age === 42)
 .name;
 

// II. --toujoursStrict


Ajoute une annotation «use strict» à chaque fichier, rendant le comportement JS plus explicite

// III. --noImplicitAny


Interdit l'utilisation implicite de `` tout '' dans TS, c.-à-d. code sans annotation de type

 // Parameter 'a' implicitly has an 'any' type
 function id(arg) {
   return arg;
 }

Grande aide avec les importations non typées de bibliothèques tierces suggérant d'installer des définitions de type

 /* Could not find a declaration file for module '3rd-party-lib'. '/node_modules/3rd-party-lib/index.js' implicitly has an 'any' type.

Try `npm install @types/3rd-party-lib` if it exists or add a new declaration (.d.ts) file containing `declare module '3rd-party-lib';`*/

import * as session from '3rd-party-lib';
 

// IV. --strictBindCallApply


Il inclut une vérification de type «plus stricte» pour «lier» / «appeler» / «appliquer», sans indicateur - tout cela est un TS valide.

 function getFullName(name: string, surname: string): string {
   return name + surname;
 }
 
 getFullName.call(null, 'John', 42);
 getFullName.apply(null, ['John', 42]);
 getFullName.bind(null)('John');
 getFullName.bind(null, 'John')();
 getFullName.bind(null, 'John')(42);
 

// V. --strictPropertyInitialization + --strictNullChecks


Aide à suivre que toutes les propriétés ont été initialisées dans le constructeur; vous devez également activer --strictNullChecks pour désactiver les types Nullable.

 class User {
// Property 'name' has no initializer and is not definitely assigned in the constructor
   name: string;
 }

Cependant, si l'affectation n'est pas dans le constructeur lui-même, convaincre TS que tout va bien.

 class User2 {
  // Property 'name' has no initializer and is not definitely assigned in the constructor
   name: string;
     
   constructor(name: string) {
     this.initializeName();
   }
 
   initializeName() {
     this.name = 'John'
   }
 }

Si vous ne pouviez pas convaincre TS que la propriété sera exactement initialisée, vous pouvez dire "Je jure par maman, je vais certainement initialiser!" ou plus brièvement "!"

class User3 {
   // definite assignment assertion
   name!: string;
 }
 


// VI. --strictFunctionTypes


Supprime un contrôle bivariant pour les arguments.

Variant dans la programmation, bref - c'est la capacité de passer Supertype / Sous - type là où type est prévu. Par exemple, il existe une hiérarchie Forme -> Cercle -> Rectangle, est- il possible de transférer ou de renvoyer une Forme / Rectangle si un Cercle est attendu ? Option de

programmation habr , SO

interface Shape { name: string };
interface Circle extends Shape { width: number };
interface Rectangle extends Circle { height: number };
 
declare var logSC: (figure: Shape) => Circle;
declare var logRC: (figure: Rectangle) => Circle;
 
declare var logCC: (figure: Circle) => Circle;
 
declare var logCS: (figure: Circle) => Shape;
declare var logCR: (figure: Circle) => Rectangle;
 
declare var wlogBB: (fn: (figure: Circle) => Circle) => void;
 
wlogBB(logCC);
wlogBB(logSC);
wlogBB(logCR);
 
// always Error
wlogBB(logCS);
// Error with --strictFunctionTypes
wlogBB(logRC);

Il est entendu que la fonction ne doit pas muter l'argument passé (agissant en tant que producteur de type), il n'y a pas d'erreurs dans TS, en fait - il y a

const squares: Square[] = [{ name: 'Square', width: 5 }];
 
// function looks like a consumer of argument
function addSmth(arg: Shape[]) {
 // work with argument as a producer
 arg.push({ name: 'Square' });
}
addSmth(squares);
 

// VII. --noImplicitThis


Si la fonction est définie en dehors de l'objet / classe, alors TS vous demandera d'indiquer explicitement à quoi «ceci» fera référence en utilisant le premier pseudo-argument nommé «ceci»

// TS force to add annotation for 'this'
 function getName(this: { name: string }, surname: string): string {
   return this.name;
 }
 
 // The 'this' is not assignable
 getName.call({}, 'Smith');
 getName.apply({}, ['Smith']);
 getName.bind({})('Smith');

Les appels seront valides

const somePerson = { name: 'John', getName };
const fullName: string = somePerson.getName('Smith')
 
getName.call({name: 'John'}, 'Smith');
getName.apply({name: 'John'}, ['Smith']);
getName.bind({name: 'John'})('Smith');
 

Les fonctions de constructeur peuvent provoquer des problèmes

function Person(this: { name: string }, name: string) {
   this.name = name;
 }
 // 'new' expression, whose target lacks a construct signature
 // Use class )
 const person = new Person('John');
 

Un bonus intéressant est d'ajouter une comparaison des méthodes de liaison de contexte pour les classes.

class A {
   x = 42;
 
   constructor() {
     this.getBound = this.getBound.bind(this);
   }
 
   getSimple(): number {
     return this.x;
   }
 
   // Has to add type for 'this', TS dont force it
   getSimpleAnnotated(this: A): number {
     return this.x;
   }
 
   getArrow = (): number => this.x;
 
   getBound(this: A): number {
     return this.x;
   }
 }
 
 const a = new A();
 
 // False positive: TS - ok, Runtime - error
 const getSimple = a.getSimple;
 getSimple();
 
 // Correct: TS - error, Runtime - error
 const getSimpleAnnotated = a.getSimpleAnnotated;
 getSimpleAnnotated();
 
 // Correct: TS - ok, Runtime - ok
 const getArrow = a.getArrow;
 getArrow();
 
 // False negative: TS - error, Runtime - ok
 const getBound = a.getBound;
 getBound();
 

All Articles