Strict mode in TypeScript: flag description, examples

--strict flag includes the following flags:


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

We give examples and try to understand in one place what all this means.

// I. --strictNullChecks


The famous problem with NPE (null pointer exception, billion dollar mistake) in the context of TS.
By default, in TS all types are Nullable and this means that we can pass “undefined” | “Null” where any other type is expected (even a primitive):

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

More interesting examples are a method call, which may not be

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

It is also implied that we cannot return “undefined” | “Null” where this is clearly not expected

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

We’ll have to explicitly indicate that “undefined” can return and only after that we get an error

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

And as a bonus - safer operations, where there may be no result, there will be an error with the flag turned on and you will have to explicitly check that “find” found something:

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

// II. --alwaysStrict


Adds 'use strict' annotation to each file, making JS behavior more explicit

// III. --noImplicitAny


Prohibits the implicit use of 'any' in TS, i.e. code without type annotation

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

Great help with untyped imports from third-party libraries suggesting to install 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


It includes a “more stringent” type check for “bind” / ”call” / ”apply”, without a flag - this is all a valid 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


Helps track that all properties have been initialized in the constructor; you must also enable --strictNullChecks to disable Nullable types.

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

However, if the assignment is not in the constructor itself, convince TS that everything is ok.

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

If you couldn’t convince TS that the property will be exactly initialized, you can say “I swear by Mom, I will definitely initialize!” or more briefly “!”

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


// VI. --strictFunctionTypes


Removes a bivariant check for arguments.

Variant in programming, in short - this is the ability to pass Supertype / Subtype there, where Type is expected. For example, there is a hierarchy Shape -> Circle -> Rectangle, is it possible to transfer or return a Shape / Rectangle if Circle is expected ?

Programming Option 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);

It is understood that the function should not mutate the passed argument (acting as type producer), there are no errors in TS, in fact - there are

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


If the function is defined outside the object / class, then TS will ask you to explicitly indicate what “this” will refer to using the first pseudo-argument named “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');

Calls will be valid

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

Constructor functions can cause problems

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

An interesting bonus is adding a comparison of context binding methods for 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