Зависимые типы в typescript — определяют тип через тип имени свойства

#typescript #dependent-type

#typescript #зависимый тип

Вопрос:

Я пытался сделать типы зависимыми от типа переданного аргумента. Надуманный пример:

 type NS = "num" | "str"
type Data<T extends NS> = T extends "num" ? number : string
type Func<T extends NS> = (x: Data<T>) => Data<T>
type Funcs = {[T in NS]: Func<T>}
type Obj = {[T in NS]: Data<T>}
const funcs: Funcs = {
  num: (x) => x * 2,
  str: (x) => x   x
}

function useFunc<T extends NS>(ns: T, obj: Obj): Data<T> {
  const f = funcs[ns]
  const x = obj[ns]
  return f(x)
}
  

Проблема в том, что f(x) имеет тип number | string , а не Data<T> , что, по-видимому, вызвано f наличием типа Funcs[T] , а не Func<T> . x также имеет тип Obj[T] , а не Data<T> .

Таким образом, typescript не может сделать вывод, что Funcs[T] и Func<T> одинаковы, и Obj[T] и Data<T> одинаковы, хотя именно так Funcs и Obj определяются…

Я могу заставить его работать, набирая все, но это противоречит цели использования типов:

 function useFunc<T extends NS>(ns: T, obj: Obj): Data<T> {
  const f = funcs[ns] as Func<T>
  const x = obj[ns] as unknown as Data<T>
  // note the double-cast as Obj[T] and Data<T> "do not sufficiently overlap"
  return f(x)
}
  

Есть ли способ заставить его работать в typescript?

Редактировать:

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

 function useFunc<T extends NS>(ns: T, obj: Obj): Data<T>
function useFunc(ns: NS, obj: Obj): Data<NS> {
  if (ns === "num") {
    const f = funcs[ns]
    const x = obj[ns]
    return f(x)
  }

  const f = funcs[ns]
  const x = obj[ns]
  return f(x)
}
  

Есть ли способ избежать этого повторения без потери безопасности типов?

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

1. Почему вы переходите к useFunc аргументу obj , который имеет тип Obj ? Это можно упростить до Data<T> .

2. Не уверен, что понимаю. Data<T> это тип свойств в объекте, как свойство, так и функция выбираются ns параметром.

Ответ №1:

Основная проблема заключается в том, что вы пытаетесь вызвать f функцию внутри useFunc , когда TS выводит f подпись как (x: never) => string | numer . Эта подпись несовместима с каждой подписью внутри funcs объекта.

Кроме того, как я уже сказал, подпись useFunc<T extends NS>(ns: T, obj: Obj): Data<T> должна быть упрощена или даже лучше сказать исправлена, чтобы useFunc<T extends NS>(ns: T, obj: Data<T>): Data<T> . Это потому, что TS должен знать тип данных, к которым вы будете передавать f , чтобы иметь возможность выполнять проверку типа. Кроме того, другая причина, по которой требуется вышеуказанная модификация, заключается в том, что useFunc вызов функции доступен только с ns равным "num" или "str" , но не одновременно. Из-за вышеизложенного obj тип параметра будет Data<"num"> или Data<"str"> . Следовательно, неправильно вводить его как Obj .

Возможное решение с минимальными изменениями будет:

 function useFunc<T extends NS>(ns: T, arg: Data<T>): Data<T> {
  const f = funcs[ns]
  return (f as Func<T>)(arg)
}
  

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

1. Я понимаю, что то, что вы предлагаете, сработает. Но мне нужно передать весь объект, который содержит два свойства, а не свойство. И свойство, и функция должны выбираться на основе ns параметра. Мне нужно вывести все типы на основе типа ns параметра.

2. Вопрос может быть сформулирован по-другому — приведенное выше является допустимым кодом JavaScript и довольно распространенным шаблоном для повторного использования кода в JS — передать строку, которая используется в качестве селектора свойств / методов для нескольких объектов. Может ли этот шаблон быть выражен типобезопасным способом в Typescript без повторения кода?

3. И вам все равно придется использовать приведение типов для f btw, которое сводит на нет смысл использования типов…

4. Я думаю, что компилятор TS недостаточно умен, чтобы делать такие предположения. Таким образом, полностью избежать утверждения типа внутри вашей функции невозможно.