preface
Extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends extends
- Represents the meaning of inheritance/extension
- Represents the meaning of a constraint
- Indicates the meaning of allocation
The basic use
Extends is a common keyword in TS and a new one introduced in ES6. In JS, extends is usually used with class, for example:
- Inherits the methods and properties of the parent class
class Animal {
kind = 'animal'
constructor(kind){
this.kind = kind;
}
sayHello(){
console.log(`Hello, I am a The ${this.kind}! `); }}class Dog extends Animal {
constructor(kind){
super(kind)
}
bark(){
console.log('wang wang')}}const dog = new Dog('dog');
dog.name; // => 'dog'
dog.sayHello(); // => Hello, I am a dog!
Copy the code
Here Dog inherits the sayHello method from its parent class because it can be called on the Dog instance Dog.
- Inheriting a type
In ts, extends extends extends not only from values like JS but also from types:
interface Animal {
kind: string;
}
interface Dog extends Animal {
bark(): void;
}
// Dog => { name: string; bark(): void }
Copy the code
Generic constraint
When writing generics, we often need to restrict the type parameters. For example, we want to pass in an array of parameters with the name attribute:
function getCnames<T extends { name: string }>(entities: T[]):string[] {
return entities.map(entity= > entity.cname)
}
Copy the code
The extends extends imposes a restriction on the passed argument that each of its entities can be an object, but it must have a Cname attribute of type string. For example, redux dispatches an action that must contain the type attribute:
interface Dispatch<T extends { type: string }> {
(action: T): T
}
Copy the code
Conditional types and higher-order types
SomeType extends OtherType ? TrueType : FalseType;
Copy the code
When the type on the left of the extends is assignable to the one on the right, Then you’ll get the type in the first branch (the “True” branch); Otherwise you’ll get the type in the latter branch (the false branch).
Extends is also useful for determining whether a type can be assigned to another type. This is especially useful when writing higher-level types, such as 🌰 :
type Animal = {
name: string;
}
type Cat = {
name: string;
}
type Bool = Animal extends Cat ? 'yes' : 'no'; // Bool => 'yes'
Copy the code
Type this in vscode or ts playground and you’ll see that Bool is of type ‘yes’. That’s because Animal and Cat are exactly the same type, or Cat has all the constraints that Animal has; In other words, a value of type Animal can be assigned to a value of type Cat (and vice versa). It is important to understand that A extends B means that type A can be assigned to type B, not that type A is A subset of type B. Expand a bit to elaborate on the problem:
type Animal = {
name: string;
occupation: string;
}
type Cat = {
name: string;
}
type Bool = Cat extends Animal ? 'yes' : 'no'; // Bool => 'no'
Copy the code
When we add a occupation attribute to Animal, we find that Bool is ‘no’. This is because Cat does not have a occupation attribute of type string. Cat does not satisfy the type constraint for Animal. Therefore, A extends B means that type A can be assigned to type B, rather than that type A is A subset of type B. It is important to understand how extends is used in ternary expressions of type.
Moving on to the example
type A1 = 'x' extends 'x' ? string : number; // string
type A2 = 'x' | 'y' extends 'x' ? string : number; // number
type P<T> = T extends 'x' ? string : number;
type A3 = P<'x' | 'y'> // ?
Copy the code
A1 and A2 are common uses of the extends condition judgment, as above.
P is a generic type parameter T, its expression and A1 and A2 are exactly the same, in the form of A3 is a generic type P incoming parameters’ x ‘|’ y ‘type, if the’ x ‘|’ y ‘to the expression of a generic class, you can see and A2 type of form is exactly the same, that is, A3 and A2 are exactly the same type, right?
Have interest can try by oneself, here gave the conclusion directly
type P<T> = T extends 'x' ? string : number; Type A3 = P < 'x' | 'y' > / / A3 is of type string | numberCopy the code
This relates to the concept of Distributive Conditional Types
When conditional types act on a generic type, they become distributive when given a union type
For conditional types that use the extends keyword (that is, the conditional expression type above), if the parameter before extends is a generic type, then when the parameter is passed in an associative typeDistributive law
Calculate the final result. The distributive property says,Split the union items of the union type into individual items
, substitute the condition types respectively, and then substitute each item into the results, and then combine them together to get the final judgment result.
If we plug a union type into ToArray, then the conditional type will be applied to each member of that union.
Let’s use the example above
type P<T> = T extends 'x' ? string : number; Type A3 = P < 'x' | 'y' > / / A3 is of type string | numberCopy the code
In this case, T precedes extends, which is a generic parameter. In the definition of A3, to T the incoming is’ x ‘and’ y ‘joint types’ x’ | ‘y’, satisfy the distributive law, so the ‘x’ and ‘y’ being opened, respectively in P < T >
P<'x' | 'y'> => P<'x'> | P<'y'>
I plug in the prime of x
'x' extends 'x' ? string : number => string
That’s y prime
'y' extends 'x' ? string : number => number
Then each substitution results together, get the string | number
In summary, the distributive law applies when two points are met: first, the parameter is a generic type, and second, the parameter is substituted into a union type
-
Special never
Type A1 = never extends 'x'? string : number; // string type P<T> = T extends 'x' ? string : number; type A2 = P<never> // neverCopy the code
In the above example, the result of A2 and A1 is different. It looks like never is not a union type, so we can substitute the definition of the condition type and get the same result as A1.
In fact, the conditional assignment type is still at work here. Never considered empty joint type, that is to say, there is no joint type of joint, so meet the distributive law of the above, however, because there is no joint can be allocated, so P < T > expression actually no execution, so the definition of A2 is similar Yu Yongyuan function does not return, is never type.
-
Prevent assignments in conditional judgments
type P<T> = [T] extends ['x'] ? string : number;
type A1 = P<'x' | 'y'> // number
type A2 = P<never> // string
Copy the code
In the definition of the conditional judgment type, the allocation of the conditional judgment type can be blocked by enclosing the generic parameter []. In this case, the type of the passed parameter T will be treated as a whole and will not be allocated.
Application in advanced types
- Exclude
Exclude is an advanced type in TS. It excludes all union items that occur in the second union type from the first union type argument, leaving only parameters that do not occur.
Example:
type A = Exclude<'key1' | 'key2', 'key2'> // 'key1'
Copy the code
So the definition of Exclude is
type Exclude<T, U> = T extends U ? never : T
This definition takes advantage of the assignment principle in conditional types to try to break instances apart and see what happens:
Type A = ` Exclude < 'key1' | 'key2', 'key2' > ` / / equivalent to type A = ` Exclude < 'key1', 'key2' > ` | ` Exclude < 'key2', 'key2'>` // => type A = ('key1' extends 'key2' ? never : 'key1') | ('key'2 extends 'key2' ? Never: 'key2') / / = > / / never is all types of subtype type A = 'key1' | never = 'key1'Copy the code
- Extract
The advanced Extract type does the opposite of Exclude. It extracts the second parameter’s joint from the first parameter’s joint. Of course, the second parameter can contain items that the first parameter does not.
Here is the definition and an example, if you are interested, you can derive it yourself
type Extract<T, U> = T extends U ? T : never
type A = Extract<'key1' | 'key2', 'key1'> // 'key1'
Copy the code
- Pick
Extends’s condition judgment, in addition to defining condition types, can also be used in generic expressions to constrain generic parameters
Type Pick<T, K extends Keyof T> = {[P in K]: T[P]} interface A {name: string; age: number; sex: number; } type A1 = Pick < A, 'name' | 'age' > / / error: type "A" key "|" noSuchKey "" does not meet the constraint" keyof A "type A2 = Pick < A, 'name' | 'noSuchKey >Copy the code
Pick mean, from T, the interface will be involved in the joint type K of the selected item, forming a new interface, which extends K keyof T K is used to constraint conditions, namely, the incoming parameter K must make this condition is true, otherwise ts will be an error, that is to say, K T the properties of the joint item must come from the interface.
This is a common scenario for the extends keyword in TS.
reference
- www.typescriptlang.org/docs/handbo…
- www.typescriptlang.org/docs/handbo…
- www.typescriptlang.org/docs/handbo…
- www.typescriptlang.org/docs/handbo…