Вы можете сделать это так.Define Ctor
:
type Ctor<C> = new (...args: any[]) => C;
Ctor<C>
- это тип для «чего-то, что может быть вызвано с помощью new
и создает C
»
Тогда вы можете определить CtorsOf
:
type CtorsOf<T> = { [K in keyof T]: Ctor<T[K]> };
Если T
является кортежем типов, CtorsOf<T>
создает кортеж, который ожидает конструктор для каждого типа T
.Например.CtorsOf<[One, Two]>
приведет к [Ctor<One>, Ctor<Two>]
.
Тогда вы можете определить example
следующим образом:
function example<C extends Component[]>(
callback: (...args: C) => void,
...constructors: CtorsOf<C>
): void {
let instances = constructors.map(c => new c()) as C;
callback(...instances);
}
C
- это кортеж, определяющий типы аргументов, которые вы ожидаетеВаш обратный вызов, а затем из этого кортежа мы получаем набор конструкторов для аргумента ...constructors
.
Я не вижу способа избежать утверждения типа в let instances = ... as C
.Проблема заключается в том, что кортеж constructors
теряется при операции .map
, а полученный массив имеет тип Component[]
.Я пробовал несколько вариантов, но даже с чем-то таким тривиальным, как ([1, "2"] as [number, string]).map(x => x);
, теряется кортеж исходного массива, и TS выводит окончательный тип для результирующего массива (string | number)[]
.
Вотполный пример адаптирован из вашего исходного источника:
class Component {}
class One extends Component { public a = 1; }
class Two extends Component { public b = 2; }
function receive(one: One, two: Two) { console.log(`one: ${one.a}, two: ${two.b}`) }
function wrongReceive(a: string, b: number) { console.log(`a: ${a}, b: ${b}`) }
// Ctor<C> is a type for "something which can be called with new and constructs a C"
type Ctor<C> = new (...args: any[]) => C;
// Given T a tuple of types, CtorsOf<T> produces a tuple which expects a constructor
// for each type of T. Eg. CtorsOf<[One, Two]> would be [Ctor<One>, Ctor<Two>]
type CtorsOf<T> = { [K in keyof T]: Ctor<T[K]> };
// If you uncomment this and use an editor that shows you what Q expands to, you'll see that
// it expands to [Ctor<One>, Ctor<Two>].
// type Q = CtorsOf<[One, Two]>;
function example<C extends Component[]>(
callback: (...args: C) => void,
...constructors: CtorsOf<C>
): void {
let instances = constructors.map(c => new c()) as C;
callback(...instances);
}
example(receive, One, Two); // This is okay.
example((c: One, d: Two) => { // This is okay.
console.log(`c: ${c.a}, d: ${d.b}`);
}, One, Two);
// example(wrongReceive, One, Two); // Fails to compile.
// example(receive, Two, One); // Fails to compile.
class TwoPrime extends Two { public bPrime = 1; };
example(receive, One, TwoPrime); // This is okay too. TwoPrime is a subclass of Two.
function receivePrime(one: One, two: TwoPrime) { console.log(`one: ${one.a}, two: ${two.b}`) }
example(receivePrime, One, TwoPrime); // This is okay.
// example(receivePrime, One, Two); // Fails to compile. The shape of Two is not compatible with TwoPrime.
const z = ([1, "2"] as [number, string]).map(x => x);