Strict mode in TypeScript: описание флагов, примеры

от автора

—strict флаг включает следующие флаги:

—strictNullChecks
—alwaysStrict
—noImplicitAny
—noImplicitThis
—strictBindCallApply
—strictFunctionTypes
—strictPropertyInitialization

Приведем примеры и попытаемся разобраться в одном месте, что все это значит.

// I. —strictNullChecks

Знаменитая проблема с NPE (null pointer exception, billion dollar mistake) в контексте TS.
По умолчанию в TS все типы Nullable и это значит, что мы можем передать “undefined” | “null” где ожидается любой другой тип (даже примитив):

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

Более интересные примеры это вызов метода, которого может и не быть

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

Так же подразумевается, что мы не можем вернуть “undefined” | “null” где это явно не ожидается

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

Придется явно указать, что может вернуться “undefined” и только после этого мы получим ошибку

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

И как бонус — более безопасные операции, где результата может не быть, с включенным флагом будет ошибка и придется явно проверять, что “find” что-то нашел:

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

// II. —alwaysStrict

Добавляет ‘use strict’ аннотацию в каждый файл, делая поведение JS более явным

// III. —noImplicitAny

Запрещает не явное использование ‘any’ в TS, т.е. код без аннотации типов

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

Отлично помогает с нетипизорованными импортами из сторонних библиотек предлагая установить type definitions

 /* 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

Включает “более строгую” проверку типов для “bind”/”call”/”apply”, без флага — это все валидный TS.

 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

Помогает отследить, что все проперти были проинициализированы в конструкторе, также необходимо включить —strictNullChecks, чтобы запретить Nullable types.

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

Однако, если присваивание будет не в самом конструкторе, убедить TS, что все ок не получится

 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'    }  } 

Если не смогли убедить TS, что проперти точно будет проинициализирована — можно сказать “Мамой клянусь, точно проинициализирую!” или более кратко “!”

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

// VI. —strictFunctionTypes
Убирает бивариантную проверку для аргументов

Вариантность в программировании, если кратко — это возможность передавать Supertype/Subtype туда, гда Type ожидается. Например, есть иерархия Shape -> Circle -> Rectangle то можно ли передать или вернуть Shape/Rectangle, если ожидается Circle?

Вариантность в программировании 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); 

Подразумевается, что функция не должна мутировать переданный аргумент (выступая в роли type producer), в TS ошибок нету, по факту — есть

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

Если функция определена вне объекта/класса, то TS попросит явно указать на что будет ссылаться “this” используя первый псевдо-аргумент с именем “this”

// 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'); 

Валидными будут вызовы

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');  

Проблем могут доставить функции-конструкторы

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');  

Интересным бонусом добавлю сравнение способов привязки контекста для классов.

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();  

ссылка на оригинал статьи https://habr.com/ru/post/490970/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *