Last updated on September 2, 2022

TypeScript 拥有非常强大的类型推导能力,不仅会在声明一个变量时自动推导其类型,也会基于函数内部逻辑自动推导其返回值类型,在使用 typeofinstanceof 等工具时自动地收窄类型(可辨识联合类型)等等。这些类型推导其实有一个共同点:它们的推导依赖开发者的输入 ,比如变量声明、函数逻辑、类型保护都需要开发者的输入。

除吃之外 TypeScript 中还存在着另一种类型推导:上下文类型(Contextual Typing)

上下文类型

type CustomHandler = (name: string, age: number) => boolean

// 推导出了参数类型
const handler: CustomHandler = (arg1, arg2) => true

虽然没有为 handler 的各个参数声明类型,但它们已经获得了正确的类型。

除了参数类型,返回值类型同样也会纳入管控:(箭头函数、函数表达式效果一样)

declare const struct: {
  handler: CustomHandler
}
// 不能将类型“void”分配给类型“boolean”
struct.handler = (name, age) => {}

在这里,参数的类型基于其上下文类型中的参数类型位置来进行匹配,arg1 对应到 name ,所以是 string 类型,arg2 对应到 age,所以是 number 类型。这就是上下文类型的核心理念:基于位置的类型推导。同时,相对于基于开发者输入进行的类型推导,上下文类型更像是反方向的类型推导,也就是基于已定义的类型来规范开发者的使用

在上下文类型中,实现的表达式可以只使用更少的参数,而不能使用更多。因为上下文类型基于位置的匹配,一旦参数个数超过定义的数量,那就没法进行匹配了。

// 正常
const handler: CustomHandler = (arg1) => true

// X
const handler: CustomHandler = (arg1, arg2, arg3) => true

除了一维层面上的推导,上下文类型也可以进行「嵌套」情况下的类型推导

declare let func: (raw: number) => (input: string) => any

// raw → number
func = (raw) => {
  // input → string
  return (input) => {}
}

在某些情况下,上下文类型的推导能力也会失效。

class Foo {
  foo!: number
}

class Bar extends Foo {
  bar!: number
}

let f1: { (input: Foo): void } | { (input: Bar): void }
// 参数“input”隐式具有“any”类型
f1 = (input) => {}

期望 input 被推导为 Foo | Bar 类型,但 TypeScript 中的上下文类型目前暂时不支持这一判断方式。

这种情况下只能使用一个联合类型参数的函数签名:

let f2: { (input: Foo | Bar): void }
// Foo | Bar
f2 = (input) => {}

但如果联合类型中将这两个类型再嵌套一层,此时上下文类型反而是正常的