Typescript: инвертировать ограничение на общий

avatar
Stéphane Veyret
8 апреля 2018 в 07:31
547
1
3

У меня есть объекты, содержащие функции:

const messages = {
  helloYou: (name: string) => `Hello ${name}`,
  goodbye: () => 'Good bye',
}

и класс, управляющий этим типом объекта:

class MyClass<T> {
  …
  getMessage<K extends keyof T>(name: K): T[K] {
    …
  }
  …
}

Затем я могу ввести безопасный вызов функции объекта, зарегистрированного в классе:

myObject = new MyClass<typeof messages>(messages)
myObject.getMessage('helloYou')('John')
myObject.getMessage('goodbye')()

Проблема в том, что getMessage возвращает функцию, и в случае, если мне не нужны параметры (как в примере goodbye выше), я, скорее всего, забуду пустую скобку. Поэтому я попытался написать функцию, принимая как имя сообщения, так и параметры. Вот что мне удалось сделать:

export function t<O extends Messages & { [P in K]: (arg1: P1) => string }, K extends keyof O, P1>(myObject: MyClass<O>  name: K, p1: P1): string

(эта функция, разумеется, перегружена для 0, 2, 3, 4… параметров функции)

Эта функция используется следующим образом:

t(myObject, 'helloYou', 'John')

Это работает хорошо и безопасно для типов, но я бы предпочел, чтобы этот метод t был методом MyClass вместо того, чтобы задавать myObject в качестве первого параметра каждого вызова. Действительно, было бы элегантнее назвать это так:

.
myObject.t('helloYou', 'John')

Но, как вы можете видеть в методе t, мне удалось наложить ограничения на тип объекта O (которые соответствуют типу объекта T, зарегистрированному в MyClass) в зависимости от других параметров функции. Если я хочу, чтобы t был методом класса, мне нужно инвертировать ограничения, т.е. ограничить другие параметры для O.

Кто-нибудь знает, как это сделать?

Спасибо.

Источник
smnbbrv
8 апреля 2018 в 13:45
0

почему бы вам просто не вызвать функции для объекта сообщений, например myObject.messages.goodbye();? Почему вы передаете общий new MyClass<typeof messages>(messages), если его можно просто опустить, например new MyClass(messages);? Какую проблему вы пытаетесь решить? Это действительно похоже на какую-то сверхинженерию.

Stéphane Veyret
9 апреля 2018 в 14:51
0

Я напечатал только достаточно кода, чтобы вы могли понять проблему, поэтому вы не видите причин, по которым я хочу это сделать. Просто чтобы уточнить, на самом деле конструктор MyClass принимает в качестве параметров не сообщения, а карту сообщений. Если я добавлю, что настоящее имя ̀MyClass` — I18n, вы, вероятно, лучше поймете, что я пытаюсь сделать… ;)

Ответы (1)

avatar
jcalz
8 апреля 2018 в 18:37
1

Вы должны быть в состоянии достичь своей цели, используя параметры this. Если вы называете первый параметр функции this и присваиваете ему тип, вы ограничиваете тип this в теле этой функции этим типом. Точно так же, как вызов функции с параметром, тип которого не соответствует аннотации типа параметра, является ошибкой, вызов метода для объекта, тип которого не соответствует аннотации параметра метода this.

В вашем случае возьмите каждую сигнатуру функции (перегрузка и реализация) вида

function t<
  O extends Messages & { [P in K]: (arg1: P1) => string }, 
  K extends keyof O, 
  P1> (myObject: MyClass<O>  name: K, p1: P1): string;

и преобразовать его в метод, поместив его в определение класса и заменив параметр myObject параметром this:

public t<
  O extends Messages & { [P in K]: (arg1: P1) => string }, 
  K extends keyof O, 
  P1> (this: MyClass<O>  name: K, p1: P1): string;

Новый метод должен вести себя так же, как ваша отдельная функция. То есть foo.t(bar, baz) должно быть ошибкой тогда и только тогда, когда t(foo, bar, baz) является ошибкой. Попробуйте. Надеюсь, это поможет; удачи!