見出し画像

Define type for specific property of interface in TypeScript

Sometimes, I face a case that I want to define a type for a specific property of an interface already defined in a library in TypeScript.
For instance, there's a case like below.

// an index.d.ts in a library
export interface Category {
 id: number;
 name: string;
}

A Category interface has id and name. These have already been defined with literal types as number and string. Therefore, it means we would be able to assign any number to its id property.
And then if the library also has an interface like:

export interface Label {
 id: number;
 name: string;
}

We can't isolate the kind of type for their id property, because the Label's id and the Category's id are having an utterly same type.
That's the thing.

Fortunately, we can use the `type` of TypeScript as an alias of something. So, I thought that I could solve this problem by using it like:

import { Category } from 'a-library';
type CategoryID = Category.id;

But it didn't work because the `interface` of TypeScript won't remain in a transpiled JavaScript code; hence we can't access any property of an interface directly.
So, I considered using some utility types. The utility types are particular types that are providing transformation ways for our types already defined. And I've found `Extract` from among those. https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttu

My attempt with it is:

import { Category } from 'a-library';
type CategoryID = Extract<Category, 'id'>;

It didn't work as well. Unfortunately, the CategoryID will be `never` type.
I've tried some other utility types such as `Pick` and `Partial,` but these are for just reducing object properties, not for my purpose.

I thought that I couldn't find a solution, but suddenly an idea came up!
It was a just straightforward way.

The solution is defining a new interface with extending an existed one and then override the target property by the desired type.

import { Category, Label } from 'a-library';

export type CategoryID = number;
export interface CategoryIF extends Category {
 id: CategoryID;
}

export type LabelID = number;
export interface LabelIF extends Label {
 id: LabelID;
}

By this solution, we could identify which id is which 🎉
I totally forgot that the interface could extend another one. Even though the name of the Category interface change to `CategoryIF` for example, but it isn't a bad idea for me.