This is the sixth day of my participation in Gwen Challenge


High-level types

Cross type

Cross typing is merging multiple types into one type. We can superimpose existing types into a single type that contains all the required features of the type. For example, Person & Serializable & Loggable are both Person and Serializable and Loggable. This means that an object of this type has members of all three types.

We mostly see the use of cross types in mixins or other places that don’t fit into a typical object-oriented model. (There are plenty of occasions when this happens in JavaScript!) Here’s a simple example of how to create mixins:

function extend<T.U> (first: T, second: U) :T & U { // T & U is the same as the union
    letresult = <T & U>{}; for (let id in first) { (<any>result)[id] = (<any>first)[id]; } for (let id in second) { if (! result.hasOwnProperty(id)) { (<any>result)[id] = (<any>second)[id]; } } return result; } class Person { constructor(public name: string) { } } interface Loggable { log(): void; } class ConsoleLogger implements Loggable { log() { // ... } } var jim = extend(new Person("Jim"), new ConsoleLogger()); var n = jim.name; jim.log();Copy the code

The joint type

Union types are closely related to cross types, but their usage is completely different. Occasionally you’ll run into a code base that wants to pass in a number or string argument. For example, the following function:

function padLeft(value: string, padding: any) {
    if (typeof padding === "number") {
        return Array(padding + 1).join("") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'. `);
}

padLeft("Hello world".4); // returns " Hello world"
Copy the code

So the padding here is any, but we really want number or string, and if it’s not number or string, ts won’t get an error. We can use the joint type, number | string

If a value is a union type, we can access only the members that are common to all types of that union type.

interface Bird {
    fly();
    layEggs();
}

interface Fish {
    swim();
    layEggs();
}

function getSmallPet() :Fish | Bird {
    // ...
}

let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim();    // errors
Copy the code

Is the type of pet Fish | Bird, layEggs is joint method, the access is ok, and swim is exclusive to Fish, if the type is a Bird, no swim method, will be an error.

Type protection and type differentiation

Union types are suitable for cases where values can be of different types. But what happens when we want to know for sure if it’s Fish? A common way to distinguish between two possible values in JavaScript is to check if the member exists. As mentioned earlier, we can only access co-owned members of the union type.

To make this code work, we use type assertions:

let pet = getSmallPet();

if ((<Fish>pet).swim) {
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}
Copy the code

User-defined type protection

TypeScript’s type protection makes this a reality. Type protection is expressions that are checked at run time to make sure the type is in a scope. To define a type protection, we simply define a function whose return value is a type predicate:

function isFish(pet: Fish | Bird) :pet is Fish {
    return(<Fish>pet).swim ! = =undefined;
}
Copy the code

In this case, pet is Fish is the type predicate. The predicate is of the form parameterName is Type. ParameterName must be a parameterName from the current function signature.

Whenever isFish is called with some variable, TypeScript reduces the variable to that specific type, as long as the type is compatible with the variable’s original type.

Typeof type protection

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join("") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'. `);
}
Copy the code

Only two forms of these Typeof type protections can be recognized: Typeof V === “typename” and typeof V! “Typename”, “typename” must be “number”, “string”, “Boolean” or “symbol”. But TypeScript doesn’t prevent you from comparing with other strings, and the language doesn’t recognize those expressions as type-protected.

A type that can be null

TypeScript has two special types, null and undefined, which have values null and undefined, respectively. By default, the type checker assumes that null and undefined can be assigned to any type. Null and undefined are valid values for all other types. This also means that you can’t prevent them from being assigned to other types, even if you wanted to.

let s = "foo";
s = null; // error, 'null' cannot be assigned to 'string'
let sn: string | null = "bar";
sn = null; / / can

sn = undefined; / / the error, "undefined" cannot be assigned to the 'string | null'

Copy the code

Optional parameters and optional properties

Used – strictNullChecks tsconfig. Json, optional parameters can be automatically add | undefined:

function f(x: number, y? :number) {
    return x + (y || 0);
}
f(1.2);
f(1);
f(1.undefined);
f(1.null); // error, 'null' is not assignable to 'number | undefined'
Copy the code

Type protection and type assertion

Since null-capable types are implemented through union types, you need to use type protection to remove NULls. Fortunately this is consistent with code written in JavaScript:

function f(sn: string | null) :string {
    if (sn == null) {
        return "default";
    }
    else {
        returnsn; }}Copy the code

Here null is obviously removed, and you can also use the short-circuit operator:

function f(sn: string | null) :string {
    return sn || "default";
}
Copy the code

If the compiler is unable to remove null or undefined, you can do so manually using type assertions. Syntax is added! The suffix: identifier. Remove null and undefined from the identifier type:

letobj = {} obj! .nameCopy the code

Type the alias

A type alias gives a type a new name. Type aliases are sometimes similar to interfaces, but can work with primitive values, union types, tuples, and any other type you need to write by hand.

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

String literal type

type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if (easing === "ease-in") {
            // ...
        }
        else if (easing === "ease-out") {}else if (easing === "ease-in-out") {}else {
            // error! should not pass null or undefined.}}}let button = new UIElement();
button.animate(0.0."ease-in");
button.animate(0.0."uneasy"); 
Copy the code

Record

In TS, if you want to define the key and value type of an object, what do you do? This time you need to use the TS Record.

interface Person { name: string; }

type Key = “p1” | “p2” | “p3”;

const person: Record<Key, Person> = { p1: { name: “zs” }, p2: { name: “ls” }, p3: { name: “ww” }, }; The generic type behind Record is the type of the object’s key and value.

ReturnType

ReturnType<T> — Gets the return value type of the function.

type T10 = ReturnType<() = > string>;  // string
type T11 = ReturnType<(s: string) = > void>;  // void
type T12 = ReturnType<(<T>() = > T)>;  / / {}
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>;  // number[]
type T15 = ReturnType<any>;  // any
type T16 = ReturnType<never>;  // any
type T17 = ReturnType<string>;  // Error
type T18 = ReturnType<Function>;  // Error

Copy the code