current position:Home>Typescript type compatibility learning

Typescript type compatibility learning

2022-04-29 18:54:34Hali

This article has participated in 「 New people's creation ceremony 」 Activities , Start the road of nuggets creation together .

Today is my study Typescript Of the 5 God , Learning today TypeScript Type compatibility ,TypeScript Type compatibility in is based on subtypes , A structure type is a way to describe a type using only its members , It and name (nominal) Type in contrast , In the type system of nominal type , The compatibility or equivalence of data types is through explicit declarations and / Or the name of the type .

// Defining interfaces 
interface Animal {
    name: string;
}
// Define a class 
class Person {
    name: string;
}
let p: animal;
//typescrtipt  Allow classes that do not inherit interfaces  , Assign a class instance to an interface 
p = new Person();
 Copy code 

stay C# or Java in , This code will report an error , because Person Class does not specify that it implements Animal Interface ,TypeScript The structural subtype of is based on JavaScript The typical way of writing code . because JavaScript Anonymous objects are widely used in , For example, function expressions and object literals , So it's better to use a structural type system to describe these types than a nominal type system .

TypeScript The type system allows some type unsafe conversions at compile time . stay TypeScript If x To be compatible with y, that y At least with x Same property .

interface Animal {
    name: string;
}
let x: Animal;
// Definition y
let y = { name: 'Alice', location: 'Seattle' };
// Can be y Assign a value to x, because x Yes name attribute 
x = y;
 Copy code 

Check here y Whether it can be assigned to x, Compiler check x Properties in , See if you can y The corresponding attribute is also found in . y Must contain a name that is name Of string Type members ,y Meet the conditions .

function HELLO(n: Animal) {
    console.log('Hello, ' + n.name);
}
HELLO(y); // OK
 Copy code 

y There's an extra one location attribute , But it will not cause errors . Only target types Animal Will be recursively checked one by one for compatibility .

2. Compare two functions

It's easy to understand when comparing primitive types with object types , The question is how to judge whether two functions are compatible , Let's start with two simple functions , They are only slightly different from the parameter list :

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
​
y = x; // OK
x = y; // Error
 Copy code 

To see x Whether it can be assigned to y, First look at their parameter list . x Each parameter of must be able to be in y Find the parameter of corresponding type in . Note that it doesn't matter whether the parameter names are the same or not , Just look at their type . here ,x Each parameter of is in y The corresponding parameters can be found in , So assignment is allowed .

The second assignment is wrong , because y There is a second required parameter , however x did not , So assignment is not allowed .

let items = [1, 2, 3];
​
// Don't force these extra arguments
items.forEach((item, index, array) => console.log(item));
​
// Should be OK!
items.forEach((item) => console.log(item));
 Copy code 

Let's see how to handle the return value type , Create two functions with only different return value types :

let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
​
x = y; // OK
y = x; // Error, because x() lacks a location property
 Copy code 

The return value type of the source function must be a subtype of the return value type of the target function .

3. Function parameter bidirectional covariance

When comparing function parameter types , Only when the parameters of the source function can be assigned to the target function or vice versa can the assignment succeed . Because the caller may have passed in a function with more precise type information , But the incoming function is called with less precise type information . This rarely makes mistakes , And can achieve a lot JavaScript Common patterns in .

enum EventType { Mouse, Keyboard }
​
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
​
function listenEvent(eventType: EventType, handler: (n: Event) => void) {}
​
// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
​
// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
​
// Still disallowed (clear error). Type safety enforced for wholly incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e));
 Copy code 
4. Optional parameters and remaining parameters

When comparing function compatibility , Optional and mandatory parameters are interchangeable . There are additional optional parameters on the source type, which is not an error , The optional parameters of the target type have no corresponding parameters in the source type, and it is not an error , When a function has remaining parameters , It is treated as an infinite number of optional parameters .

This is unstable for type systems , But from a runtime perspective , Optional parameters are generally not mandatory , Because for most functions, it is equivalent to passing some undefinded.

Common functions receive a callback function and call with parameters that are predictable to the programmer but uncertain to the type system :

function invokeLater(args: any[], callback: (...args: any[]) => void) {}
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
 Copy code 

For functions with overloads , Each overload of the source function must find the corresponding function signature on the target function . This ensures that the target function can be called wherever the source function can be called .

Enumeration types are compatible with number types , And the number type is compatible with the enumeration type . Different enumeration types are not compatible . such as ,

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
​
let status = Status.Ready;
status = Color.Green;  
 Copy code 

Class and object literals and interfaces are similar , But it's a little different : Class has types of static and instance parts . When comparing objects of two class types , Only the members of the instance will be compared . Static members and constructors are not in the scope of comparison .

class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { }
}
​
class Size {
    feet: number;
    constructor(numFeet: number) { }
}
​
let a: Animal;
let s: Size;
​
a = s;  // OK
s = a;  // OK
 Copy code 

Private and protected members of a class affect compatibility . When checking the compatibility of class instances , If the target type contains a private member , Then the source type must contain this private member from the same class . similarly , This rule also applies to type checking that contains protected member instances . This allows the subclass to assign values to the parent class , But it cannot be assigned to other classes of the same type .

because TypeScript It's a structural type system , Type parameters only affect the result types that use them as part of the type . such as ,

interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
​
x = y;  // OK, because y matches structure of x
 Copy code 

x and y Is compatible , Because their structure is no different when using type parameters . Change this example , Add a member , You can see how it works :

interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; 
 Copy code 

For generic parameters that do not specify a generic type , All generic parameters will be treated as any Compare . Then compare with the result type , Like the first example above .

such as ,

let identity = function<T>(x: T): T {
    // ...
}
​
let reverse = function<U>(y: U): U {
    // ...
}
​
identity = reverse;  // OK, because (x: any) => any matches (y: any) => any
 Copy code 

copyright notice
author[Hali],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2022/04/202204291854293395.html

Random recommended