Параметры условного метода на основе универсального типа

#typescript #typescript-typings

#typescript #typescript-типизации

Вопрос:

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

В идеале класс должен быть перегружен следующим образом.

 class Emitter<T = void> {
    public activate(): void // When T is void
    public activate(arg: T): void // When T isn't void
    public activate(arg?: T) { /* ... */ }
}
  

Наличие метода в качестве свойства функции работает теоретически, но требует a @ts-ignore для реализации метода.

 type OneArgFn<T> = T extends void
    ? () => void
    : (arg: T) => void

interface Emitter<T> {
    readonly activate: OneArgFn<T>
}
  

Другая возможность заключается в экспорте другого конструктора, когда предоставляется универсальный или нет, например, следующий

 interface EmitterNoArg extends Emitter {
    activate: () => true
}
interface EmitterOneArg<T> extends Emitter<T> {
    activate: (arg: T) => void
}

interface EmitterConstructor {
    new(): Emitter

    new(): EmitterNoArg
    new<T>(): EmitterOneArg<T>
}
  

но тогда для его экспорта требуется unknown ключевое слово.

 export default Emitter as unknown as EmitterConstructor
  

Они не кажутся оптимальными. Есть ли правильный способ иметь условные аргументы на основе универсального типа? Я бы подумал, что новые условные типы TypeScript решат эту проблему.

Ответ №1:

Одним из способов сделать это было бы использование кортежей в параметрах rest в отдельной общедоступной подписи:

 type OneArgFn<T> = T extends void
    ? () => void
    : (arg: T) => void

class Emitter<T = void> {
    public activate(...a: Parameters<OneArgFn<T>>): void
    public activate(arg?: T) { /* ... */ }
}

new Emitter().activate();
new Emitter<string>().activate("") // in 3.4 argument names are preserved
  

Комментарии:

1. Вау, я знал, что вы можете определять возвращаемые типы, но мне даже в голову не приходило, что это может работать и для аргументов. Я бы хотел, чтобы документ упоминал об этом. Спасибо!

2. @MoPro Документы не могут охватить все это, но здесь есть сообщество SO, которое поможет 🙂

3. Из любопытства, в чем преимущество второй перегрузки activate в этом решении? Существует ли ситуация, когда одного только первого недостаточно? Код в первом может так же легко проверять if a[0] === undefined , как код во втором проверяет, не определено ли arg значение. Спасибо!