Home > Net >  How to define a type inside a generic interface for a property inside this generic type
How to define a type inside a generic interface for a property inside this generic type

Time:06-29

Example:

export interface Column<T> {
    field: string;
    columnFormatter?: (props: {
        value: any/** field type inside T**/; data: T; node: any
    }) => void;
}

field is the name of the property inside type T, how can I say that the value is of that type?

export interface IPurchase {
    id: string;
    name: string;
    purchaseDate: Date;
}

let doSomethingWithMyDate: (myDate: Date) => (true);


const columns: Array<Column<IPurchase>> = [
    {
        field: "purchaseDate", /* "purchaseDate" must be inside IPurchase */
        columnFormatter: ({ value /* must identify that this is a Date */ }) => 
            doSomethingWithMyDate(value)

    }];

CodePudding user response:

In order to represent the correlation between the field property and the type of the value property of the props parameter to columnFormatter, you need Column<T> to be a union type with one member for every key of T. For example, given your IPurchase example, you need Column<IPurchase> to be

type ColumnIPurchase = {
    field: "id";
    columnFormatter?: ((props: {
        value: string;
        data: IPurchase;
        node: any;
    }) => void);
} | {
    field: "name";
    columnFormatter?: ((props: {
        value: string;
        data: IPurchase;
        node: any;
    }) => void) 
} | {
    field: "purchaseDate";
    columnFormatter?: ((props: {
        value: Date;
        data: IPurchase;
        node: any;
    }) => void);
}

This will behave as desired:

const columns: Array<Column<IPurchase>> = [
    {
        field: "purchaseDate",
        columnFormatter: ({ value }) => doSomethingWithMyDate(value)
    },
    {
        field: "name",
        columnFormatter: ({ value }) => doSomethingWithMyDate(value) // error!
        //  string isn't a Date ----------------------------> ~~~~~ 
    }
];

So that's what we want... how can we write Column<T> to do it?


Here's one way:

type Column<T> = { [K in keyof T]-?: {
    field: K;
    columnFormatter?: (props: { value: T[K]; data: T; node: any }) => void;
} }[keyof T]

The general form of this type, {[K in keyof T]-?: F<K>}[keyof T], is known as a distributive object type, as coined in microsoft/TypeScript#47109; we are making a mapped type over the keys in keyof T and then immediately index into it with keyof T so as to get a union of F<K> for every key K in keyof T.

In particular here we are computing { field: K; columnFormatter?: (props: { value: T[K]; data: T; node: any }) => void; } where K is the type of the key, and T[K] is the type of the property value corresponding to that key.

And you can verify that Column<IPurchase> evaluates to exactly the desired type.

Playground link to code

  • Related