Home > Software engineering >  Is it possible to get the index of an array item when creating a type in typescript
Is it possible to get the index of an array item when creating a type in typescript

Time:09-05

I'm trying to create a function createCells that takes an array of strings and returns an object whose properties depends on the index of items in the array

For example the function createCells(['10', '12', '13']) would return an object with properties {Cell0, Cell1, Cell2}

So far I've been able to create one where the object returned is based on the value of items in the array

const createCells = <T extends `${number}`[]>(
    args: T,
) => {
    return args.reduce(
        (prev, curr) => ({
            ...prev,
            [`Cell${curr}`]: () => { },
        }),
        {}
    ) as {
            [key in typeof args[number] as `Cell${key}`]: (
            ) => void;
        };
};

but this way the function createCells(['10', '12', '13']) would return an object with properties {Cell10, Cell12, Cell13}

For what I'm trying to achieve, the function would be

const createCells = <T extends `${number}`[]>(
    args: T,
) => {
    return args.reduce(
        (prev, curr, index) => ({
            ...prev,
            [`Cell${index}`]: () => { },
        }),
        {}
    ) 
};

How do i use typescript to indicate the type of the object returned by the function

CodePudding user response:

// it is possible to get the length of a tuple and therefore you can create a counter
type Increment<T extends unknown[]> = [...T, unknown]

// create a helper type to do something based on the value and the index
type CellType<CellValue, Index extends unknown[]> = { [K in `Cell${Index["length"]}`]: CellValue } // do something based on the value or the index

// Iterate over your tuple and intersect the result of CellType<~,~> to create the target object
type CreateCells<T extends unknown[], Index extends unknown[] = []> =
    T extends [infer Head, ...infer Rest]
    ? CellType<Head, Index> & CreateCells<Rest, Increment<Index>>
    : {}

infer exact value typescript

// This type gets infers the value without using required and
// prettifies the intersection {a:"a"} & {b:"b"} => {a:"a",b:"b"}
export type Narrowable = string | number | bigint | boolean;
export type Narrow<A> =
    | (A extends Narrowable ? A : never)
    | (A extends [] ? [] : never)
    | {
        [K in keyof A]: A[K] extends Function ? A[K] : Narrow<A[K]>;
    };

const createCells = <T extends `${number}`[]>(
    // Narrow allows us prevent the widening
    args: Narrow<T>,
): Narrow<CreateCells<T>> => {
    // for narrow we have to cast the argument 
    return (args as unknown as string[]).reduce(
        (prev, curr, index) => ({
            ...prev,
            [`Cell${index}`]: () => { },
        }),
        // yeah, any here to make our ts-compiler happy
        // I think this is a common practice if you are creating an object dynamicly
        {} as any
    )
};

type Test = Narrow<CreateCells<["1", "3"]>>

const x = createCells(["1", "2", "3", "124214"]) //{ Cell0: "1"; Cell1: "2"; Cell2: "3"; Cell3: "124214";}
const z = createCells(["1", "2", "3", "124214asd"]) //invalid

playground

  • Related