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