Вывод сигнатуры обратного вызова аргумента как кортежа в обобщенной функции? - PullRequest
0 голосов
/ 27 февраля 2019

Исходя из моего предыдущего вопроса Я хочу переслать отображение универсального типа аргумента в качестве параметров в обратный вызов, который передается в качестве параметра.Мое желание состоит в том, чтобы передать произвольное количество функций конструктора, использовать их для создания экземпляров, а затем переслать экземпляры для обратного вызова.

У меня есть эта работа в настоящее время, когда параметр является просто массивом для обратного вызова, но это синтаксически неудобно, и TypeScript также не предоставляет инструментов для проверки того, что предоставленный обратный вызов имеет правильную сигнатуру.Если вы посмотрите на код ниже, вы должны увидеть мои намерения;Я хочу иметь возможность указывать обычные параметры для обратного вызова (не массива) и иметь ошибку TypeScript, если обратный вызов имеет несовместимую подпись.

Может ли TypeScript сделать это, и если да, то как?

class Component {}
class One extends Component { public a = 1; }
class Two extends Component { public b = 2; }

type CompCon = new (...args: any) => Component;

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}`) }

function example<T extends Array<CompCon>>(
  callback: (...args: ???) => void,
  ...constructors: T
): void {
  let instances = constructors.map( (c: CompCon) => new c() );
  callback(...instances);
}

example(receive, One, Two); // should be ok
example(wrongReceive, One, Two); // should have typescript compile errors on wrongReceive having wrong signature
example((c: One, d: Two) => {  // should be ok
  console.log(`c: ${c.a}, d: ${d.b}`);
});

1 Ответ

0 голосов
/ 01 марта 2019

Вы можете сделать это так.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);
...