Mat*_*tia 3 type-inference typescript
有没有办法帮助编译器推断如下内容:
class Base<T> {
children(... children: (A<any> | B<T>)[]) { return this }
onVisit(handler:(context: T)=>void) { return this }
}
class A<T> extends Base<T> {
constructor( public context: T ) { super() }
}
class B<T> extends Base<T> {}
const foo = new A({ bar: 1 })
.children(
new A({baz:2}).onVisit(({baz})=>{}),
new B().onVisit(({bar})=>{}) // Fails here because it infers that the instance as type B<unknown> instead of B<{bar:number}>
)
Run Code Online (Sandbox Code Playgroud)
这似乎不是因为编译器无法从调用函数中提取一些上下文,因为这是有效的:
function f1<T>(p: T, ...h: ((p:T) => void)[]) { }
function f2<T>(h: (p: T) => void) { return h }
f1({ a: 1 },
f2((p) => { console.log(p.a) }),
)
Run Code Online (Sandbox Code Playgroud)
我可能完全困惑(很可能),但似乎如果后者有效,那么前者也应该有效。
当编译器检查表达式的内容以确定它是什么类型时,TypeScript 中的“正常”类型推断就会发生。它会查看表达式内部来执行此操作。这往往与运行时发出的程序的控制流发生在同一方向。如果我有类似的代码
let x = "";
let y = x.length;
Run Code Online (Sandbox Code Playgroud)
"",编译器确定( )的类型,并使用它来确定( )""的类型。然后编译器通过检查属性的类型(将是)来确定 的类型,然后使用它来确定 的类型(这将是)。前面的表达式决定后面的表达式的类型。xstringx.lengthlengthstringnumberynumber
但是,在某些情况下,编译器会进行上下文键入。在上下文类型中,当编译器检查表达式的上下文以确定其类型时,就会发生推理。它看起来在表达式之外来执行此操作。这往往发生在运行时发出的程序的控制流的相反方向。如果我有类似的代码
[""].map(z => z.length)
Run Code Online (Sandbox Code Playgroud)
,回调内的变量z没有显式类型,但无法通过检查回调的内容来确定其类型。但是,由于值map()数组的方法期望其参数是带有属性的回调,因此编译器使用此上下文来给出的类型。这里,在某种意义上,后面的表达式用于确定前面的表达式的类型。stringstringzstring
但上下文打字很脆弱。它只发生在少数特殊情况下,并且可以通过将必要的上下文信息“远离”需要推断类型的表达式来轻松打破:
let cb = z => z.length; // error, z is implicitly any
[""].map(cb); // oops, now we have an any[]
Run Code Online (Sandbox Code Playgroud)
z => z.length这里正在创建相同的回调。但这是自然发生的。string[]它唯一使用的地方是作为 a方法的回调map()。因此,从技术上讲,编译器可以想象, “好吧,cb预计是像 之类的类型的回调(val: string) => any,因此我们向上一行并给出cb该类型,然后从该上下文我们可以推断它z必须是 a string。但这确实不会发生。推理失败。
发生上下文类型的一个地方是通过使用上下文的预期返回类型来推断被调用泛型函数的类型参数。你的f2()情况是这样的,类似于:
declare function f<T>(): T;
const numGood: number = f(); // infers number for T
Run Code Online (Sandbox Code Playgroud)
该函数f()声称返回任何可能类型的值T。人们永远无法以“正常”方式T从调用中推断出f(),因为没有任何东西可以f()告诉您T应该是什么。但从上下文来看,编译器希望它返回 a number,因为numGood被注释为number。所以它有效。只要类型映射简单明了,这实际上可以通过多个函数调用向后传播:
declare function id<T>(x: T): T;
const numGood: number = id(id(id(id(f())))); // infers number for all Ts
Run Code Online (Sandbox Code Playgroud)
但是,在您的new B().onVisit情况下,您试图让编译器在给定其返回值的属性(或更确切地说,方法)的类型的情况下根据上下文推断函数(或更确切地说,构造函数调用)的类型参数。这显然破坏了上下文推理。它类似于:
declare function g<T>(): { a: T };
const numBad: number = g().a; // error!
Run Code Online (Sandbox Code Playgroud)
该函数g()返回类型为 的值{a: T}。即使在g().a需要 a 的上下文中使用number,这显然不会向后传播到 的调用站点g()。推理失败。T默认为unknown,并且您会收到错误。
我无法指出特定的 GitHub 问题或文档来概述何时可以或不可以期望上下文输入起作用。也有一些险些发生的情况,例如microsoft/TypeScript#29771,它描述了编译器执行上下文类型的能力受到限制的一些情况。但我还没有看到一个规范的答案说“f2是的,new B().onVisit不是”。
除此之外,如果遇到类型推断失败的情况,请考虑手动注释或自己指定类型。如果您不想new B()生成B<unknown>,则编写new B<{bar: number}>()它应该开始工作。乏味?当然。但至少它有效!