8. 类型兼容
约 1123 字大约 4 分钟
2025-05-21
一、简介
1、概念
类型的兼容性由实际的结构(属性和方法)决定,而非显式的类型声明名称。一般和结构化原则放在一起介绍。
结构化原则主要用在类型检查上,类型检查是理解 TypeScript 类型系统的关键概念。
A = B
上述代码,我们可以理解为:类型上 A
兼容 B
,B
的值符合 A
的要求。
为什么对象看起来成员更多,反而更严格呢?
我们可以这样去看,对象类型{ name: string; age: number }
相当于 {name:string} & {age:number}
,是多个条件的交集。而联合类型 string | number | boolean
是并集。从拓扑学的角度来讲,相交的条件越多就越严格,越严格的类型越容易被其他类型兼容,成为子类型。
2、主要使用场景
类型检查主要发生在两类场景:
- 值传递:函数的参数传递、返回值类型检查;变量的声明、赋值;类的实例化,。
- 类型操作:属性索引;类的继承;接口的实现、合并、继承;类型别名的合并;类型转化、类型断言;
上述场景都需要涉及到类型检查,或者说类型兼容性检查。
二、详细介绍
1、成员数量检查
成员数量检查主要检查的是成员列表。
如果要 A
兼容 B
, 首先应该满足是 B
包含了 A
的全部成员,不可缺少,可以增加额外成员。
class A = {
name: string;
age: number;
};
class B = {
name: string;
age: number;
sex: string;
};
2、属性的类型检查
属性的类型检查主要检查同名属性的类型是否兼容,一般来说类型更加严格的属性,越容易被兼容。
string | number
兼容string
{name:string}
兼容{name:string, age:number}
例如下边的代码,A
兼容 B
,prop1
和 prop2
的类型都兼容。
class A {
prop1: string | number;
prop2: {
name: string;
};
}
class B {
prop1: number;
prop2: {
name: string;
age: number;
};
}
let obj: A = new B();
3、方法的类型检查
方法的类型检查主要集中在参数数量、参数类型、返回值类型的检查。其中参数主要是逆变,返回值是协变。即参数的数量允许减少,参数的类型允许更严格,返回值的类型允许更严格。
函数有两种写法:
- 一种是传统表达式写法,参数的类型允许双变,即可以更严格,也可以更宽松。
- 一种是函数表达式写法:要求参数类型只能逆变,即只能更严格。
例如下边代码中:speak
写法只能是逆变,hold
和 breath
的写法支持双变。
class Parent {
name: string;
}
class Child extends Parent {
age: number;
}
class Person {
speak = (val: Child) => {
return "speak";
};
hold(val: Child) {
return "hold";
}
breath(val: Parent) {
return "breath";
}
}
class Man extends Person {
speak = (val: Parent) => {
return "speak";
};
override hold(val: Parent) {
return "hold";
}
override breath(val: Child) {
return "breath";
}
}
4、可选性检查
可选的可以变必选,但是必选的不能变可选。
interface Options {
name: string;
debug?: boolean;
log: string;
}
// 子接口可以使可选属性变为必需
interface StrictOptions extends Options {Interface 'StrictOptions' incorrectly extends interface 'Options'.
Types of property 'log' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'. name: string;
debug: boolean; // 现在是必需的
log?: string;
}
5、可访问性检查
可访问性主要涉及到类的继承。子类中,成员的可访问性也可以和父类保持一致,也可以更宽松。但是子类不能定义父类的同名私有成员。
主要表现在以下几个方面:
- 父类中是
public
,在子类中只能为public
,不可以被改变。 - 父类中是
protected
,在子类中可以为public
和protected
,可以被提升。 - 子类不可以定义父类中已存在的同名私有
private
成员。
class Parent {
public a: string = "a";
protected b: string = "b";
private c: string = "c";
}
class Child1 extends Parent {Class 'Child1' incorrectly extends base class 'Parent'.
Property 'a' is protected in type 'Child1' but public in type 'Parent'. protected declare a: string;
}
class Child2 extends Parent {
public declare b: string;
// @annotate: b 的类型变宽松
}
class Child3 extends Parent {Class 'Child3' incorrectly extends base class 'Parent'.
Types have separate declarations of a private property 'c'. private declare c: string;
}
6、静态属性检查
静态属性的检查和属性类型检查类似。但是静态属性和普通属性的检查是分开的。 静态属性只和静态属性去匹配检查。
class Options {
version: number;
static version: string = "1.0.0";
}
// 子接口可以使可选属性变为必需
class StrictOptions extends Options {
declare version: number;
static version: string = "1.0.1";
}
7、只读属性不检查
只读属性不影响类型检查。
class Options {
readonly version: number;
name: string;
}
Class StrictOptions extends Options {
declare version: number;
declare readonly name: string;
}
更新日志
e7112
-1于