Большая часть вывода типа в TypeScript выполняется в направлении «вперед по времени».То есть он выводит типы выражения с учетом типов его частей.Например,
declare const x: string;
declare function f<T>(x: T): Array<T>;
const y = f(x); // y inferred as Array<string>;
В этом случае тип f(x)
определяется типом x
и подписью f
.Это относительно просто для компилятора, поскольку он более или менее имитирует нормальную работу среды выполнения JavaScript, которая знает f
и x
и вычисляет f(x)
.
Но естьтакже контекстная типизация , в которой логический вывод происходит как бы в направлении «назад во времени».То есть компилятор знает ожидаемый тип выражения и типы некоторых, но не всех его частей и пытается определить тип отсутствующего элемента или частей должно быть для получения ожидаемого результата.Например:
declare function anyX<T>(): T;
const someX = anyX(); // inferred as {}
declare function f<T>(x: T): Array<T>;
const y: Array<string> = f(anyX()); // anyX() inferred as string
В этом случае функция anyX()
является общей, но невозможно вывести T
из параметров в anyX()
(поскольку она не принимает параметров).И, если вы просто вызовете его напрямую (как в someX
), он не сможет сделать вывод и станет пустым типом {}
.
Но компилятор знает, что y
должен быть Array<string>
, и что f()
примет тип своего ввода и вернет массив этого типа.Таким образом, можно сделать вывод, что anyX()
должен возвращать string
в этом случае.
Ваш код «Пример 1» является экземпляром контекстной типизации, которую выполняет компилятор: вывод типов параметров функции (или конструктора) из его возвращаемого (или экземпляра) типа.И это работает и для вложенных вызовов функций / конструкторов:
const z: Array<Array<Array<string>>> = f(f(f(anyX())));
Теперь контекстная типизация хороша, но это происходит не во всех мыслимых ситуациях.И, очевидно, единственное место, где он не возникает, заключается в выводе параметра универсального типа универсального типа / интерфейса / класса с учетом типов его свойств или методов:
type G<T> = { g: T; }
declare function anyG<T>(): G<T>;
const h: string = anyG().g; // error! no contextual typing here
Этопроблема с вашим "Примером 3".Компилятор не задерживает вывод типа возврата anyG()
, пока после обращения к типу g
.Вместо этого компилятор агрессивно вычисляет тип возвращаемого значения и, если ему не удается вывести T
из чего-либо, делает его G<{}>
.И есть ошибка.
Я не знаю, есть ли идеальный ответ относительно , почему здесь не происходит контекстная типизация.Вероятно, это связано с тем, что для его выполнения потребуется намного больше времени обработки, и обычно оно того не стоит (поскольку этот вариант использования не происходит постоянно).Я искал проблем с GitHub TypeScript , но не нашел ничего подходящего.Если вы считаете, что ваш вариант использования убедителен, вы можете подать проблему там (после более тщательного поиска, чтобы не дублировать существующую проблему), но я бы не стал затаить дыхание, ожидая, что кто-то решит ее.
Вместо этого, обходной путь, подобный вашему «Примеру 2», при ручном указании параметра неферментированного типа - разумный путь вперед:
const h: string = anyG<string>().g; // okay
Или, если это продолжает происходить с вами, и вы хотите еще какой-нибудь типбезопасность, вы можете заключить код неисправности в функцию и воспользоваться контекстным выводом типа из типа возвращаемого значения в тип параметра, который, как мы знаем, работает:
class Model {
prop1!: number;
}
class A<TModel> {
constructor(p: (model: TModel) => any) {}
bar = (): A<TModel> => {
return this;
};
}
function foo(p: A<Model>) {}
// helper function to get some contextual typing
function fixIt<TModel>(p: (model: TModel) => any): A<TModel> {
return new A(p).bar();
}
foo(fixIt(x => x.prop1)); // okay now
Хорошо, это как можно ближе кответ.Надеюсь, это поможет;удачи!