A lot of big companies are using TypeScript, and I feel like I can’t do a front end of TypeScript if I don’t look at it 😂. This post is from my blogThat’s not necessarily trueSeries, so, this idea is not necessarily true of 😉

TypeScript has a type system and is a superset of JavaScript. It compiles into plain JavaScript code. TypeScript supports any browser, any environment, any system and is open source.

As a weakly typed, dynamic language, JavaScript is like an untamed wild horse. Anyone can sit on it for a while, but the only person who can really ride it is an expert.

In recent years, the front end has undergone rapid development and is no longer just a toy. In the face of ever larger and more durable projects, this relaxed approach has been a hindrance.

When things get big, there are rules

Rules are learned from experience, but also for the betterment of the direction, such as design principles and design patterns in programming. “Man Maketh Manners”, I remember in Kingsman, the leading characters always like to say that when they educate others, “Without propriety, there is no way to stand up”.

In TypeScript, propriety is Type, and Type is rule. Typescript provides compile-time static type checking through type annotations to detect errors ahead of time and improve code readability and maintainability.

Type annotations in TypeScript are a lightweight way to add constraints to functions or variables

In JavaScript, variables are used to store specific values at a specific time, and their values and data types can change throughout the life of the script.

In TypeScript, identifiers (names of variables, functions, classes, attributes, or function parameters) specify the type (or infer from the type) when they are defined. At compile time, TypeScript will tell you to throw an error if an unexpected type is present (although sometimes it doesn’t affect the program’s performance).

In TypeScript, type annotations are added to identifiers with: type.

let isDone: boolean = false;    / / a Boolean;
let decLiteral: number = 6;    / / number.
let name: string = "bob";    / / the string;
let list: number[] = [1.2.3];    // Array<number>;
let list: Array<number> = [1.2.3];    // Array<number>;
let x: [string, number];    // tuple;
enum Color {Red, Green, Blue}    // enum;
let notSure: any = 4;    // any;
function warnUser() :void {    // void;
console.log("This is my warning message");
}
let u: undefined = undefined;    // undefined;
let n: null = null;    // null;
function error(message: string) :never {    // never;
throw new Error(message);
}
let obj: object = {};    // object
Copy the code

In TypeScript, arrays merge objects of the same type, while tuples merge objects of different types. (Array

, can also merge different types of data)

Are the types in the type annotation those types?

One of TypeScript’s core tenets is type checking for the structure of a value, sometimes referred to as “duck typing” or “structure typing.” The ones above are just base types, which are the basic units for filling structures. In TypeScript, types should not remain at the level of JavaScript data types, but should include a composite structure of the underlying types.

let str: 'Hello';    // String literal type;
str = 'Hi'    / / the error;

let something: 'Hello' | 1;    // Join type;
something = 1    / / ok.

let obj: {name: string, age: number};    // Object literals
obj = {
    name: "Night Xiaochen".age: 18,}Copy the code

In other words, when defining an identifier, a type template is used to describe the structure and internal type composition of the identifier. That is, the type template is what the identifier is expected to look like.

Code is for people to look at, and for machines to run by the way

This is what code is supposed to do. But in TypeScript, the two sentences can be reversed. Code is meant for machines to run, and for people to read, by the way. When it comes to TypeScript’s benefits, it’s important to note the compiler and IDE enhancements, including code completion, interface hints, jump to definitions, refactoring, and more.

This also benefits from the fact that the types of identifiers are accurately delineated or expressed, so writing good Typescript code should specify the types of identifiers, not the arbitrary any.

The most common way to express complex structures is the ———— interface

Interfaces are something you don’t have in JavaScript, and are a very flexible concept that can either abstract behavior or describe “the shape of an object.” For structure types that need to be reused, you can use interface annotations instead of inline object literals.

interface Iperson {    / / object
    name: string,
    age: number,
    sayHi(): void,}let obj: Iperson = {
    name: "Night Xiaochen".age: 18.sayHi: (a)= >{}}/* —————— manual splitter —————— */

interface Iperson {    // Function type
    (name: string, age: number): string
}
let person: Iperson = (name, age) = > {
    return `${name}.${age}`
}
person('Dawn of the Night'.18);

/* —————— manual splitter —————— */

interface Iperson {    // constructor
    new (name: string, age: number)
}
let person: Iperson = class Person {
    name: string;
    age: number;
    constructor(name, age) {
        this.name = name;
        this.age = age; }}new person('Dawn of the Night'.18);

/* —————— manual splitter —————— */

interface Iperson {    // Class implements the interface
    name: string,
    age: number,
}
class Person implements Iperson{
    name = 'Dawn of the Night'
    age = 18
}
new Person()

/* —————— manual splitter —————— */

interface Iperson {    // Mixed type
    (name, age): string,
    age: number,
}

function Person() :Iperson {
    let me = <Iperson>function (name, age): string { return `${name}, ${age}` } me.age = 18; return me; } let person = Person(); Person (' yichen ', 18) person.ageCopy the code

This is how interfaces represent objects, ordinary functions, constructors, and classes. You can also accurately control the attributes of the interface, such as optional attributes, arbitrary attributes, and read-only attributes.

Finally, interfaces can inherit from each other, and interfaces can inherit from classes. When an interface inherits from a class, it inherits its members but not its implementation. However, when an interface inherits from a class that has a private or protected member, the interface can only be implemented by that class or its subclasses, depending on the nature of the class’s access modifier.

After interfaces, we can talk about classes, because they have many similarities, such as acting as type templates for objects, inheriting members, and so on.

What exactly is a class?

ES6 introduces the concept of Class, which allows you to define classes through the Class keyword, which is essentially JavaScript’s existing prototype-based inheritance syntactic sugar. Class can be inherited through the extends keyword. In addition to implementing all the functionality of ES6 classes, TypeScript adds some new uses.

class Person {
    static age: number = 18;
    constructor(public name: string, public age: number) { }
    sayHi(name: string): string{
        return `Hi,${name}`}}/* —————— splitter —————— */
var Person = /** @class */ (function () {
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    Person.prototype.sayHi = function (name) {
        return "Hi," + name;
    };
    Person.age = 18;
    returnPerson; } ());Copy the code

When TypeScript is compiled, you can see that a class is really just a function.

Prior to ES6, objects were created as constructors, which owned and shared property methods bound inside the constructor and property methods on the prototype. TypeScript interfaces describe class types that are the type templates that instance parts of classes should follow. As the static part of the class ———— constructor, functions should also have their own attribute characteristics.

interface static_person {
    age: number,
    new (name: string, age: number);
}

interface instance_person {
    name: string,
    age: number,
    say(name: string): string
}

let person: static_person = class Person implements instance_person{
    static age: number = 18;
    constructor(public name: string, public age: number) { }
    say(name) {
        return `Hi,${name}`}}new person('Dawn of the Night'.18)
Copy the code

As you can see from the above code, both the static and dynamic parts of a class have their own type templates. What if you want to use the class itself as a type template? The simplest approach is the Typeof class approach.

class Person {
  static age: number = 18;
    constructor(public name: string, public age: number) {}
    say(name) {
        return `Hi,${name}`}}class Man {
    static age: number;
    constructor(public name: string, public age: number) {}
    public sex = 'man';
    say(name){return `Hi, The ${this.sex}.${name}`}}let man: typeof Person = Man;
new man('Dawn of the Night'.18)
Copy the code

The static part of the class, the instance part of the class, and the class itself all have their own type templates to follow. Knowing the differences, you can better understand the use of classes as interfaces, interfaces that inherit classes, and so on.

class Person {
  name: string;
  age: number;
}
interface Man extends Person {
  sex: 'man'
}

let man: Man = {
    name: 'Dawn of the Night'.age: 18.sex: 'man'
}
Copy the code

In addition to structural constraints, classes also restrict their members through access modifiers, including public, private, protected, readOnly, and so on.

class Person {
  private name: string;
  protected age: number;
}

interface SayPerson extends Person {
  sayHi(): string
}

class Human extends Person implements SayPerson {
  sayHi() {
    return `Hi, The ${this.age}`}}Copy the code

When an interface inherits from a class, it inherits its members but not its implementation. When an interface inherits from a class that has a private or protected member, the interface can only be implemented by that class or its subclasses.

What if the type of an identifier is indeterminate?

For a function with similar internal logic and different input parameter types, there is no need to repeat most of the code because of different parameter types. In this case, a type variable is needed instead.

/* Generic function */
class Person {
    className = 'person'
}
class Human {
    classname = 'human'
}
function create<T> (Class: new () = >T) : T{
    return new Class();
}
create(Person).className

/* Generic interface */
interface Creat<T>{
    (Class: new () => T):T
}
class Person {
    className = 'person'
}
class Human {
    classname = 'human'
}
function create<T> (Class: new () = >T) : T{
    return new Class();
}
let person: Creat<Person> = create;

person(Person)    // OK
person(Human)    // Error
Copy the code

Note that type variables represent types, not values. The type variable can be any type, but depending on the scenario, it is best to be more precise in describing the type of the identifier. In line with the above statement, “To write Typescript code well, you need to specify the types of identifiers, not the arbitrary any.” So for generics, we can also make some constraints, that is, generic constraints.

class Person {
  name: string;
  age: number;
}
interface Man extends Person {
  sex: 'man'
}
function getProperty<T.K extends keyof T> (obj: T, key: K) :any {
  return obj[key]
}
let man: Man = {
    name: 'Dawn of the Night'.age: 18.sex: 'man'
}
getProperty(man, 'sex')
Copy the code

Annotating the type of an identifier with a type variable can sometimes feel imprecise.

Know the possible types of identifiers and combine them

class Man {
    name: string;
    age: number;
    study():string {return ' '}}class Women {
    name: string;
    age: number;
    sing():string{return ' '}}function instance(Class: Man | Women) {
    if((<Man>Class).study) { return (<Man>Class).study() } else { return (<Women>Class).sing() } } let man:Man = { name: 'Night Xiaochen ', age: 18, study() {return' I love learning '; } } let women: Women = { name: 'godness', age: 17, sing() {return 'I love singing'}} instance(man) // I love learning instance(women) // I love singingCopy the code

There are cross types, union types, and so on, while type naming is a more flexible way to organize types.

/ / website 🌰
type Name = string;
type NameResolver = (a)= > string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver) :Name {
    if (typeof n === 'string') {
        return n;
    }
    else {
        returnn(); }}Copy the code

When you have multiple types, you sometimes need to do something special for a type, so there are type assertions (< type >) and type guards (typeof, instanceof, in, etc.).

You can also use conditional judgment to choose which type to use.

/ / website 🌰
declare function f<T extends boolean> (x: T) :T extends true ? string : number;
// Type is 'string | number
let x = f(Math.random() < 0.5)
Copy the code

Of course, there is no need to add type annotations for many of the identifiers in the code above.

Type inference, that is, where and how are types inferred

More type annotations isn’t always better, either. Even if you don’t add type annotations, TypeScript finds the best generic type through contextual categorization and so on.