Three Ways of Using "extends" in TypeScript
Tags: typescript, programming, javascript, webdev
Published: 2023-06-20 06:25:32

When writing a TypeScript code, you will often see the "extends" keyword. But please be careful! This keyword works differently according to its context.
In this article, I am going to explain how to use "extends" in three different ways in TypeScript.

## 1. Inheritance
The "extends keyword" will be used for **Interface Inheritance** (or Class Inheritance). Here is an example of the Interface Inheritance:
```ts
interface Vehicle {
    wheels: number;
    maker?: string;
}

interface Car extends Vehicle {
    power: "gas" | "electricity";
}

interface Bicycle extends Vehicle {
    folding: boolean;
}

const myCar: Car = {
    wheels: 4,
    maker: "Toyota",
    power: "gas"
} // OK

const car: Car = {
    wheels: 4,
    maker: "Honda",
    power: "gas",
    folding: false
} // Error
// Type '{ wheels: number; maker: string; power: "gas"; folding: boolean; }' is not assignable to type 'Car'.
    // Object literal may only specify known properties, and 'folding' does not exist in type 'Car'.

const bicycle: Bicycle = {
    wheels: 2,
} // Error
// Property 'folding' is missing in type '{ wheels: number; }' but required in type 'Bicycle'.
```
You can make an inherited interface from a base interface. (In this example, `Vehicle` is the base interface, and `Car` and `Bicycle` are the inherited interfaces.)
If you want to make a new interface taking over properties from other interfaces, this technique is useful because you do not have to declare a new interface from scratch again.
(I discussed Interface Extensions and Merging in detail in another article, so please read [this one](https://dev.to/tomoy/interface-extendingmerging-in-typescript-3fjb) if you want to know more about it.)

## 2. Generic Constraints
If you are an intermediate or advanced TypeScript programmer, you may want to use **Generic Types** in your code. For example, if you want to get the value from an object's property, you may write the code below:

```ts
const testObj = { x: 10, y: "Hello", z: true };

function getProperty<T>(obj: T, key: keyof T) {
  return obj[key];
}

const xValue = getProperty(testObj, 'x');
const yValue = getProperty(testObj, 'y');
```
The above code is functional. But this is not type safe enough. Let me hover my cursor over `xValue` and `yValue` and see their types.

```ts
const xValue = getProperty(testObj, 'x');
// const xValue: string | number | boolean

const yValue = getProperty(testObj, 'y');
// const yValue: string | number | boolean
```

You might expect the type of `xValue` would be `number` and the one of `yValue` would be `string`. However, unfortunately, the TypeScript type system could not narrow down the returned type of the `getProperty` function. As a result, the type of the return value of the function has become a union type of the `testObj` properties.
If you want to make a more type-safe function, you should use a technique, **Generic Constraints**, and narrow down the type of the return value. Let me refactor the previous example code:

```ts
const testObj = { x: 10, y: "Hello", z: true };

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const xValue = getProperty(testObj, 'x');
// const xValue: number
const yValue = getProperty(testObj, 'y');
// const yValue: string
```
As you saw, the types of `xValue` and `yValue` are narrowed down to a certain type of the `testObj`'s properties.
In the above example code, the `extends` keyword works to constrain the type of the generic, `K`. In other words, one of the keys of `T` is chosen for `K` instead of allowing any keys to be the type of the `key` argument. Thus, the function can be limited to only one return type instead of a union type.

## 3. Conditional Types
TypeScript has the capability of making a logic to dynamically generate a type according to a type assigned to a generic.
Here is an example:

```ts
type IsString<T> = T extends string ? true : false;

type x = IsString<'hello'>;
// type x = true

type y = IsString<number>;
// type y = false

type z = IsString<string>;
// type z = true
```

The `IsString` type is the so-called **Conditional Type**. The type of `IsString` differs according to its generic, `T`.
Let me interpret the line, `T extends string ? true : false;`.
If the type of `T`(left) is a subtype of `string`(right), in other words, if the `T`(left) type is assignable to the `string`(right) type, it returns `true`. Otherwise, it will be `false`.

## Conclusion
In conclusion, the `extends` keyword in TypeScript is a versatile tool, playing crucial roles in interface inheritance, generic constraints, and conditional types. Mastering these concepts will help you write cleaner, more robust and more flexible TypeScript code.
However, at the same time, this keyword may confuse you when reading a codebase written by other developers since the `extends` keyword works differently according to contexts.
Therefore, you should keep in your mind the fact that the keyword has several meanings, "Inheritance", "Generic Constraints", and "Conditional Types".