The base type

Contains basic types: number, Boolean, string, null, undefined

Complex types: Array

or string[], tuple, enum, object

Special types: Any, void, Never(types of values that Never exist, eg: function containing throw “)

ES6: Symbol

Advanced types: union types (tuple introduces the concept of union types), crossover types, default types. Reference: advancedTypes

Type:

// base
let x: null = null;
let unusable: void = undefined;

function warnUser(): void {

}

// any
let x: Any = 10;
x = true;
let list: any[] = [1, true, "free"];Copy the code

tuple

// Declare a tuple type let x: [string, number]; // Initialize it x = ['hello', 10]; // OK // Initialize it incorrectly x = [10, 'hello']; // Error // error: Type '[string, number, number]' is not assignable to Type '[string, number]'. The assignment type is the union type at declaration time. x = [10, 'hello', 10]; x[3] = 'world'; / / OK, strings can be assigned to (string | number) type 【 this is joint type.  console.log(x[5].toString()); // OK, 'string' and 'number' both have toString. X [6] = true; / / Error, Boolean not (string | number) typeCopy the code

Union type & cross type

let x: [string | number] = ['2']; x.push(10); let x: string | number = '2'; / / type "true" can not be assigned to a type of "string | number". x = true; // Cross type, cross type has no meaning for base type. AdvancedTypes. Md let x: string & number = undefined; let y: string & number = null; // The default is undefuned until a value is defined. These two special values can be assigned. x = null; y = undefined;Copy the code

enum: enum desc

  • TypeScript supports numeric and string-based enumerations.
  • String enumerations have the ability to infer values from enumerations, but string enumerations have no self-growth behavior, and string enumerations can be serialized well
  • Heterogeneous enumeration: Allows a set of enumeration values to contain different types.
// String enumeration: User-defined initial value enum SEASON {spring = 'spring', summer = 'summer', autumu = 'autumn', winter = 'winter'}; Let base: number = 10; let base: number = 10; Enum P {y = '1', x = ++ base, // error Computed values are not allowed in enumerations containing string values. Z = base, // error Computed values are not allowed in enumerations containing string value members. } / / heterogeneous enumeration: allows but try to avoid enum BooleanLikeHeterogeneousEnum {No = 0, Yes = "Yes",} / / the following part more troublesome, suggest the official documents. // Numeric enumerations: TSC tries to infer the values of other enumerations. The inference rules are as follows: // The first member of the enumeration has no initializer, in which case it is given the value 0: // There is no initializer and its preceding enumerator is a numeric constant. In this case, the value of the current enumerator is incremented by the value of its previous enumerator. // But if the value you explicitly give cannot be inferred, the rest of the assignment needs to be done manually. For enumeration values of type number, an index with the corresponding index is generated as follows: up: 1, 1: Enum Direction {up = 1, Down, Left, Right} console.log(Direction); // { 1: "Up", 2: "Down", 3: "Left", 4: "Right", Up: 1, Down: 2, Left: 3, Right: 4 } enum T { a = 1, b, c = 10, d = 100, f } // { 1: "a", 2: "b", 10: "c", 100: "d", 101: "f", a: 1, b: 2, c: 10, d: F: 101} // let base = 10; enum P { x = ++ base, y; } // The type of the joint enumeration with the enumeration member, the runtime enumeration, is recommended to refer to the official documentation. Enum {A} // {0: "A", A: 0} let A = enum. let nameOfA = Enum[a]; Let {x, y} = P console.log(x === y); let {x, y} = P console.log(x === y); // error: This condition will always return 'false' since the types 'P.x' and 'P.y' have no overlap. Const enum enum {A = 1, B = A * 2} const enum P {A = 1, B, C = 2} // {1: "A", 2: "C", A: 1, B: 2, C: 2 2}Copy the code

Never

// a function that returns never must have an unreachable endpoint: function infiniteLoop(): Function error(message: string): never {throw new error(message); function error(message: string): throw new error(message); }Copy the code

Symbol

  • Symbols is immutable and unique
  • Like strings, Symbols can also be used as keys for object properties.
  • API reference for Symbol
let sym2 = Symbol("key"); let sym3 = Symbol("key"); sym2 === sym3; // the symbols are unique let sym = Symbol(); let obj = { [sym]: "value" }; console.log(obj[sym]); // "value"Copy the code

Types of assertions

Sometimes you’ll find that you know more about a value than TypeScript does. Usually this happens when you clearly know that an entity has a more exact type than its existing type. It simply tells the compiler that this is the type and passes the check.

Often seen in crossover types.

Eg1: A common scenario is that an interface has a feature called extra attribute checking that does not allow fields to be passed in outside the interface convention (the following OTH field will not compile). . This is where type assertions are needed

interface SquareConfig { color? : string; width? : number; } function createSquare({ color = 'white', width = 0 }: SquareConfig): { color: string; area: number } { return { area: width ** 2, color }; } // error: 'oth' not expected in type 'SquareConfig' let mySquare = createSquare({ color: "red", width: 100, oth: true }); // right mySquare = createSquare(<SquareConfig>{ color: "red", width: 100, oth: true }); // or mySquare = createSquare({ color: "red", width: 100, oth: true } as SquareConfig);Copy the code

Eg2: Function default type assertion. The safest way to destruct + default values is {a = true, b = 10} = {} in TS does not pass compilation and requires type assertion

interface SquareConfig { color: string; width: number; } // error cannot assign type '{}' to type 'SquareConfig'. The attribute "color" is missing from type "{}". // function createSquare({ color = 'white', width = 0 }: SquareConfig = {}): void { // right function createSquare({ color = 'white', width = 0 }: SquareConfig = <SquareConfig>{}): void { // console.log(color, width); } createSquare();Copy the code

Type declaration

Es6 let and const add level block scope, curly braces as level block, to make up for var holes.


Type inference:Type inference doc

In some cases where the type is not explicitly specified, type inference helps provide the type, and if you rely on this feature, it’s pretty much the same as writing without TS


Deconstruct & unfold/converge

The principle of deconstruction is pattern matching, no matter how complex it is, just find the right pattern.

/ / complex deconstruction const {a: [{a: name}, a2], b: {c, d: {e, f}}} = {a: [{2} a:, '5'], b: {10, c: d: {e: true, f: 'last' } } }; Function names({a, b} : {a: string, b: number} = {a: '1', b: Let defaults = {food: "spicy", price: "spicy"; , ambiance: "noisy" }; let search = { ... defaults, food: "rich" };Copy the code

Type Compatibility

Type compatibility in TypeScript is based on structural subtypes. A structural subtype is a way to describe a type using only its members.

This is in contrast to the nominal type (C# or Java). In a type system based on nominal types, the compatibility or equivalence of data types is determined by an explicit declaration and/or the name of the type. This is different from the structural type system, which is a composition structure based on type and does not require explicit declaration.

TypeScript’s structural subtypes are designed based on how JavaScript code is typically written. Because anonymous objects, such as function expressions and object literals, are widely used in JavaScript, it is better to use the structural type system to describe these types than the nominal type system.

TypeScript’s type system allows certain operations that are not secure at compile time

Basic rule: inclusion.

If x is to be compatible with y, then y has at least the same properties as X, that is, it must be a superset, and the other way around is not true.

Only members of the target type (with the same property) are checked for compatibility one by one. The comparison is performed recursively, checking each member and its children.

interface Named {
    name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;
// error
// y = x;Copy the code

class

Classes have types that have static parts and instance parts. When objects of two class types are compared, only instance members are compared. Static members and constructors are outside the scope of the comparison.

A private member of the class

Private members affect compatibility judgment. When an instance of a class is used to check compatibility, if the target type contains a private member, the source type must contain that private member from the same class. This allows subclasses to assign to their parents, but not to other classes of the same type.

Subtypes and assignments

So far, we have used compatibility, which is not defined in the language specification. In TypeScript, there are two types of compatibility: subtypes and assignments. They differ in that assignment extends subtype compatibility, allowing any to be assigned to or from any values and allowing numbers to be assigned to enumerated types or enumerated types to numbers.

Different parts of the language use each of these mechanisms. In fact, type compatibility is controlled by assignment compatibility, even in the implements and extends statements.


A statement to merge

Amazing operation: “declaration merge” is when the compiler merges two separate declarations for the same name into a single declaration. The combined declaration has both the features of the original two declarations. Any number of declarations can be combined; Not limited to two declarations.

interface Box { height: number; width: number; } interface Box { scale: number; // error: identifier "scale" repeated // scale: number; } // three box ? fuck class Box { length: number; // error: Non-function members of the interface should be unique. If they are not unique, they must be of the same type. // If two interfaces declare non-function members of the same name and their types are different, the compiler will report an error // width: string; // Allow attributes of the same name to exist in different declaration bodies. width: number; } // Identifier "Box" is repeated. // let boxErr: Box = {height: 5, width: 6, scale: 10}; let box: Box = { height: 5, width: 6, scale: 10, length: 7 }; console.log(box);Copy the code

Declarations in TypeScript create one of three entities: a namespace, a type, or a value.

  • The declaration to create a namespace creates a new namespace that contains the names used with (.) Symbol to use when accessing.
  • The declaration for creating a type is to use the declared model to create a type and bind it to the given name.
  • The statement that creates the value creates the value you see in the JavaScript output.

  1. Interface declaration merge

Overall, there are no scenarios where this feature is appropriate, both in terms of extension and code organization, which are awkward, and there are better solutions than declaring merge. To be found

Non-function members of an interface should be unique. If they are not unique, they must be of the same type, as in the code above.

For function members, each function declaration with the same name is treated as an overload of that function. Note that when interface A is merged with subsequent interface A, the subsequent interface has higher priority.

interface Cloner { clone(animal: Animal): Animal; } interface Cloner { clone(animal: Sheep): Sheep; } interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat; } // These three interfaces are combined into one declaration: note that the declaration order remains the same in each group of interfaces, but the order between the groups is that later interface overloads appear first. interface Cloner { clone(animal: Dog): Dog; clone(animal: Cat): Cat; clone(animal: Sheep): Sheep; clone(animal: Animal): Animal; }Copy the code

One exception to this rule is when a special function signature occurs. If one of the parameters in the signature is of a single string literal type (i.e., not a combined type of string literals), it will be promoted to the top of the overload list…..

  1. Namespaces merge with classes and functions and enumerated types and so on.

To be honest, it doesn’t feel very useful, either from a design point of view or from a rational point of view, that there’s no reason to write cohesive designs that are scattered but merged in use. For this part, please refer to the original text:

Declaration merge, module expansion

  1. Merge namespaces and classes: Implement partial patterns

    • The inner class:
    class Album {
        label: Album.AlbumLabel;
    }
    namespace Album {
        export class AlbumLabel { }
    }Copy the code
    • Member extension: It is also common to create a function and extend it later to add attributes
    function buildLabel(name: string): string {
        return buildLabel.prefix + name + buildLabel.suffix;
    }
    
    namespace buildLabel {
        export let suffix = "";
        export let prefix = "Hello, ";
    }
    
    console.log(buildLabel("Sam Smith"));Copy the code

    The enumeration in the same way

  2. Illegal merger

TypeScript doesn’t allow all merges. Currently, classes cannot be merged with other classes or variables. To learn how to mimic class merges, see TypeScript mixin.

  1. The global extension
// observable.ts export class Observable<T> { // ... still no implementation ... } declare global { interface Array<T> { toObservable(): Observable<T>; }} / / equivalent Array. Prototype. ToObservable = function () {/ /... }Copy the code