TS 类型检查机制:TS 编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。作用是辅助开发,提高开发效率。
1、类型推断
不需要指定变量的类型(函数的返回值类型),TS 可以根据某些规则自动地为其推断出一些类型。
1.1 基础类型推断
let a ; // 如果不进行赋值,则 TS 推断类型为 ang
let b = 1; // 此时,TS 推断 b 的类型为 string
let c = []; // c 的类型被推断为 any[],即以 any 为类型的数组
let d = (x = 1) => x + 1; // 为 x 添加默认值 1,则会推断入参类型为 number,根据计算结果,推断返回值为 number。
1.2 最佳通用类型推断
当需要从多个类型中推断出一个类型时, ts 就会尽可能推断出一个兼容当前所有类型的通用类型。
let b = [1, null]; //b 的类型为:(number | null)[];
1.3 上下文类型推断
1.1 、1.2 讲的都是根据结果推断数据类型,即从右向左推断。那么从左向右推断就属于上下文类型推断。通常会发生在事件处理中。
window.onkeydown = (event) => {
console.log(event.code);
console.log(event.button) ; // Error,event 无 button 属性,编译时会报错
} // 此处 event 会被推断为 KeyboardEvent 类型,并且能够提示我们 event 具有哪些属性
1.4 类型断言
当 TS 的类型推断不符合预期,此时 TS 提供了一个方法,允许覆盖 TS 的推断,就是类型断言。
let foo = {};
foo.bar = 1; //此处会报错,应为 bar 未被定义
// 定义一个接口
interface Foo {
bar: number;
}
let foo = {} as Foo; // 类型断言的使用,不建议这样使用
let foo1: Foo = { // 建议使用
bar : 1
}
2、类型兼容性
当一个类型 Y 可以被赋值给另一个类型 X 的时候,我们就可以说类型 X 兼容类型 Y。如:
let s : string = 'a';
// 如果此时将tsconfig.json 中的 strictNullChecks 设置为 false, 那么此时 s = null 是不会报错的。
我们可以任务 null 类型是 string 的子类型,也就是说 string 兼容 null 类型。
类型兼容性的例子,广泛存在于接口、函数、类中。
- 接口兼容
interface X {
a: any;
b: any;
}
interface Y {
a: any;
b: any;
c: any;
}
let x: X = {a: 1, b: 2}
let y: Y = {a: 1, b: 2, c: 3}
x = y
y = x // Error
Y 接口具备 X 接口的所有属性,虽然 Y 有额外的属性,但仍旧可认为是 X 的类型。源类型具有目标类型的必要属性,就可以赋值。简而言之,成员少的会兼容成员多的。
- 函数的兼容
type Handler = (a: number, b: number) => void
function hof(handler: Handler) {
return handler
}
函数兼容需要满足的几个条件如下:
- 参数个数的要求
- 参数类型
- 返回值类型
// 1)参数个数
let handler1 = (a: number) => {}
hof(handler1)
let handler2 = (a: number, b: number, c: number) => {}
hof(handler2) // Error
// 可选参数和剩余参数
let a = (p1: number, p2: number) => {}
let b = (p1?: number, p2?: number) => {}
let c = (...args: number[]) => {}
// 固定参数可以兼容可选参数和剩余参数的
a = b
a = c
// 可选参数不兼容固定参数和剩余参数的
b = a // Error
b = c // Error
// 剩余参数可以兼容固定参数和可选参数
c = a
c = b
// 2)参数类型
let handler3 = (a: string) => {}
hof(handler3) // Error , 类型不兼容
interface Point3D {
x: number;
y: number;
z: number;
}
interface Point2D {
x: number;
y: number;
}
let p3d = (point: Point3D) => {}
let p2d = (point: Point2D) => {}
p3d = p2d
p2d = p23 // Error,参数成员个数多的要兼容参数成员个数少的
// 3) 返回值类型
let f = () => ({name: 'Alice'})
let g = () => ({name: 'Alice', location: 'Beijing'})
f = g
g = f // Error
2.2 函数重载
function overload(a: number, b: number): number // 目标函数
function overload(a: string, b: string): string // 目标函数
function overload(a: any, b: any): any {} // 源函数
// 目标函数的参数个数要多于原函数,否则函数重载报错
2.3 枚举兼容
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }
let fruit: Fruit.Apple = 1
let no: number = Fruit.Apple // 枚举和 number 之间是可以兼容的
let color: Color.Red = Fruit.Apple // Error 枚举之间是不兼容的
2.4 类兼容性
静态成员和构造函数是不参与比较的。如果两个类具有相同实例成员,那么他们的实例可以相互兼容。
class A {
constructor(p: number, q: number) {}
id: number = 1
private name: string = ''
}
class B {
static s = 1
constructor(p: number) {}
id: number = 2
private name: string = ''
}
class C extends A {}
let aa = new A(1, 2)
let bb = new B(1) // aa 和 bb 是完全兼容的,如果 class A 和 class B 具有同名私有成员,那么 aa 和 bb 不可完全兼容
let cc = new C(1, 2)
aa = cc
cc = aa // 可以看出父类和子类是完全兼容的
2.5 泛型兼容性
2.5.1 泛型接口
interface Empty<T> {}
let obj1: Empty<number> = {};
let obj2: Empty<string> = {};
obj1 = obj2 // 泛型接口中无任何成员,所以 obj1 和 obj2 可以完全兼容
interface Empty<T> {
value: T
} // 若接口定义中包含成员,那么 obj1 和 obj2 不兼容
2.5.2 泛型函数
let log1 = <T>(x: T): T => {
console.log('x')
return x
}
let log2 = <U>(y: U): U => {
console.log('y')
return y
}
log1 = log2 // 如果两个泛型的定义相同,但是没有指定类型参数,那么他们之间是可以相互兼容的