我们并不总是需要为变量指定具体的类型,ts会根据一些规则推断变量类型。
基础类型推断
如果我们在定义一个基础类型时,只为其赋值,并未具体指定类型:
let a = 1; // 自动推断 a 为number类型
let b = "str"; // 自动推断 b 为string类型
如果不是用let
关键字,而是用const
,情况会有些不同:
const foo = 1; // foo类型为1
const bar = "str"; // bar类型为"str"
let baz = foo; // baz类型为number
因为const
指定的变量表示是常量,不可变。所以ts能在基础类型的基础上进一步推断出更具体的类型,这个和字面量类型相似。而let
指定的变量是可变类型,所以只能推断出基础类型。
如果一个类型的赋值加入了条件判断,如三元表达式,它会被推断为联合类型:
// fn是一个返回值为boolean的方法
let x = fn() ? "string" : fn() ? true : 1;
// x 类型为 string | number | boolean
// 如果使用 const
const x = fn() ? "string" : fn() ? true : 1;
// x 类型为 true | "st" | 1
枚举和类也是同样效果
数组类型
如果我们有以下数组变量arr
:
const arr = [1, "string", undefined];
此时ts会将它推断为(string | number | undefined)[]
类型,因为该数组中包含了number
,string
,undefined
等类型,所以ts会推断数组是由这几个类型的联合类型组成。
函数类型
如果我们指定了函数的类型,那么在给函数赋值时,就不在需要显式指定,ts会自动推断:
let fn: (a: number, b: number) => void;
// 此时a,b会自动被推断为number类型
fn = (a, b) => {
a + b;
};
// 第二个参数为string类型,类型不兼容
fn(1,"st")
如果我们没有显式指定返回值类型,ts会根据参数类型和return
语句推断:
// 返回结果会被推断为string类型
function callback(a: number, b: string) {
return a + b;
}
// Type 'string' is not assignable to type 'number'
const result: number = callback(1, "s");
如果我们没有显式指定函数参数类型,会被默认推断为any
:
// a为any,所以我们在函数内访问a的任意属性都是合法的
function anyFn(a) {
a.name;
}
这是不安全的,我们希望尽量避免使用any
,可以在配置文件中开启noImplicitAny
。此时对于这种推断出为any
的类型会报错:
但是显式指定参数为any
是合法的。
结构化
如果我们声明一个不带类型的对象:
const data = {
name: "xxx",
age: 20,
};
// 此时的data会被推断为一个匿名类型
// {
// name: string;
// age: number;
// }
// type '1' is not assignable to type 'string'
data.name = 1;
如果我们想声明一个对象,并且对象的属性都是只读的,使用as const
:
const data = {
name: "xxx",
age: 20,
} as const;
// 此时的data会被推断为
// {
// readonly name: "xxx";
// readonly age: 20;
// }
就算此时有一个接口类型与之结构相同,data
也不会被推断为该接口类型,可以理解为此时ts自动为data
推断出一个匿名的接口类型。