--strict флаг включает следующие флаги (и в какой версии добавлены):
--strictNullChecks (2.0)
--alwaysStrict (2.1)
--noImplicitAny
--noImplicitThis (2.0)
--strictBindCallApply (3.2)
--strictFunctionTypes (2.6)
--strictPropertyInitialization (2.7)
Приведем примеры и попытаемся разобраться в одном месте, что все это значит.
// 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();