1. Introduction

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.

TypeScript constrains variable types through type annotations.

TypeScript is compile-time behavior that introduces no overhead and does not change the runtime.

TypeScript’s type system is structured, Structral Subtyping, which means constraints on values, not identifiers. That is, in TS, as long as the structure, attribute, and method types of two objects are the same, the types are the same.

Structral Subtyping compares types with the same structure. Naming Type Comparison Type Identifier The same, the same.

interface Foo {
    name: string;
}

interface Bar {
    name: string;
}

let foo: Foo = { name: '1' };
let bar: Bar = { name: '2' };

foo = bar; // success
Naming Type Foo and Bar are different and will report an error. But TS compiles normally.
Copy the code

2. Install the TS environment

$ npm i -g typescript      Ts # installation
$ tsc --init               Generate the tsconfig.json configuration file
$ npm install @types/node  Get the type declaration file
$ tsc filename.ts          By default, a.js file with the same name will be generated in the same directory as filename.ts
Copy the code

3. Variable types

There are seven data types available in JavaScript (the BigInt type is still in the proposal stage and is supported by most browsers but not yet included in the standard).

Note: TS also supports Bigint. Although number and Bigint both represent numbers, the two types are incompatible.

The above 7 types are still present in TS. TS is used to type JS, adding statically typed annotation extensions on top of JavaScript. If TS loses its role, that is, there is no type constraint on the variable, then the any type is used to degrade TS to JS. In addition, five types of TS are added: Array, tuple, enum, void, never, and unknown.

To sum up, there are 14 data types available in TypeScript, as follows:

    1. Any (any type)
    1. Boolean (Boolean type)
    1. Number (number type)
    1. String (String type)
    1. Null type
    1. Undefined type
    1. Symbol type
    1. Object Object type
    1. Array (Array type)
    1. Tuple (tuple type)
    1. Enum (Enumeration type)
    1. Void type
    1. Never type
    1. Unknown type

3-0. Knowledge extension

TypeScript also has types such as Number, String, Boolean, and Symbol.

In TS, number and number are two data types and cannot be equivalent. The tests are as follows:

let a: String = "S";
let b: string = "s";
a = b; // success
b = a; // error TS2322: Type 'String' is not assignable to type 'string'.
// 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible.
Copy the code

In actual project development, the Number, String, Boolean, Symbol types are not used at all, and there is no special purpose. Just as we don’t need to use the Number, String, Boolean, and Symbol constructors to create new instances.

3-1. Any (any type)

Any (any type) can represent any type of value, that is, there is no constraint. Any bypasses static type detection.

Any is usually used to declare DOM nodes, because DOM nodes are not objects in the usual sense, that is, not real objects.

let a: any = 0;
a = ' ';
a = {};

/** * A DOMValue represents a DOM value/DOM operation; * Because the concept is too broad, we define it as any */
export type DOMValue = any;
Copy the code

In TS projects, the more you use any, the more TS looks like JS. Therefore, it is recommended to avoid using any in general. If you must use unknown, it is recommended to use unknown.

We can do whatever we want with values of type any without type checking at compile time. The unknown type introduced in TS 3.0 is a security type of the Any type. You need to check the type before performing operations on the value of the unknown type, because the value of the unknown type can only be assigned to any type and unknown type.

3-2. Boolean (Boolean type)

let canRun: boolean = true;
let isSelected: boolean = false;
isSelected = 1; // error TS2322: Type 'number' is not assignable to type 'boolean'.
Copy the code

3-3. Number (Number type)

In TS, the number type, like JS, supports decimal integers, floating point numbers, binary numbers, octal numbers, and hexadecimal numbers.

let integer: number = 10;
let floatingPoint: number = 0.01;
let binary: number = 0b1111;
let octal: number = 0o111;
let hex: number = 0x1111;
Copy the code

Note: Infinity, -infinity, NaN are also of type Number.

3-4. String (String type)

Any string definition method supported in JS can be constrained by string type.

let s1: string = 'csaj';
let s2: string = `${s1} - csaj`;
let s3: string = String('csaj');
Copy the code

3-5. Null type

The null type can only be assigned to NULL.

The null type is often used in interface formulation for back-end interactions to indicate that an object or property may be null.

interface HomeInfoRsp {
    roomId: number;
    roomName: string;
}
type HomeInfoState = {
    loaded: boolean;
    data: null | HomeInfoRsp;
    loading: boolean;
    error: any;
};
Copy the code

3-6. Undefined type

Data of the undefined type can only be assigned to undefined.

The undefined type is often used to define interface types, which represent defaultable, undefined attributes.

interface ItemProps {
    name: string; description? :string;
}
Copy the code

Undefined can be assigned to void, but void cannot be assigned to undefined. The tests are as follows:

let undeclared: undefined = undefined;
let unusable: void = undefined;
unusable = undeclared; // success
undeclared = unusable; // error TS2322: Type 'void' is not assignable to type 'undefined'.
Copy the code

Undefined and NULL are subtypes of all other types, that is, undefined and NULL can be assigned to all other types.

3-7. Symbol type

The symbol type can be created using the Symbol constructor.

let s1: symbol = Symbol(a);let s2: symbol = Symbol('s2');
Copy the code

3-8. Object Indicates the object type

The object type indicates a non-primitive type.

In actual project development, it is basically useless.

let a: object = { name: 'CS agee' };
Copy the code

3-9. Array (Array type)

Arrays merge objects of the same type. If you want to merge objects of different types, you need to use a union type definition. Tuples naturally support merging different types of objects, but they are limited in length and have a type defined for each element.

Constraints on array types can be defined using both a literal [] and a generic <>. There is no essential difference between the two definitions, but I recommend using the literal []. The benefits are twofold: 1. Avoid syntax conflicts with JSX; 2.

// Array type
const PEOPLE: string[] = ['阿吉'.'CS agee'.'Brave Cow'];
const STATUS_CODE: Array<number> = [200.301.302.304.404.500];
// Tuple type
let tomInfoRecord: [string.number] = ['Tom'.25];
Copy the code

If the array element type is explicitly defined, all operations that do not conform to the type convention will be reported as an error.

const PEOPLE: string[] = ['阿吉'.'CS agee'.'Brave Cow'];
PEOPLE[0] = 'Aggie zero'; // success
PEOPLE[3] = 'Not afraid of difficulties'; // add elements
console.log(PEOPLE); // ['0 Aggie ', 'CS Aggie ', 'brave Niuniu ',' not afraid of difficulties']
PEOPLE[0] = 500; // error TS2322: Type 'number' is not assignable to type 'string'.
Copy the code

You can use associative types if the elements in an array have multiple types, in which case you must define the type using the generic <>.

const PEOPLE: Array<number | string> = ['阿吉'.'CS agee'.'Brave Cow'];
PEOPLE[0] = 'Aggie zero'; // success
PEOPLE[0] = 500; // success
Copy the code

3-10. Tuple (tuple type)

There is no concept of tuples in JavaScript because JS is a dynamic language, and dynamic languages naturally support multitype arrays of elements.

The tuple type limits the number and type of elements in an array. It is often used to return multiple values. Example: useState in React Hook:

import { useState } from 'react';
function getNumber() {
    const [count, setCount] = useState(0);
    return [count, '0x1234234'];
}
X and y are two distinct tuples
let x: [number.string] = [1.'cs'];
let y: [string.number] = ['cs'.1];
Copy the code

3-11. Enum Enum type

There is no concept of enumerations in JavaScript, and enumerations are unique to TS. Enum is compiled into a bidirectional Map structure.

Enumeration types are used to define collections of constants that contain names. TS supports both numbers and strings. Advanced uses of enumerations, such as heterogeneous types, joint enumerations, and so on, will be covered later.

Enumeration type definitions contain values and types.

Numeric enumerations start at subscript 0 and increase by default. You can also set the initial value of an enumerator.

String enumerations support the use of strings to initialize enumerators. They do not support growth. Each enumerator must be initialized.

enum ErrcodeType {
    NOLOGIN_ERROR = -1,
    FETCHCS_ERROR = -2,}enum ErrMsge {
    NOLOGIN_MSG = 'Not landed',
    FETCHCS_MSG = 'Authorization failed',}function isLogin() :{ errcode: ErrcodeType, errormsg: ErrMsge } {
    // ajax
    return { errcode: ErrcodeType.FETCHCS_ERROR, errormsg: ErrMsge.FETCHCS_MSG }
}

console.log(isLogin()); // {errcode: -2, errorMsg: 'Authorization failed'}
Copy the code

Numeric enumerations have self-mapping, string enumerations do not.

enum ErrcodeType {
    NOLOGIN_ERROR = -1,
    FETCHCS_ERROR = -2,}enum ErrMsge {
    NOLOGIN_MSG = 'Not landed',
    FETCHCS_MSG = 'Authorization failed',}console.log(ErrcodeType.NOLOGIN_ERROR); // -1
console.log(ErrcodeType[-1]); // NOLOGIN_ERROR
console.log(ErrMsge.NOLOGIN_MSG); / / not landed
console.log(ErrcodeType['Not landed']); // undefined 
// "Compile time no error, but ts file mouse move to '
Copy the code

Enumerations are self-compatible only, and in project development, it is recommended that “identical” enumerations be separated into public enumerations.

enum ErrcodeType {
    NOLOGIN_ERROR = -1,
    FETCHCS_ERROR = -2,}enum ErrCode {
    NOLOGIN_ERROR = -1,
    FETCHCS_ERROR = -2,}console.log(ErrcodeType.NOLOGIN_ERROR == ErrCode.NOLOGIN_ERROR);
// error TS2367: This condition will always return 'false' since the types 'ErrcodeType' and 'ErrCode' have no overlap.
// this means that although both enumerations are -1, they are not equal in TS
Copy the code

Let’s extend this a little bit. When we learn JS, there is a classic saying that closures are everywhere in JS, and the compiled products of enumerated types are closures. Example code:

/ / 1. Ts file
enum ErrcodeType {
    NOLOGIN_ERROR = -1,
    FETCHCS_ERROR = -2,}enum ErrMsge {
    NOLOGIN_MSG = 'Not landed',
    FETCHCS_MSG = 'Authorization failed',}// tsc 1.ts

// 1.js
var ErrcodeType;
(function (ErrcodeType) {
    ErrcodeType[ErrcodeType["NOLOGIN_ERROR"] = -1] = "NOLOGIN_ERROR";
    ErrcodeType[ErrcodeType["FETCHCS_ERROR"] = -2] = "FETCHCS_ERROR";
})(ErrcodeType || (ErrcodeType = {}));
var ErrMsge;
(function (ErrMsge) {
    ErrMsge["NOLOGIN_MSG"] = "\u672A\u767B\u9646";
    ErrMsge["FETCHCS_MSG"] = "\u6388\u6743\u672A\u901A\u8FC7";
})(ErrMsge || (ErrMsge = {}));

ErrcodeType ErrMsge
console.log(ErrcodeType); 
/ / {
// NOLOGIN_ERROR: -1,
// '-1': 'NOLOGIN_ERROR',
// FETCHCS_ERROR: -2,
// '-2': 'FETCHCS_ERROR'
// }
console.log(ErrMsge); 
// {NOLOGIN_MSG: 'failed ', FETCHCS_MSG:' failed '}
Copy the code

3-12. The void type

The void type means that no value is returned.

It only applies to functions that have no return value.

Variables of type void can only be assigned to any and unkown.

3-13. Never type

Never indicates the type of value that will never exist. Let’s look at this definition with a little bit of fun code.

type Never = 1 & 2; // Equivalent to type Never = Never
Copy the code

Did you think that Never meant that it could be both 1 and 2, which obviously doesn’t exist in JS? So never means that no value is compatible. Any means everything is compatible. Any and never have opposite meanings.

Never is a subtype of any type, that is, never can be assigned to any type, but not to any type except never itself.

Never is compatible only with its own type. NaN is not even equal to itself.

Based on the above features, never can be used to disallow specific properties under the interface, that is, to be decorated as read-only properties. As follows:

interface ItemProps {
    name: string; description? :never;
}
function makeItem(props: ItemProps) :void {
    const { name, description } = props;
    props.description = name + description; // error TS2322: Type 'string' is not assignable to type 'never'.
}

makeItem({ name: 'CS agee'.description: ' is good item! ' });
Copy the code

In the code above, assigning any type to props. Description gives a type error, equivalent to saying that description is read-only.

We usually use “x | never value equal to x” this feature, to filter the object in the field. Such as

// Any function type
type AnyFunc = (. args:any[]) = > any;
// Any object type
type AnyObj = { [key: string] :any }

// Delete the function type attribute
type GetStaticKeysFor<T> = {
    [K in keyof T]: T[K] extends AnyFunc ? never : K
}[keyof T]; // keyof T => a | never | never => a
type GetStaticFor<T> = {
    [K in GetStaticKeysFor<T>]: T[K]
}

// Preserve the attributes of the function type
type GetFuncKeysFor<T> = {
    [K in keyof T]: T[K] extends AnyFunc ? K : never
}[keyof T];
type GetFuncFor<T> = {
    [K in GetFuncKeysFor<T>]: T[K]
}
type ABC = { a: 1, b(): string, c(): number };

// Test case
type DeleteFnExm = GetStaticFor<ABC>;
// type DeleteFnExm = {
// a: 1;
// }
type KeepFnExm = GetFuncFor<ABC>;
// type KeepFnExm = {
// b: () => string;
// c: () => number;
// }
Copy the code

The never type is usually used to define a function that consistently throws an error. As follows:

function ThrowErrorMsg(msg: string) :never {
    throw Error(msg);
}
Copy the code

3-14. Unknown type

The unknown type is used to describe variables of uncertain type. The implication is that the type can be any type. This means that you need to use assertions before using Unkown.

// err.ts
function addUnknown(a: unknown, b: unknown) {
    return a + b;
}
// tsc err.ts
// error TS2365: Operator '+' cannot be applied to types 'unknown' and 'unknown'.

// success.ts
function addUnknown(a: unknown, b: unknown) {
    if (typeof a === 'number' && typeof b === 'number') {
        returna + b; }}// tsc success.ts
// Compile successfully without error
Copy the code

We can do whatever we want with values of type any without type checking at compile time. The unknown type introduced in TS 3.0 is a security type of the Any type. You need to check the type before performing operations on the value of the unknown type, because the value of the unknown type can only be assigned to any type and unknown type.

let can_not_known: unknown;
let any_thing: any = can_not_known; // success
let not_known: unknown = can_not_known; // success
let num: number = can_not_known; // error TS2322: Type 'unknown' is not assignable to type 'number'.
Copy the code

All type narrowing methods are valid for unknown. If no type narrowing is performed, the type narrowing method is valid for unknown. All operations report errors.

let result: unknown;
if (typeof result === 'number') {
    result.toFixed(); // Cursor over result will prompt: let result: number
}
result.toFixed(); // error TS2339: Property 'toFixed' does not exist on type 'unknown'.
Copy the code

4. Object type

4-1. Interface type and type alias

In development, we usually use interface and type declarations to constrain objects. We can also constrain functions, just in different syntax.

We usually refer to an interface as an interface and type as a type alias.

Interface is primarily used to define the data model, and type is primarily used for type aliases.

interface BrandPavilion {
    name: string; // Brand pavilion name
    score: number; / / weight
}
  
interface SetBrandPavilion {
    (name: string.score: number) :void;
}

type BrandPavi = {
    name: string; // Brand pavilion name
    score: number; / / weight
};
  
type SetBrandPavi = (name: string, score: number) = > void;
Copy the code

4-2. Interface Indicates the interface type

An interface is a set of abstract method declarations. Interfaces are used to name the structural types of values and define the data model.

Interface type and type alias can separate the inline type to realize type reuse. During development, you can simply extract the public file and export it through export.

Interface with optional attributes, read-only attributes, redundant attributes check, code examples are as follows:

interface Person {
    name: string;
    readonly age: number; // Optional attributehobby? :string; // Read-only attribute
}
// Check sex for extra attributes
let p: Person = { name: '阿吉'.age: 10.sex: 'male' }; 
// error TS2322: Type '{ name: string; age: number; sex: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'sex' does not exist in type 'Person'.
Copy the code

One way around redundant attribute checking is to use assertion AS. As follows:

let p: Person = { name: '阿吉'.age: 10.sex: 'male' } as Person; // success
Copy the code

There is another special way to circumvent the check: assigning variables to known objects. This method is usually used to resolve the inconsistency between parameters and arguments when a function is passed.

interface Person {
    name: string;
    readonly age: number; // Optional attributehobby? :string; // Read-only attribute
}
let clone_p = { name: '阿吉'.age: 10.sex: 'male' };
let p: Person = clone_p;

function getInfo(person: Person) {
    console.log(`${person.name} is ${person.age}years old! `);
}
getInfo(clone_p); // success
getInfo({ name: '阿吉'.age: 10.sex: 'male' }); // error
// error TS2345: Argument of type '{ name: string; age: number; sex: string; }' is not assignable to parameter of type 'Person'.
// Object literal may only specify known properties, and 'sex' does not exist in type 'Person'.
Copy the code

Interface can also define constructor types, but in practice there are few scenarios for constructor types to be defined and more to be obtained through Typeof.

export interface Person {
    name: string;
    new (name: string): Person;
}
const P: Person;
new P('GGG'); // success
new P(123); // error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Copy the code

4-3. Type Indicates the type alias

The essence of a type alias is to give a type a new name, not to create a new type.

type NameType = string;
type AgeType = number;
type SayHhhFun = () = > string;
interface Person {
    name: NameType;
    age: AgeType;
    sayHhh: SayHhhFun;
}
let person: Person = { name: 'CS agee'.age: 18.sayHhh: () = > 'Hhh' }; // success
Copy the code

The interface type and type alias can separate the inline type and reuse the type through type abstraction. During development, you can simply extract the public file and export it through export.

// GItemListProps is associated with GItemMenuProps
type GItemListProps = { // List of items
    id: string;
    name: string;
    onShowList: () = > void;
};
type GItemMenuProps = { // Menu items
    id: string;
    name: string;
    onReportClick: () = > void;
    onSetItem: () = > void;
};
// Abstraction reduces duplication, and type abstraction enables type reuse
interface BrandCard {
    id: string;
    name: string;
}
type GItemListProps = BrandCard & {
    onShowList: () = > void;
};
type GItemMenuProps = BrandCard & {
    onReportClick: () = > void;
    onSetItem: () = > void;
};
Copy the code

In development, type aliases are mainly used to address scenarios where interface types cannot be overridden, such as union types, cross types, and so on. They can also extract interface attribute types and reference themselves.

The joint type Usually use | operator connected the two types (or more). For example A | B, A or B constraints to meet at least one.

Intersecting types usually join two types (or more than one) using the & operator. Let’s say A&B, the constraints A and B satisfy at the same time.

Extension type A extends B A extends B, similar to &

type retcode = string | number; // Union type: retcode
interface BrandCard { // Type reuse BrandCard, GItemProps implements dynamic generation of types
    id: string;
    name: string;
}
// Cross type: GItemProps
type GItemProps = BrandCard & {
    onReportClick: () = > void;
    onShow: () = > void;
};
// Retrieve the interface attribute type
type BrandNameType = BrandCard['name']; // BrandNameType is the alias of type string
type DOMCalcMethod = () = >(a);// Reference itself: DOMAccessTree, TreeNode
interface DOMAccessTree { // DOMAccessTree is a recursive data structure
    [key: string]: DOMAccessTree | DOMCalcMethod;
}
type TreeNode<T> = {
    value: T; // Type aliases can also be generic
    leftNode: TreeNode<T>;
    rightNode: TreeNode<T>;
}
Copy the code

Let’s see how to delete/retain a function item of object type

// Define the function
type AnyFunc = (. args:any[]) = > any;
// Delete the function type attribute
type GetStaticKeysFor<T> = {
    [K in keyof T]: T[K] extends AnyFunc ? never : K
}[keyof T];
type GetStaticFor<T> = {
    [K in GetStaticKeysFor<T>]: T[K]
}
// Preserve the attributes of the function type
type GetFuncKeysFor<T> = {
    [K in keyof T]: T[K] extends AnyFunc ? K : never
}[keyof T];
type GetFuncFor<T> = {
    [K in GetFuncKeysFor<T>]: T[K]
}
type ABC = { a: 1, b(): string, c(): number };
// Test case
type DeleteFnExm = GetStaticFor<ABC>;
// type DeleteFnExm = {
// a: 1;
// }
type KeepFnExm = GetFuncFor<ABC>;
// type KeepFnExm = {
// b: () => string;
// c: () => number;
// }
Copy the code

Also, type can be used with Typeof. Typeof Obtains the types of variables, objects, and functions.

interface Person {
    name: string;
    age: number;
}
const str: string = 'CS';
type StrType = typeof str; // equivalent to type StrType = string;
const p: Person = { name: 'CS agee'.age: 18 };
type PType = typeof p; PType = Person;
// TypeOF can also get the types of nested objects
function getNum(x: string) :number {
    return Number(x);
}
type Func = typeof getNum; Type Func = (x: string) => number
Copy the code

Extension extension keyof: typeof operator gets the typeof a variable or object or function. Keyof gets a set of types for all keys of a type, and its return type is the union type.

In the actual practice of the project, the sample code is abstracted as follows:

interface Person {
    name: string;
    age: number;
}
const person: Person = {
    name: 'CS agee'.age: 18};// You need to implement a function that modifies the value of an attribute in the Person object
const changePersonQuestion = (key, value) = > {
    person[key] = value;
};
// Question: How to define the type of key and value?
// The solution is as follows:
const changePersonAnswer = <T extends keyof Person>(key: T, value: Person[T]): void => { person[key] = value; } // Extension: the above code does not conform to pure functions, there are hidden dependencies. Passing person as a parameter makes it a pure function with no side effects.Copy the code

4-4. interface VS type

The official document explains the difference as follows:

    1. An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
    1. An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
  1. You can name an interface in the extends or implements clause, but not in the type alias of an object type literal.
  1. An interface can have multiple merge declarations, but not type aliases for object type literals.

In human terms, the summary is:

Interface: When the interface encounters an interface with the same name, the class is automatically aggregated. Interface Specifies only the object and function types

Type: type cannot be repeated. The type declaration is not restricted;

Also, both allow extending extends, but the syntax is different.

interface PersonA {
    name: string;
}
interface PersonA {
    age: number;
}
let p: PersonA = { name: 'CS agee'.age: 18 }; // The interface with the same name is automatically aggregated

type PersonB = { name: string };
/** type Unrepeatable error TS2300: Duplicate Identifier 'PersonB'. */
// type PersonB = { age: number };

// interface extends interface
interface PersonC { 
    name: string;
};
interface PersonCC extends PersonC {
    age: number;
}
// type extends type
type PersonD = { 
    name: string;
};
type PersonDD = PersonD & { age: number }
// interface extends type
type PersonE = { 
    name: string;
};
interface PersonEE extends PersonE {
    age: number;
};
// type extends interface
interface PersonF { 
    name: string;
};
type PersonFF = PersonF & { age: number }
Copy the code

5. Function types

5-1. Definition, Default, is

Type is usually used to define a function type. The return value type of the function can be inferred and can be default. Void is used when the function returns no value.

Function return value type inference + generic ==> complex type inference

type AddFuncType = (a: number, b: number) = > number; // TypeScript function type definitions
const add: AddFuncType = (a, b) = > a + b;
const addMissing = (a: number, b: number) = > a + b; // const add: (a: number, b: number) => number

interface Person {
    say: () = > void;
    eat: (a: string) = > void;
}
const person: Person = {
    say: () = > console.log('hello'),
    eat: (fruit: string) = > console.log(`eat a ${fruit}`),}Copy the code

Generator functions in ES6 also provide Generator constraints in TS.

type AnyYieldType<T> = T;
type AnyReturnType<T> = T;
type AnyNextType = number;

function* HWG() :Generator<AnyYieldType<string>, AnyReturnType<boolean>, AnyNextType> 
{
    const resVal = yield 'Hello'; // const resVal: number
    console.log(resVal);
    yield 'World';
    return true;
}
Copy the code

Function parameters can also be constrained by the type predicate is, as follows:

Type predicate functions are usually used for type guards. The reason for type guarding is that TS requires it to be clear whether an attribute of the object exists. It is not expected to get undefined if the attribute does not exist.

function isString(s: any) :s is string { // If true, s is a string
    return typeof s === 'string';
}
let test = ' ';
if (isString(test)) {
    console.log('this is string type');
}
Copy the code

We can abstract a type guard function, judgeType.

Nature: Narrowing the guarded type to the type specified by IS when the type guard returns true.

function judgeType<T> (Target: any, TargetProperty: keyof T) :Target is T {
    return (Target asT)[TargetProperty] ! = =undefined;
}

class Animal {}

class TeacupDog extends Animal {
    constructor(){
        super(a); }wang() {
        console.log('TeacupDog wang'); }}class GoldenDog extends Animal {
    constructor(){
        super(a); }wang() {
        console.log('GoldenDog wang'); }}class Cat extends Animal {
    constructor(){
        super(a); }miao() {
        console.log('Cat miao'); }}const teacupDog = new TeacupDog(); / / teacup dogs
const goldenDog = new GoldenDog(); / / golden retriever
const cat = new Cat(); / / the cat

if (judgeType(teacupDog, 'wang')) {
    teacupDog.wang();
}
if (judgeType(cat, 'miao')) {
    cat.miao();
}
Copy the code

5-2. Optional parameters and default parameters

Function definition collocation? Optional arguments can be passed, which must be placed at the end of the argument. TS will also infer the types of function arguments from the types of default arguments.

The default parameter type of a function must be a subtype of the parameter type.

function operate(a: number | string, b = 2, c? :string) { // (parameter) b: number
    if (c === 'add') {
        return Number(a) + b;
    } else {
        return Number(a) - b; }}Copy the code

5-3. Remaining parameters

TS also supports type definitions for the remaining parameters.

function add(. arg: (number | string) []) :number {
    return arg.reduce<number> ((a, b) = > a + Number(b), 0);
}
add(0.'1'.2); / / 3
Copy the code

5-4.this

TS can declare the object referred to by this in the first argument to the function.

When TS is translated to JS, the pseudoparameter this is erased.

function sayName(this: Window) {
    console.log(this.name);
}
window.sayName = sayName;
const person = {
    name: 'the name of the person',
    sayName
};
window.sayName();
person.sayName(); // error TS2684: The 'this' context of type '{ name: string; sayName: (this: Window) => void; }' is not assignable to method's 'this' of type 'Window'.
Copy the code

We can also explicitly constrain this in class. Example code is as follows:

interface Horn { / / the speaker
    amplifyVolume(sound: (this: void) = > void) :void; // Amplify the sound
}

class Person {
    private name: string;
    constructor(name: string) {
        this.name = name;
    }
    say(this: Person) {
        console.log('hello');
    }
    // To start chain-style writing, think of Person as a chain-calling library
    eat(): this { // Use this to express the return value type succinctly, rather than as Person
        console.log(`The ${this.name} eat dinner`);
        return this;
    }
    sleep(): this {
        console.log(`The ${this.name} Slept for a day`);
        return this;
    }
    wakeup(): Person {
        console.log(`The ${this.name} wake up`);
        return this; }}const person = new Person('阿吉');
const horn: Horn = {
    amplifyVolume(fn) {
        fn();
    }
}
person.eat() // Aji eat dinner
    .sleep() // Aji Slept for a day
    .wakeup() // Aji wake up
    .say(); // hello

horn.amplifyVolume(person.say); // error TS2345: Argument of type '(this: Person) => void' is not assignable to parameter of type '(this: void) => void'.
Copy the code

5-5. Function overloading

Polymorphism: The same behavior has different effects on different objects.

JS is a dynamic language that naturally supports polymorphism.

Overloading: Multiple functions with the same name have different parameter types.

There is no overloading in JS. JS does not restrict the type of the passed argument. For functions with the same name, the function declared later overwrites the one declared earlier.

In TypeScript, each definition in the list of function overloads must be a subset of the function implementation. TypeScript looks up the list of function overloads that match the input type and uses the first matching overload definition in preference.

interface A {
    num: number;
}
interface B extends A {
    count: string;
}
// Function overload list order: AB
function transform(a: A) :string;
function transform(a: B) :number;
// Function implementation
function transform(a: A | B) :any {}

// Test case
let a = { num: 1 };
let b = { num: 1.count: ' ' };
// select * from type A. B can look for either type A or type B. The first match in the current function's reload list is A, so both of the following are strings.
let str = transform(a as A); // string
let num = transform(b as B); // string

* function transform(a: B): number; * function transform(a: A): string; * let STR = transform(a as a); // string * let num = transform(b as B); // number */
Copy the code

6. Literal types

6-1. Definitions

In TS, a literal representation of a type is a literal type.

There are three categories: string literals, numeric literals, and Boolean literals. The following are their respective uses.

let strLiteralType: 'string' = 'string'; // let strLiteralType: "string"
let numLiteralType: 1 = 1; // let numLiteralType: 1
let boolLiteralType: true = true; // let boolLiteralType: true
Copy the code

String literals are usually composed of multiple union types that describe the type of an explicit member. Numeric literals and Boolean literals are used the same way.

When const defines a constant’s type default, the type is the assigned literal type.

When let defines a constant’s type default, its type is the parent of the assignment literal type. In TS, the design for converting Literal quantum types into parent types is called Literal design KP. Similarly, there is a role for Type choi or Type Narrowing.

type OType = 'add' | 'sub';
function operation(a: number, b: number, otype: OType) :number {
    if (otype === 'add') {
        return a + b;
    } else if (otype === 'sub') {
        return a - b;
    } else {
        return 0;
    }
}
operation(2.4.'add'); // success
operation(2.4.'ADD'); // error TS2345: Argument of type '"ADD"' is not assignable to parameter of type 'OType'.

// Const assignment literal type
const strC = 'strC'; // const strC: "strC"
const boolC = false; // const boolC: false
const numC = 1; // const numC: 1

// Let assigns the parent type of the literal type
// Literal design for the process
let strCC = 'strCC'; // let strCC: string
let boolCC = false; // let boolCC: boolean
let numCC = 1; // let numCC: number
Copy the code

6-2.Literal Widening

Definition: if a variable, function parameter, or object writable property declared with let or var does not explicitly declare its type, but deduces its type as a literal type, it is automatically widened to its parent type. This design is called Literal design.

An object property can be narrowed as const to make it read-only.

let strCC = 'strCC'; // let strCC: string
// The type of the variable strCC is a literal type whose value is strCC. The type of the variable strCC is broadened to string
function add(a = 1, b = 2) :number {
    return a + b;
} // function add(a? : number, b? : number): number
// The type of variable a is a literal type with a value of 1, and the type of variable a is widened to number
let obj = {
    name: 'CS agee'.// (property) name: string
    age: 18 as const.// (property) age: 18
};

/ / contrast
const strCCC = 'strCCC'; // const strCCC: "strCCC"
let a = strCCC; // let a: string
const strCCCC: 'strCCCC' = 'strCCCC'; // const strCCCC: "strCCCC"
let b = strCCCC; // let b: "strCCCC"
Copy the code

6-3.Type Widening

Definition: if a variable declared with let or var has no explicit type and its value is null or undefined, its type is widened to any. This design is called Type Korean.

let x; // let x: any
let y = null; // let y: any
let z = undefined; // let z: any

/ / contrast
const a = null; // const a: null
let b = a; // let b: null Because the type of A is null.
function add (a = null) { // (parameter) a: null
    return a;
}
Copy the code

6-4.Type Narrowing

Definition: Narrowing the type of a variable from a broad set to a smaller set.

This feature is most commonly used for type guarding.

// example 1
function getSomething(a: number | string | null) :number {
    if (typeof a === 'number') {
        return a; // (parameter) a: number
    } else if (typeof a === 'string') {
        return Number(a); // (parameter) a: string
    } else {
        return Number(a); // (parameter) a: null}}// example 2
let result: unknown;
if (typeof result === 'number') {
    result.toFixed(); // Cursor over result will prompt: let result: number
}
// example 3
type SrtType = string | 'str'; // type SrtType = string
Copy the code

Type narrowing can be avoided with & {}.

type Str = 's' | 'str'; // type Str = "s" | "str"
type StrTypeSmall = 's' | 'str' | string ; // type StrType = string
type StrType =  's' | 'str' | string & {}; // type StrType = "str" | (string & {})
Copy the code

6-5. Summary

For declared simple type literals, let broadens the type and const Narrows it.

Properties of declared objects (whether let or const) are automatically inferred as types, which can be narrowed down to read-only as const.

// Simple type literals
let a = 1; // let a: number
const b = 1; // const b: 1
/ / object
let obj = {
    name: 'CS agee'.// (property) name: string
    age: 18 as const.// (property) age: 18
};
Copy the code

7. The class type

7-1.extends

Subclasses inherit from their parent class, and if they have constructor, they must call the super method there.

Super calls the superclass constructor method.

class Animal {}
class Dog extends Animal{}
class Snake extends Animal{
    constructor() {
        super();
    }
}
class Cat extends Animal{
    constructor() {} // error TS2377: Constructors for derived classes must contain a 'super' call.
}
Copy the code

7-2. Modifier

Public: A public property or method that can be accessed and called arbitrarily. In the absence of modifiers, the default is public.

Private: a property or method that is private (only accessible and called by the same class)

Protected: A protected property or method (accessible only by itself and its subclasses)

Readonly: Read-only modifier that cannot be changed.

class Father {
    public name: string;
    public readonly idcard = 'father';
    private address: string = 'dad';
    protected secret: string = 'as fater, I also like playing games';
    constructor(name: string) {
        this.name = name;
        this.idcard;
        this.address;
        this.secret;
    }
    callme() {
        console.log(`I am father, call me The ${this.address} or The ${this.idcard}`);
    }
    tellSecret() {
        console.log(this.secret); }}class Son extends Father {
    static displayName = 'Properties of the class itself';
    constructor(name: string) {
        super(name);
    }
    getFartherSecret() {
        console.log(this.secret); }}let father = new Father('阿吉');
let son = new Son('Little Aggie');

console.log(father.name);
console.log(son.name);

father.name = 'father';
console.log(father.name);

son.getFartherSecret();

console.log(Son.displayName);

father.callme();
father.idcard = 'father'; // error TS2540: Cannot assign to 'idcard' because it is a read-only property.
Copy the code

The code above refers to static static properties, as well as static static methods. Properties and methods that are not static are initialized only when the class is instantiated. Static Static properties and methods exist only on classes, not on instances of classes. We can access static properties and methods directly through the class.

Static Static attribute Description: Share attributes to improve performance. Extract class-related constants, properties and methods that do not depend on this, and make them static.

If static methods rely on this, you must explicitly annotate this. This in non-static methods points to class instances by default.

7-3. An abstract class

Abstract class: a class that cannot be instantiated and can only be inherited by subclasses.

Note: When declaring a class, you also declare an interface type named the class name. Members other than the constructor constructor are members of the interface type. Display annotation a variable of type of this class. An error is reported if these members are missing.

Abstract classes are implemented in two ways :(1) define an abstract class, class implements the abstract keyword constraint, and (2) use an interface, interface implements the keyword constraint.

Abstract classes can not only define the types of class members, but also share non-abstract properties and methods. Interfaces can only define the types of class members.

Subclasses that inherit from abstract classes must implement abstract properties and methods, or an error will be reported.

We mention abstraction here, and you can take a look at the Abstract Factory pattern from the previous article D 004.

Let’s start with using abstract class definitions

abstract class Parent {
    abstract name: string;
    abstract sonName: string;
    abstract getSon(): string;
    eat(): void {
        console.log('Father eat');
    }
    sayHello(): void {
        console.log('Father say hello'); }}/* sonName, sonName, getSon(); /* sonName, sonName, sonson (); /* sonName, sonName, sonson (); * Eat () in Father, sayHello(), * If not defined in the subclass instance, get the non-abstract method in Fater directly. * /
class Dad extends Parent {
    name: string;
    sonName: string;
    constructor(name: string, sonName: string) {
        super(a);this.name = name;
        this.sonName = sonName;
    }
    getSon(): string {
        return this.sonName;
    }
    sayHello(): void {
        console.log('dad say hello'); }}const dad = new Dad('阿吉'.'Little Aggie');
console.log(dad.getSon()); // Little Aji
dad.sayHello(); // dad say hello
dad.eat(); // Father eat
Copy the code

Let’s look at using the interface definition

interface Parent {
    name: string;
    sonName: string;
    getSon:() = > string;
}
/* sonName, sonName, getSon(); /* sonName, sonName, sonson (); /* sonName, sonName, sonson (); * Eat () in Father, sayHello(), * If not defined in the subclass instance, get the non-abstract method in Fater directly. * /
class Dad implements Parent {
    name: string;
    sonName: string;
    constructor(name: string, sonName: string) {
        this.name = name;
        this.sonName = sonName;
    }
    getSon(): string {
        return this.sonName;
    }
    sayHello(): void {
        console.log('dad say hello'); }}const dad = new Dad('阿吉'.'Little Aggie');
console.log(dad.getSon()); // Little Aji
dad.sayHello(); // dad say hello
// dad.eat(); Interfaces cannot share properties and methods, only types
Copy the code

8. Union types

Definition: its type is one of multiple atomic types, connect multiple use | atom type.

function getSomething(a: number | string | null) :number {
    if (typeof a === 'number') {
        return a; // (parameter) a: number
    } else if (typeof a === 'string') {
        return Number(a); // (parameter) a: string
    } else {
        return Number(a); // (parameter) a: null}}Copy the code

Now, one interesting thing to review is the narrowing of types. But again, we have tools & {} to control type narrowing.

type Str = 's' | 'str'; // type Str = "s" | "str"
type StrTypeSmall = 's' | 'str' | string ; // type StrType = string
type StrType =  's' | 'str' | string & {}; // type StrType = "str" | (string & {})
Copy the code

9. Cross types

Definition: To combine multiple types into one type, concatenate multiple types using &.

type PersonD = { 
    name: string;
};
type PersonDD = PersonD & { age: number }
Copy the code

10. Generics

Definition: Parameterize a type and then perform some specific processing.

Enumeration types do not support generics

interface Person {
    name: string;
    age: number;
}
const person: Person = {
    name: 'CS agee'.age: 18};// You need to implement a function that modifies the value of an attribute in the Person object
const changePersonQuestion = (key, value) = > {
    person[key] = value;
};
// Question: How to define the type of key and value?
// The solution is as follows:
const changePersonAnswer = <T extends keyof Person>(key: T, value: Person[T]): void => {
    person[key] = value;
}
Copy the code

Conditional assignment type: In the case of conditional type determination, if the input is a union type, it is broken down into atomic types for type operation.

To put it simply, generics + extends + ternary operator = conditional allocation

type StrNumType = string | number;
type StrNumJudgment<T> = T extends StrNumType? T : null;
type ComeInType = string | boolean;
type OutType = StrNumJudgment<ComeInType>; // type OutType = string | null
type OutWithoutGeneric = ComeInType extends StrNumType ? ComeInType : null; // type OutWithoutGeneric = null
// OutType trigger criteria, OutWithoutGeneric does not trigger criteria
Copy the code

— — — — — — — — — — — — — — — —

Transfer ===> Public number: CS Aji