类型兼容性
类型兼容性用于确定一个类型是否能赋值给其他类型。
TypeScript 类型系统 相关内容可以查看前置文章,支持一下拜托了~~~
若我们可以将任何 T 类型的值替换为 S 类型的值而安全使用,那么我们称 S 是 T 的子类型。
这里说的安全使用,可以理解成不产生运行时错误。
type A = { a: number }
type B = { a: number, b: number }
const b: B = { a: 123, b: 123 }
const a: A = b
在上面的代码中,B 类型的变量 b
,成功赋值给了 A 类型的变量 a
,没有出现编译错误,运行时也不产生错误,我们就可以认为 B 是 A 的子类型。
变量 a
期望得到一个 A 类型的值,但是传入了一个更复杂约束更大的类型 B ,也是符合 A 类型的预期的(有属性名为 a
,且 a
的类型是 number
),所以不会产生编译和运行过程中的错误。
笔者自己的理解就是小范围类型是大范围类型的子类型。
名义上的兼容性
在其他面向对象语言中,比如 Java、 C#,他们都是名义上的类型系统,只要名字相同,就是相同的类型。他们的子类型是根据继承来定义的。
在 TypeScript 内置的标称类型之间,也存在着兼容性:
如 string
类型与 number
类型不兼容:
let str: string = '123';
let num: number = 123;
str = num;
num = str;
上面示例中,string
与 number
是两种不兼容的类型,所以不能相互赋值。
在TypeScript中,有 any
、 unknown
和 never
三个很特殊的类型:
any
可以看作所有类型的父类型;unknown
可以看作所有类型的父类型;never
可以看作所有类型的子类型;
any
、unknown
叫做顶类型或者超类型,never
叫做底类型。
在用户自定义的两个类型,即使类型名字是不一样的,但是它们表达的类型是一样的,或者存在包含关系,那么它们在结构上存在兼容性关系。
结构上的兼容性
一个类型如果具有另一个类型的所有的公共属性,并且这些属性的类型也兼容,则前者可以赋值给后者。例如,如果一个对象有 name
和 age
属性,它可以赋值给一个只需要 name
的类型。
type S = { name: stirng }
type T = { name: string, age: number }
let a: S = { name: 'Rick' }
let b: T = { name: 'Morty', age: 14 }
a = b // 不报错
b = a // 报错
上面代码中,变量 a
赋值给变量 b
会报错,变量 b
赋值给变量 a
不会报错,我们就可以认为类型 T 是类型 S 的子类型。
如果一个类型有可选属性,那么这些属性在赋值时可以被省略。例如,一个类型定义为 { name?: string }
可以接受一个没有任何属性的对象,或可以接受任何包含 name
属性的对象。
type S = { name?: string }
type T = { name: string, age: number, gender: string }
let a: S = { name: 'Morty' }
let b: T = { name: "Rick", age: 70, gender: "male" }
a = b // 不报错
a = {} // 不报错
如果一个类型有索引签名,那么另一个类型必须有相同类型的索引签名,或者有更严格的索引签名。拥有更严格索引签名的类型就是父类型。
type S = {
[name: string]: number
} // 范围更小
type T = {
[name: string]: number | string
} // 范围更大
let a: S = { name: 123 }
let b: T = { name: 'Morty' }
// 子类型可以赋值给父类型
b = a // 不报错
上面代码中,类型 S 比类型 T 有更严格的索引签名(T 可以拥有更多种的类型),故类型 S 是子类型,类型 T 是父类型。
本文最后
第一次写有点深奥的东西(自己都没怎么搞懂),如果有任何内容上的问题,希望大佬们可以指出来,希望不会误导到其他人。