如果未明确设置,编译器将无法解析泛型

时间:2019-04-26 19:03:19

标签: typescript typescript-generics

我有以下简单代码:

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完全相同。但是,如果未明确设置泛型类型并且方法“ bar”已被使用,则Ts编译器​​似乎无法“保持”泛型类型。在构造函数之后立即调用。 我的问题了。 这是一个错误还是我只是做错了什么?如果是,我该如何解决呢?

请让我知道是否缺少某些信息。

1 个答案:

答案 0 :(得分:2)

TypeScript中的大部分type inference都是沿“时间向前”方向完成的。也就是说,它根据给定的片段类型来推断表达式的类型。例如,

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运行时的正常运行,该运行时知道fx并计算f(x)


但是还有contextual typing,其中推理以某种“时间倒退”的方式进行。也就是说,编译器知道表达式的 expected 类型及其部分的某些但不是全部的类型,并尝试推断缺少的部分的类型。 必须已经才能产生预期的结果。例如:

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()是通用的,但是无法从参数推断TanyX()(因为它不带参数)。而且,如果您直接调用它(如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<{}>。而且有一个错误。

我不知道关于为什么此处没有上下文键入的完美答案。可能是因为这将需要更多的处理时间来执行,并且通常不值得(因为此用例不会一直发生)。我搜索了TypeScript's GitHub issues,但没有找到合适的内容。如果您认为用例令人信服,则可以在此处提出问题(进行更彻底的搜索,以免重复出现的问题),但我不会屏住呼吸等待有人解决它。

相反,像您的“示例2”那样的解决方法是手动指定未推断的类型参数,这是一种合理的解决方法:

const h: string = anyG<string>().g; // okay

或者,如果这种情况持续发生,并且您想要更多的类型安全性,则可以将故障代码包装在函数中,并利用从返回类型到参数类型的上下文类型推断,我们知道这是可行的: / p>

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

好的,就我所能找到的答案。希望能有所帮助;祝你好运!