类型兼容与赋值

为什么要有类型兼容

因为实际工作中,往往无法做到类型一致

比如在上面的 JS 代码中,假设 runTask 函数的参数只接受具有 abc 这 3 个 key 的对象,但在一般情况下,即使我们多传一个 d,也不会报错

你有的,我都有,则我能代替你;
y 有的,x 都有,则 x 兼容 y

简单类型的兼容

类型小的兼容类型大的

注意:图中的大小圆应当是包含关系,即小圆应该在大圆内部,此处为了展示清晰将小圆挪到了外部,后面的图示亦如此

集合 hi 小于集合 A,因此将 hi 赋值给 a 不报错

普通对象的兼容

属性多的兼容属性少的

对象属性越多,限制越多,表示的集合范围越小,所以其实还是类型小的兼容类型大的

注意:对象兼容的前提是必须得存在共同的属性,此处两个对象共同的属性是 nameage

兼容的情况下,作为参数也不报错

1
2
3
4
5
6
7
8
9
type Person = { name: string; age: number }
let user = { name: 'ClariS', age: 18, id: 1, email: 'xxx@yyy.com' }

let p: Person = user // 不报错

const f1 = (p: Person) => {
console.log(p)
}
f1(user) // 不报错

接口的兼容

子接口兼容父接口

函数的兼容

函数的兼容比较复杂,需要考虑参数和返回值

参数个数不同

存在相同类型的参数的函数,参数少的兼容参数多的

如何理解上图中的兼容关系?(图中绿色箭头代表可以兼容,红色箭头表示不能兼容)

如何理解上图中的代码?(图中绿色箭头代表可以兼容,红色箭头表示不能兼容)

主要看划线的右边参数的部分:

  1. 从上往下看,箭头全绿(ab 都是 number
  2. 从下往上看,存在红色的箭头(参数 s 没有可以兼容的参数)

因此 接受一个参数的函数 兼容 接受两个参数的函数,反过来则不行

为什么容忍参数变少呢?从下例中可以窥探一二

在 JS 中,少写参数是很常见的事情
在 JS 中,少写参数是很常见的事情

参数类型不同

对参数要求少的兼容对参数要求多的

和参数的兼容关系正好相反
和参数的兼容关系正好相反

从前面的例子我们知道,MyMouseEvent 是兼容 MyEvent 的,这里函数的兼容关系就正好反过来了。

返回值不同

不考虑参数类型的情况下,函数的兼容关系和返回值的兼容关系保持一致

思考:如果函数的参数和返回值同时存在且兼容关系相反呢?

实际工作中的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface Event {
timestamp: number;
}
interface MyMouseEvent extends Event {
x: number;
y: number;
}
function listenEvent(eventType: string, handler: (n: Event) => void) {
/* ... */
}
// 我们希望这样用,但会报错
listenEvent("click", (e: MyMouseEvent) => console.log(e.x + "," + e.y));

// 因此只能这样用
listenEvent("click", (e: Event) =>
console.log((e as MyMouseEvent).x + "," + (e as MyMouseEvent).y)
);

// 还可以这么用
listenEvent("click", ((e: MyMouseEvent) => console.log(e.x + "," + e.y)) as (
e: Event
) => void);

// 这个就太离谱了,也会报错
listenEvent("click", (e: number) => console.log(e));

在不关闭 TS 严格检查的情况下,可以通过设置 "strictFunctionTypes": false 来避免函数报错

1
2
3
4
5
6
7
8
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"strictFunctionTypes": false,
...
}
}
设置 strictFunctionTypes: false 后
设置 strictFunctionTypes: false 后

特殊类型的兼容

顶类型(Top Type)与底类型(Bottom Type)

在 TS 中,顶类型(Top Type)表示可以接受任何值的类型,而底类型(Bottom Type)表示不可能有任何值的类型。

顶类型通常称为通用超类型,因为它是系统中所有其他类型的超类型。TS 提供了两种顶类型:anyunknownany 类型表示可以接受任何值,但它不提供任何类型检查unknown 类型也可以接受任何值,但在使用之前必须先进行类型检查

底类型通常称为通用子类型,因为它通常是系统中所有其他类型的子类型。TS 提供了一种底类型:nevernever 类型表示不可能有任何值,它用于表示永远不会返回值的函数或抛出异常的函数的返回类型。

1
2
3
4
5
6
7
8
9
// Top Type 示例
let topType: unknown;
topType = 1; // 可以赋任何值
topType = 'hello';

// Bottom Type 示例
function throwError(): never {
throw new Error('Error');
}

(●'◡'●)ノ♥