Объединение Typescript с зависимыми типами

#javascript #typescript

#javascript #typescript

Вопрос:

Если у меня есть следующее объединение, которое обертывает функцию и ее аргумент, как я могу его вызвать?

 type Wrapper = {
  fn: (a: string) => void
  arg: string
} | {
  fn: (a: number) => void
  arg: number
}

let foo!: Wrapper
foo.fn(foo.arg) // Error Type 'string' is not assignable to type 'never'.
  

Я не могу понять, как это назвать. Все, что я пробовал, по сути, сводится к приведению (например, приведению к (a:any) => void ), которое я могу сделать, если нужно, но я чувствую, что мне не нужно этого делать.

Можно ли вызвать эту функцию без приведения?

Редактировать: Чтобы уточнить, я спрашиваю, существуют ли решения, которые не требуют изменения определения Wrapper .

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

1. Проблема в том, что, учитывая type F = (x: A) => T и type G = (x: B) => U , объединение type H = F | G является (x: A amp; B) => T | U . Чтобы вызвать функцию типа H , нам нужно будет указать аргумент, который является подтипом обоих A и B .

2. @AluanHaddad Да, похоже, это логика typescript, но она явно теряет информацию о типе. Я хочу знать, есть ли способ не потерять информацию о взаимосвязи между fn и arg .

3. Язык не считается arg дискриминантом . Вам нужно будет ввести дискриминант, то есть ссылку на игровую площадку

Ответ №1:

Вы можете использовать условный тип для устранения неоднозначности типов. К сожалению, это требует, чтобы вы в основном снова дублировали свои типы объединения оболочек, и если у вас есть еще что-то, что вам нужно проверить, ваш условный тип станет довольно уродливым. Но это позволяет вам избежать приведения (вроде).

 type Wrapper = {
  fn: (a: string) => void
  arg: string
} | {
  fn: (a: number) => void
  arg: number
}

type Disambiguate<W extends Wrapper> = W['arg'] extends string
  ? { fn: (a : string) => void, arg: string }
  : { fn: (a: number) => void, arg: number };

let foo!: Disambiguate<Wrapper>

// The compiler is satisfied. No error here.
// foo can only be one of the branches in the conditional type.
foo.fn(foo.arg);
  

Ответ №2:

Не совсем уверен в вашем варианте использования, но вышесказанное может быть достигнуто с помощью этого:

 
type ArgType = number | string

type Wrapper<T extends ArgType> = {
  fn: (a: T) => void
  arg: T
}

let foo!: Wrapper<number>
foo.fn(foo.arg)

let foo1!: Wrapper<string>
foo1.fn(foo1.arg)
  

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

1. Спасибо за попытку. Мой пример был упрощен. Я ищу ответ на общий случай, когда мы не можем переместить объединение таким образом.