从零开始的 TypeScript 学习(六)—— 类型兼容性

30 阅读3分钟

类型兼容性

类型兼容性用于确定一个类型是否能赋值给其他类型。

TypeScript 类型系统 相关内容可以查看前置文章,支持一下拜托了~~~

若我们可以将任何 T 类型的值替换为 S 类型的值而安全使用,那么我们称 ST 的子类型。

这里说的安全使用,可以理解成不产生运行时错误。

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,没有出现编译错误,运行时也不产生错误,我们就可以认为 BA 的子类型。

变量 a 期望得到一个 A 类型的值,但是传入了一个更复杂约束更大的类型 B ,也是符合 A 类型的预期的(有属性名为 a,且 a 的类型是 number),所以不会产生编译和运行过程中的错误。

笔者自己的理解就是小范围类型是大范围类型的子类型。

名义上的兼容性

在其他面向对象语言中,比如 Java、 C#,他们都是名义上的类型系统,只要名字相同,就是相同的类型。他们的子类型是根据继承来定义的。

在 TypeScript 内置的标称类型之间,也存在着兼容性:

string 类型与 number 类型不兼容:

let str: string = '123';
let num: number = 123;str = num;
num = str;

上面示例中,stringnumber 是两种不兼容的类型,所以不能相互赋值。

在TypeScript中,有 anyunknownnever 三个很特殊的类型:

  • any可以看作所有类型的父类型;
  • unknown可以看作所有类型的父类型;
  • never可以看作所有类型的子类型;

anyunknown叫做顶类型或者超类型never叫做底类型

在用户自定义的两个类型,即使类型名字是不一样的,但是它们表达的类型是一样的,或者存在包含关系,那么它们在结构上存在兼容性关系。

结构上的兼容性

一个类型如果具有另一个类型的所有的公共属性,并且这些属性的类型也兼容,则前者可以赋值给后者。例如,如果一个对象有 nameage 属性,它可以赋值给一个只需要 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 是父类型。

本文最后

第一次写有点深奥的东西(自己都没怎么搞懂),如果有任何内容上的问题,希望大佬们可以指出来,希望不会误导到其他人。