TypeScript 类型兼容性
前面小节中,介绍了 TypeScript 类型检查机制中的 类型推断
与 类型保护
,本节来介绍 类型兼容性
。
1. 解释
类型兼容性用于确定一个类型是否能赋值给其他类型。
let address: string = 'Baker Street 221B'
let year: number =
address = year // Error
代码解释: 第 3 行,类型 ‘number’ 不能赋值给类型 ‘string’。
2. 结构化
TypeScript 类型兼容性是基于结构类型的;结构类型只使用其成员来描述类型。
interface User {
name: string,
year: number
}
let protagonist = {
name: 'Sherlock·Holmes',
year: ,
address: 'Baker Street 221B'
}
let user: User = protagonist // OK
代码解释: 接口 User 中的每一个属性在 protagonist 对象中都能找到对应的属性,且类型匹配。另外,可以看到 protagonist 具有一个额外的属性 address,但是赋值同样会成功。
3. 比较两个函数
3.1 函数参数
先看一段代码示例:
let fn1 = (a: number, b: string) => {}
let fn2 = (c: number, d: string, e: boolean) => {}
fn2 = fn1 // OK
fn1 = fn2 // Error
代码解释:
第 4 行,将 fn1 赋值给 fn2 成立是因为:
- fn1 的每个参数均能在 fn2 中找到对应类型的参数
- 参数顺序保持一致,参数类型对应
- 参数名称不需要相同
第 5 行,将 fn2 赋值给 fn1 不成立,是因为 fn2 中的必须参数必须在 fn1 中找到对应的参数,显然第三个布尔类型的参数在 fn1 中未找到。
参数类型对应即可,不需要完全相同:
let fn1 = (a: number | string, b: string) => {}
let fn2 = (c: number, d: string, e: boolean) => {}
fn2 = fn1 // OK
3.2 函数返回值
创建两个仅是返回值类型不同的函数:
let x = () => ({name: 'Alice'})
let y = () => ({name: 'Alice', location: 'Seattle'})
x = y // OK
y = x // Error
let x : () => void
let y = () => 'imooc'
x = y // OK
4. 枚举的类型兼容性
枚举与数字类型相互兼容:
enum Status {
Pending,
Resolved,
Rejected
}
let current = Status.Pending
let num =
current = num
num = current
不同枚举类型之间是不兼容的:
enum Status { Pending, Resolved, Rejected }
enum Color { Red, Blue, Green }
let current = Status.Pending
current = Color.Red // Error
5. 类的类型兼容性
类与对象字面量和接口的兼容性非常类似,但是类分实例部分和静态部分。
比较两个类类型数据时,只有实例成员会被比较,静态成员和构造函数不会比较。
class Animal {
feet!: number
constructor(name: string, numFeet: number) { }
}
class Size {
feet!: number
constructor(numFeet: number) { }
}
let a: Animal
let s: Size
a = s! // OK
s = a // OK
类的私有成员和受保护成员会影响兼容性。 允许子类赋值给父类,但是不能赋值给其它有同样类型的类。
class Animal {
protected feet!: number
constructor(name: string, numFeet: number) { }
}
class Dog extends Animal {}
let a: Animal
let d: Dog
a = d! // OK
d = a // OK
class Size {
feet!: number
constructor(numFeet: number) { }
}
let s: Size
a = s! // Error
代码解释:
第 13 行,子类可以赋值给父类。
第 14 行,父类之所以能够给赋值给子类,是因为子类中没有成员。
最后一行,因为类 Animal 中的成员 feet 是受保护的,所以不能赋值成功。
6. 泛型的类型兼容性
泛型的类型兼容性根据其是否被成员使用而不同。先看一段代码示例:
interface Empty<T> {}
let x: Empty<number>
let y: Empty<string>
x = y! // OK
上面代码里,x 和 y 是兼容的,因为它们的结构使用类型参数时并没有什么不同。但是当泛型被成员使用时:
interface NotEmpty<T> {
data: T
}
let x: NotEmpty<number>
let y: NotEmpty<string>
x = y! // Error
代码解释: 因为第 4 行,泛型参数是 number 类型,第 5 行,泛型参数是 string 类型,所以最后一行赋值失败。
如果没有指定泛型类型的泛型参数,会把所有泛型参数当成 any 类型比较:
let identity = function<T>(x: T): void {
// ...
}
let reverse = function<U>(y: U): void {
// ...
}
identity = reverse // OK