#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 недостаточно умен, чтобы делать такие предположения. Таким образом, полностью избежать утверждения типа внутри вашей функции невозможно.