TypeScript类型推论(Type Inference)

要完全理解类型推论需要完整理解类型上下文,并且理解TS对于是否可以使用类型推论是基于静态分析完成的。
上下文类型应用在许多地方。常见的例子包括函数调用的参数赋值的右手端位置类型断言对象和数组的成员,和返回语句。上下文类型还充当最佳公共类型中的候选类型。
TS中需要为每个JS名字规定类型,而名字出现在对应的上下文中则会自动获得类型,若没有对应的上下文,这个名字则会自动获得类型any。

名字:通过声明语句声明的名字,例如var、let、const、function a() {}、class A {}、import A from ‘.a’、函数参数等,都会在JS环境中添加一个名字,而TS可以给这个名字指定类型。

在JS中名字的声明是可以在上面提到的常见例子指定的位置,函数参数调用、赋值右手端位置、对象数组成员、返回语句。

函数调用的参数

interface Cb {
    
    
	(a: number): void;
}
interface Fn {
    
    
	(cb: Cb): void;
}

const fn: Fn = function (cb) {
    
    }

fn(function (a) {
    
     // 这里a的类型是number
	console.log(a + 1)
})

因为fn这个名字是类型Fn,而Fn类型的入参是类型Cb,所以在fn使用匿名函数作为入参调用的时候TS可以知道这个匿名函数对应的位置是类型Cb,换句话说匿名函数当前的类型上下文是Cb。而Cb要求入参是number类型,所以推断出匿名函数的参数a是number类型。

赋值的右手端位置

interface Cb {
    
    
	(a: number): void;
}

const fn: Cb = function (a) {
    
    
	console.log(a + 1)
}

fn是类型Cb,因为Cb要求入参是number类型,所以TS推断出对应位置匿名函数的入参是number类型。

对象和数组的成员

interface Cb {
    
    
	(a: number): void;
}
interface Obj {
    
    
	fn: Cb
}
const obj: Obj = {
    
    
	fn: function (a) {
    
    
		console.log(a + 1)
	}
}

obj是类型Obj,而Obj具有属性fn是类型Cb。因为Cb要求入参是number类型,所以TS推断出对象obj.fn右手端对应位置匿名函数的入参是number类型。

返回语句

interface Cb {
    
    
  (a: number): void
}

interface Fn {
    
    
  (): Cb
}

const fn: Fn = function () {
    
    
  return (a) => {
    
    
    console.log(a + 1)
  }
}

在这例子里,返回的匿名函数获得类型上下文Cb,而Cb要求入参是number,所以匿名函数的入参被推断出是number类型。

上面的几种类型都可以认为名字出现在了赋值的右手端,而被复制的名字可以给这个值提供对应的类型上下文。

类型断言

interface Fn {
    
    
  (a: number): void;
}

const fn = function (a) {
    
    
  console.log(a + 1);
} as Fn;

在这里匿名函数赋值给变量fn而fn本身是没有类型的,所以没办法推断匿名函数的入参a的类型,但是我们使用类型给这个匿名函数指定了类型上下文Fn,让TS具有了推算参数a的依据,得出参数a是number类型。

小结

类型推断起作用的条件是名字出现在对应的上下文位置,而这个上下文可以通过赋值操作的左手端提供,也可以使用类型断言直接提供。这样TS可以根据对应的类型推断出对应变量的类型。

一些意外

interface Fn {
    
    
	(a: number): void;
}

function fn(a) {
    
    }

let a: Fn = fn

在这个例子里,TS无法推断出函数fn的参数a是number类型,因为赋值操作提供的类型上下文在右手端,而fn这个函数声明的位置,并不是右手端。对应的右手端位置并不是函数声明,而是函数声明的引用。所以TS无法静态分析出fn中a的类型。
还有一个原因是fn的使用并不唯一,在这里我们将fn赋值给Fn类型的变量a,我们完全可以将它再赋值给Fn1类型的变量b,所以这种情况下fn中a的类型是由运行时决定的,无法静态分析出来。

interface Fn {
    
    
	(a: number): void;
}

function fn(a) {
    
    }

let a: Fn = fn

interface Fn1 {
    
    
	(a: number): void;
}

let b: Fn1 = fn

这里Fn1要求入参a的类型是string,所以fn的入参a既可能是number也可能是string,只有运行这个函数的时候才能确定知道参数a是什么类型。所以在这个例子中a会自动获得隐式类型any。

进行如下修改:

interface Fn {
    
    
	(a: number): void;
}

let a: Fn = function fn(a) {
    
    }

在这里fn是一个表达式,并不会再当前环境中新建一个名字fn,换句话说这个fn不会再用在别的地方,并且a这个名字直接出现在了右手端对应位置,所以这个fn函数可以得到类型上下文Fn,从而推断出参数a的类型是number。

Guess you like

Origin blog.csdn.net/letterTiger/article/details/118435572