简体   繁体   中英

Typescript - Create Combined Type From Array Items' Generic Types

Given an array of items that implemented a specific class with a generic type, I would like to create combined type from the array items - For each item, take its generic (defined as first generic argument for example) and create a type that implements all the the generic values of the items.

For Example:

// Defining 2 types with different set of methods
type ObjectWithMethods1 = {
  call1(a: string): void;
}

type ObjectWithMethods2 = {
  call2(): number;
}

class BaseItem<T> {
}

// Defining 2 classes that both extends the same class/abstract class,
// each passing a different generic value.
class Item1 implements BaseItem<ObjectWithMethods1> {
}
class Item2 implements BaseItem<ObjectWithMethods2> {
}

// An array that contains instances of the same base class, however each of its items
// used a different type in the BaseItem generics  
const arr: BaseItem<any>[] = [new Item1(), new Item2()]

// How to define the typing of a "getCombinedObject" method that its return type will be an object 
// that implements both ObjectWithMethods1 and ObjectWithMethods2,
// e.g. ObjectWithMethods1 & ObjectWithMethods2
const combined = getCombinedObject(arr);
combined.call1('test'); //void
combined.call2(); //number

I've tried achieving it in few different ways, can fetch the generic values of the array but failed to achieve the aggregated values of the array.

Its something conceptually similar to this (without the additional depth created due to the of the iteration of the array items):

type CombinedObject<TArray extends TItem[], TItem extends Record<string, any>> = {
  [key: string]: {
    [Index in keyof TArray]: TArray[Index] extends { [key]: any } ? TArray[Index][key] : never;
  };
};

Thanks alot!

Description is in comments

// Defining 2 types with different set of methods
type ObjectWithMethods1 = {
    call1: (a: string) => void;
}

type ObjectWithMethods2 = {
    call2: () => number;
}

class BaseItem<T> {
    /**
     * We need to make this class unique
     */
    tag = 'base'
}

/**
 * It is important to extend and implement
 */
class Item1 extends BaseItem<ObjectWithMethods1> implements ObjectWithMethods1 {
    call1 = (a: string) => 'str'
}

/**
 * It is important to extend and implement
 */
class Item2 extends BaseItem<ObjectWithMethods2> implements ObjectWithMethods2 {
    call2 = () => 42;
}

class Item3 { }

// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
    k: infer I
) => void
    ? I
    : never;


/**
 * Elem extends BaseItem<any> - makes sure that each instance extends BaseItem
 * UnionToIntersection - will merge all instances into one
 */
function getCombinedObject<Elem extends BaseItem<any>, Arr extends Elem[]>(arr: [...Arr]): UnionToIntersection<[...Arr][number]>
function getCombinedObject<Elem extends BaseItem<any>, Arr extends Elem[]>(arr: [...Arr]) {
    return arr.reduce((acc, elem) => ({ ...acc, ...elem }), {})
}

const combined = getCombinedObject([new Item1(), new Item2()]);
combined.call1('test'); // string
combined.call2(); // number

Playground

PS Avoid declaring bivariant methods:

type ObjectWithMethods1 = {
  call1(a: string): void;
}

This is unsafe. More information you can find here

I just talk about the need of the types for the development time

Consider this example:

class BaseItem<T>{ }

interface ObjectWithMethods1 {
    call1(a: string): void;
}
class Item1 implements BaseItem<ObjectWithMethods1> { }

Since T generic type is unused in BaseItem class implementation, it is completely ignored in class Item1 .

TS is unable to distinguish BaseItem<ObjectWithMethods1> and BaseItem<ObjectWithMethods2> .

See example:

declare var x: BaseItem<ObjectWithMethods1>
declare var y: BaseItem<ObjectWithMethods2>
x = y // ok
y = x // ok

Hence, these types from TS perspective are equal.

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