preface

“Mom, I want to write TypeScript.” “Enough errors, kid?”

The “use strict” directive is new in JavaScript 1.8.5 (ECMAScript5).

So far, front-end ER basically open strict mode by default to knock code.

So did you know that Typescript actually has its own strict schema?

1. TypescriptStrict schema rules

When Typescript’s strict mode is set to ON, it uses strict typing rules in the Strict family to validate code for all files in a project. The rule is:

Rule name explain
noImplicitAny Variables or function arguments are not allowed to be implicitanyType.
noImplicitThis Don’t allowthisContext is implicitly defined.
strictNullChecks Not allowed to appearnullorundefinedThe possibility of.
strictPropertyInitialization Validates the properties defined before and after initialization inside the constructor.
strictBindCallApply rightbind, call, applyStricter type detection.
strictFunctionTypes Strictly contravariant comparison of function parameters.

2. noImplicitAny

This rule does not allow variables or function arguments to have an implicit any type. See the following example:

Function extractIds (list) {return list.map(member => member.id)}Copy the code

The above example does not type the list; the map loops through the item parameter member. In Typescript strict mode, the following error occurs:

Function extractIds (list) {// ❌ ^^^^ // Parameter 'list' // has an 'any' type.ts (7006) Return list.map(member => member.id) // ❌ ^^^^^^ // Parameter 'member' // has an 'any' type.ts (7006)} return list.map(member => member.id) // ❌ ^^^^^^ // Parameter 'member' // has an 'any' type.ts (7006)}Copy the code

The correct way to write it is:

Interface Member {id: number name: string} function extractIds (list: Member[]) { return list.map(member => member.id) }Copy the code

1.1 What Can I Do about Browser Events?

Browser events, such as e.preventDefault(), are key code that prevents the browser from behaving by default.

This is an error in Typescript strict mode:

Function onChangeCheckbox (e) {// ❌ ^ // Parameter 'e' implicitly // has an 'any' type.ts (7006) e.preventDefault() const value = e.target.checked validateCheckbox(value) }Copy the code

If you need to use this class normallyWeb API, you need to define the extension globally. Such as:

Interface ChangeCheckboxEvent extends MouseEvent {target: HTMLInputElement } function onChangeCheckbox (e: ChangeCheckboxEvent) { e.preventDefault() const value = e.target.checked validateCheckbox(value) }Copy the code

1.2 Third-party libraries also need to define types

Note that this also raises an error if you import a non-typescript library, because the imported library is of type ANY.

Import {Vector} from 'Sylvester' // ❌ ^^^^^^^^^^^ // Could not find a declaration file // for module 'sylvester'. // 'sylvester' implicitly has an 'any' type. // Try `npm install @types/sylvester` // if it exists or add a new declaration (.d.ts) // file containing `declare module 'sylvester'; ` // ts(7016)Copy the code

This can be a major headache for projects refactoring Typescript versions, requiring specific definition of third-party library interface types

3. noImplicitThis

This rule does not allow the this context to be defined implicitly. See the following example:

Function uppercaseLabel () {return this.label.touppercase ()} const config = {label: 'foo-config', uppercaseLabel } config.uppercaseLabel() // FOO-CONFIGCopy the code

In non-strict mode, this points to the Config object. This.label simply retrieves config.label.

However, the reference to this on a function may be ambiguous:

Function uppercaseLabel () {return this.label.toupperCase () // ❌ ^^^^ // 'this' implicitly has type 'any' // because it does not have a type annotation. ts(2683) }Copy the code

If this.label.toupperCase () is executed alone, an error is reported because this context config no longer exists because label is undefined.

One way around this is to prevent this from using functions without context:

// Typescript strict mode const config = {label: 'foo-config', uppercaseLabel () {return this.label.touppercase ()}}Copy the code

It is better to write interfaces that define all types rather than infer from Typescript:

Interface MyConfig {label: string uppercaseLabel: (params: void) => string} const config: MyConfig = { label: 'foo-config', uppercaseLabel () { return this.label.toUpperCase() } }Copy the code

4. strictNullChecks

This rule does not allow the possibility of null or undefined. See the following example:

Function getArticleById (articles: Article[], id: string) { const article = articles.find(article => article.id === id) return article.meta }Copy the code

In Typescript non-strict mode, there is no problem writing this way. But strict mode can get you into a little trouble:

“You can’t do that, in casefindDidn’t match anything?” :

Function getArticleById (articles: Article[], id: String) {const article = articles.find(article => article.id === id) return article.meta // ❌ ^^^^^^^ // Object is possibly 'undefined'. ts(2532) }Copy the code

“I star you star!

So you will change to the following:

Function getArticleById (articles: Article[], id: string) { const article = articles.find(article => article.id === id) if (typeof article === 'undefined') { throw new Error(`Could not find an article with id: ${id}.`) } return article.meta }Copy the code

“It smells good!

5. strictPropertyInitialization

This rule validates the properties defined inside the constructor before and after initialization.

You must ensure that each instance’s properties have an initial value, which can be assigned in the constructor or when the property is defined.

(strictPropertyInitialization, this smelly long names like the React in the source of many wayward property)

See the following example:

// Typescript non-strict mode class User {username: string; } const user = new User(); const username = user.username.toLowerCase();Copy the code

If strict mode is enabled, the type checker further reports an error:

class User { username: string; // ❌ ^^^^^^ // Property 'username' has no initializer // and is not definitely assigned in the constructor} const user = new User(); / const username = user.username.toLowerCase(); // ❌ ^^^^^^^^^^^^ // TypeError: Cannot read property 'toLowerCase' of undefinedCopy the code

There are four solutions.

Option # 1: Permitundefined

Provide a undefined type for the username attribute definition:

class User {
  username: string | undefined;
}

const user = new User();
Copy the code

The attribute can string | undefined type, but like this, to ensure that when using value of type string:

const username = typeof user.username === "string"
  ? user.username.toLowerCase()
  : "n/a";
Copy the code

This is very Typescript.

Scenario # 2: Attribute values are explicitly initialized

It’s a bit silly, but it works:

class User {
  username = "n/a";
}

const user = new User();

// OK
const username = user.username.toLowerCase();
Copy the code

Option # 3: Assign in the constructor

The most useful solution is to add parameters to the username constructor and assign them to the username property.

Thus, whenever new User() is used, a default value must be supplied as an argument:

class User {
  username: string;

  constructor(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();
Copy the code

This can be further simplified with the public modifier:

class User {
  constructor(public username: string) {}
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();
Copy the code

Scenario # 4: Explicit assignment assertion

In some scenarios, properties are initialized indirectly (using helper methods or dependency injection libraries).

In this case, you can use explicit assignment assertions on attributes to help the type system recognize the type.

class User { username! : string; constructor(username: string) { this.initialize(username); } private initialize(username: string) { this.username = username; } } const user = new User("mariusschulz"); // OK const username = user.username.toLowerCase();Copy the code

By adding an explicit assignment assertion to the username property, we tell the type checker: username, that it can expect the property to be initialized even if it cannot detect the property itself.

6. strictBindCallApply

This rule will test types more rigorously for bind, call, and apply.

What you mean? See the following example:

// JavaScript
function sum (num1: number, num2: number) {
  return num1 + num2
}

sum.apply(null, [1, 2])
// 3
Copy the code

Non-strict mode does not validate the type and number of arguments if you can’t remember the type. Neither Typescript nor the environment (probably the browser) will raise an error when running code:

Function sum (num1: number, num2: number) {return num1 + num2} sum. Apply (null, [1, 2, 3]). 3?Copy the code

In Typescript’s strict mode, this is not allowed:

Function sum (num1: number, num2: Num1 + num2} sum. Apply (null, [1, 2, 3]) // ❌ ^^^^^^^^^ // Argument of type '[number, number, number]' is not // assignable to parameter of type '[number, number]'. // Types of property 'length' are incompatible. // Type '3' is not assignable to type '2'. ts(2345)Copy the code

So what? “…” Extended operators and reduce friends come to the rescue:

// Typescript strict mode function sum (... args: number[]) { return args.reduce<number>((total, num) => total + num, 0) } sum.apply(null, [1, 2, 3]) // 6Copy the code

7. strictFunctionTypes

This rule checks and restricts function type parameters from being contravariantly and not bivariantly.

At first glance, inner OS: “What is this?” , here is an introduction:

What are covariance and contravariance?

Covariant and contravariant wikipedia is very complicated, but it really boils down to one principle.

  • A subtype can be cast to a parent type implicitly

In the easiest example to understand, the relationship between int and float can be written as follows. Int ≦ float: That is, int is a subtype of float.

This stricter check applies to all function types except method or constructor declarations. Methods are specifically excluded to ensure that classes and interfaces with generics, such as Array, remain covariant overall.

Look at this example where Animal is the parent of Dog and Cat:

declare let f1: (x: Animal) => void; declare let f2: (x: Dog) => void; declare let f3: (x: Cat) => void; f1 = f2; // error f2 = f1 when enabling --strictFunctionTypes; // correct f2 = f3; / / errorCopy the code
  1. The first assignment statement is allowed in the default type checking mode, but will be flagged as an error in strict function type mode.
  2. The strict function type pattern marks it as an error because it cannot be justified.
  3. In either case, the third assignment is wrong because it never makes sense.

Another way to describe this example is that T is doubly variable in type (x: T) => void in the default type-checking mode, but is mutable in strict function type mode:

interface Comparer<T> { compare: (a: T, b: T) => number; } declare let animalComparer: Comparer<Animal>; declare let dogComparer: Comparer<Dog>; animalComparer = dogComparer; // Error dogComparer = animalComparer; / / rightCopy the code

Write here, forced to die a dish chicken front end.

Summary & Reference

Reference article:

  1. How strict is Typescript’s strict mode?
  2. How to understand covariant contravariance in programming languages?
  3. TypeScript is strict about function types

In job interviews, people often ask why Typescript is better than JavaScript.

From these strict mode rules, you can get a glimpse of the secrets, today strict, tomorrow Bug seconds to shake the pot, oh yeah.

❤️ Read three things

If you find this article inspiring, I’d like to invite you to do me three small favors:

  1. Like, so that more people can see this content (collection does not like, is a rogue -_-)
  2. Follow the public account “front end persuader” to share original knowledge from time to time.
  3. Look at other articles as well
  • Chrome Devtools Advanced Debugging Guide (New)
  • 90 lines of code, 15 elements for infinite scrolling
  • React Hooks 120 lines of code implement a fully interactive drag-and-upload component
  • React Hooks 160 lines of code for dynamic and cool visualizations – leaderboards

You can also get all the posts from my GitHub blog:

Front-end persuasion guide: github.com/roger-hiro/…