简体   繁体   中英

Typescript, javascript: how to create a combined type dynamically via looping typeof?

Suppose we have:

const array = [a, b, c]  // could be any number of items in here
type T = ???  // how to write this?

such that the result is equivalent to

type T = typeof a & typeof b & typeof c

except T is dynamically created based on looping through array (since array may actually have items other than a , b , c ). A solution using interfaces may be acceptable as well.

If the array is typed correctly it will be typed as a union of the element types. Ex:

let a = { aProp: 1 };
let b = { bProp: 1 };
let c = { cProp: 1 };
const array = [a, b, c]  // typed as ({ aProp: number; } | { bProp: number; } | { cProp: number; })[]

Starting from this we can transform the union to an intersection using conditional types (see this answer for an explanation of UnionToIntersection ) and use a type query to get the type of an item in the array:

type UnionToIntersection<U> = 
    (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never

type T = UnionToIntersection<typeof array[number]>  // { aProp: number; } & { bProp: number; } & { cProp: number; }

Typescript types only exist at compile time, they will be removed at runtime (making it javascript). As the values of the array only exist at runtime, there is no way to get their type on compile-time.

Okay, so I couldn't get @Titian's answer to work; here's another solution

type Intersect<T extends any[]> = {[key in keyof T[number]] :T[number][key]};

And you can use it like this, say 在此处输入图片说明 See how the IDE knows its properties, and it error's if you try to assign anything else!


Unfortunately it thinks they are optional, you can combat this if you force them all to be real.

type Defined<T> = T extends (undefined|void) ? never : T;
type Intersect<T extends any[]> = {[key in keyof T[number]] :Defined<T[number][key]>};

The issue here is if you have a type that is {optional ?:string} , I think you'll lose that nuance.


I thought that simple & s would solve this issue, but it doesn't (and adds an upper bound to the size of your 'dynamic' array; in this case I could only be bothered copy-pasting for 6-length arrays).

type Intersect<T extends any[]> =
    T[5] extends void ? T[4] extends void ? T[3] extends void ? T[2] extends void ? T[1] extends void ? T[0] extends void ?
    [] : T[0] : T[0]&T[1] : T[0]&T[1]&T[2] : T[0]&T[1]&T[2]&T[3] : T[0]&T[1]&T[2]&T[3]&T[4] : T[0]&T[1]&T[2]&T[3]&T[4]&T[5]
;

I feel like the real solution will be when they figure out recursive types.

type Head<T> = T extends [infer U, ...Array<unknown>] ? U : never;
type Tail<T> = T extends any[] ?
    (...args :T) => any extends (head :any, ...args :infer U) => any ?
        U :
        never
    :
    never
;
type Intersect<T extends any[]> = T[1] extends void ? T[0] : Head<T>&Intersect<Tail<T>>;

Head & Tail (which can pull the first type of an array, and the remaining types as a new array type respectively) both work, but Intersect breaks when it refers back to Intersect .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM