Компилятор просто не достаточно умен, чтобы видеть такие манипуляции с типами параметров универсального типа. С конкретными типами компилятор может просто оценить конечный тип как пакет конкретных свойств, поэтому он знает, что вывод будет иметь тип Foo['bar']
, который имеет тип number
.
Есть разные способы, чтобы продолжить здесь. Часто наиболее удобно использовать утверждение типа , потому что вы умнее компилятора. Это сохраняет исходящий JavaScript-код тем же, но просто говорит компилятору не беспокоиться:
function bar<T, K extends NumberFields<T>>(property: K): Adapter<T> {
return function (from: T): number {
return from[property] as unknown as number; // I'm smarter than the compiler ?
}
}
Конечно, это не безопасно для типов, поэтому вам нужно быть осторожным, чтобы не лгать компилятору (например, from[property] as unknown as 12345
также будет компилятором, но, вероятно, это не так).
Эквивалентным утверждениям типа является функция single- overload , в которой вы задаете сигнатуру вызова настолько точную, насколько вам нужно, а затем ослабляете ее, чтобы реализация не жаловалась:
// callers see this beautiful thing
function bar<T, K extends NumberFields<T>>(property: K): Adapter<T>;
// implementer sees this ugly thing
function bar(property: keyof any): Adapter<any>
{
return function (from: any): number {
return from[property];
}
}
Другой способ - попытаться позволить компилятору заверить вас в безопасности типов, перепечатав функцию в форме, понятной компилятору. Вот менее глубокий способ сделать это:
function bar<T extends Record<K, number>, K extends keyof any>(property: K): Adapter<T> {
{
return function (from: T): number {
return from[property]
}
}
}
В этом случае мы представляем T
в терминах K
, а не наоборот. И никаких ошибок здесь нет. Единственная причина, по которой не следует использовать этот метод, заключается в том, что, возможно, вы хотите, чтобы ошибки появлялись в K
, а не в T
. То есть, когда люди вызывают bar()
с неверным параметром, вы хотите, чтобы он жаловался на неправильный тип K
, а не на неправильный тип T
.
Вы можете получить лучшее из обоих миров, выполнив следующее:
function bar<T, K extends NumberFields<T>>(property: K): Adapter<T>;
function bar<T extends Record<K, number>, K extends keyof any>(property: K): Adapter<T> {
{
return function (from: T): number {
return from[property]
}
}
}
И последнее. Я не уверен, как вы планируете звонить bar()
. Вы хотите сами указать параметры типа?
bar<Foo, "bar">("bar");
Или вы хотите, чтобы компилятор выводил их?
bar("bar")?
Если это последнее, вам, вероятно, не повезло, так как T
выводить некуда, кроме, возможно, какого-то внешнего контекста. Так что вы можете захотеть посмотреть на это больше.
Хорошо, надеюсь, это поможет. Удачи!