Компилятор не может разрешить Generic, если он не установлен явно - PullRequest
0 голосов
/ 26 апреля 2019

У меня есть следующий простой код:

class Model {
    prop1: number;
}

class A<TModel> {
    constructor(p: (model: TModel) => any) {}
    bar = (): A<TModel> => {
        return this;
    }
}

function foo<T>(p: A<Model>) { }

Пример 1:

foo(new A(x => x.prop1)) // Works

Пример 2:

foo(new A<Model>(x => x.prop1).bar()) // Works

Пример 3:

foo(new A(x => x.prop1).bar()) // Doesn't work. (Property 'prop1' does not exist on type '{}'.)

Моя «проблема» в том, что я хочу, чтобы пример 3 работал точно так же, как пример 2. Но компилятор Ts, похоже, не может «сохранить» универсальный тип, если он явно не установлен, а метод «bar» сразу вызывается после конструктора. Мой вопрос сейчас. Это ошибка или я просто что-то не так делаю, и если да, то как я могу решить эту проблему?

Пожалуйста, дайте мне знать, если какая-то информация отсутствует.

1 Ответ

2 голосов
/ 27 апреля 2019

Большая часть вывода типа в 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

Хорошо, это как можно ближе кответ.Надеюсь, это поможет;удачи!

...