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, so 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
Another useful tool is to determine whether a type can be assigned to another type. This is useful when writing advanced types, such as 🌰 :
type Human = {
name: string;
}
type Duck = {
name: string;
}
type Bool = Duck extends Human ? 'yes' : 'no'; // Bool => 'yes'
Copy the code
Type this in vscode or ts playground and you’ll see that Bool is of type ‘yes’. This is because the types of Human and Duck are exactly the same, or Duck has all the constraints of Human. In other words, a value of type Human can be assigned to a value of type Duck, 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 Human = {
name: string;
occupation: string;
}
type Duck = {
name: string;
}
type Bool = Duck extends Human ? 'yes' : 'no'; // Bool => 'no'
Copy the code
When we add a occupation attribute to Human, we find that Bool is ‘no’. This is because Duck does not have a String occupation attribute, and the type Duck does not satisfy the type constraint of type Human. 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.
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 cname attribute. We can write:
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
Type determination and higher-order types
As mentioned above, extends is used in a ternary expression of a type to dynamically determine whether the type passed in meets a condition. This is useful for writing higher-order types, such as Extract:
type Extract<T, U> = K extends U ? T : never;
Copy the code
You can Extract a type from T that can be assigned to U. This is usually used to Extract the common part of the union type, such as the intersection of two objects’ keys. Such as:
type CommonKeys = Extract<'a'| 'b' | 'c'.'d'|'c'> // => 'c'
Copy the code
Complex types also work:
type Obj = Extract<
string[] | boolean | string.
number | string | string[] | void
> // string | string[]
Copy the code
Let’s take a look at how one of the most commonly used higher-order types utilizes extends. Omit Omit first Omit two generic parameters, the first is the target object and the second is the property key to be culled:
interface Men {
sex: 'male';
age: number;
lover: Men;
}
type Boy = Omit<Men, 'lover'>; // { sex: 'male'; age: number; }
Copy the code
Here, a new type Boy is obtained by eliminating the lover attribute of Men with the use of Omit, and its source code is as follows:
/ * *
* Exclude from T those types that are assignable to U
* /
type Exclude<T, U> = T extends U ? never : T;
/ * *
* Construct a type with the properties of T except for those in type K.
* /
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Copy the code
The extends extends extends extends implements a Exclude type to Exclude the key of an attribute, similar to Extract, which returns “never” if the constraint is met. Then, another high-order type Pick is used to extract the remaining properties satisfying the conditions from the target object.
This is a common scenario for the extends keyword in TS.