15. 类型运算
约 1129 字大约 4 分钟
2025-06-03
1、交换顺序
改变成员类型的顺序不影响联合类型的结果类型。
type T0 = string | number;
type T1 = number | string;
2、使用分组运算符
对部分类型成员使用分组运算符不影响联合类型的结果类型。
type T0 = (boolean | string) | number;
type T1 = boolean | (string | number);
3、类型化简
联合类型的成员类型如果存在兼容性,可以进行化简。
例如,有联合类型 boolean | true | false
。其中,true
类型和 false
类型是 boolean
类型的子类型,因此可以将 true
类型和 false
类型从联合类型中消去。最终,联合类型 boolean | true | false
的结果类型为 boolean
类型。
type T0 = boolean | true | false;
// 所以T0等同于 T1
type T1 = boolean;
4、规则优先级
&
的优先级高于 |
,使用括号 ( )
可以改变优先级。
A & B | C & D
// 该类型等同于如下类型:
(A & B) | (C & D);
分配律
A & (B | C)
// 等同于
(A & B) | (A & C);
一个稍微复杂的类型等式。
(A | B) & (C | D)
// 等同于
A & C | A & D | B & C | B & D
(string | 0) & (number | "a");
// 等同于
(string & number) | (string & "a") | (0 & number) | (0 & "a");
never | "a" | 0 | never;
// 等同于
"a" | 0;
function extend<T extends object, U extends object>(
first: T,
second: U
): T & U {
const result = <T & U>{};
for (let id in first) {
(<T>result)[id] = first[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<U>result)[id] = second[id];
}
}
return result;
}
const x = extend({ a: "hello" }, { b: 42 }); // { a: 'hello', b: 42 }
5、never
类型被视为空集
never
可以视为空集。
type NeverIntersection = never & string; // never
type NeverUnion = never | string; // string
很适合在交叉类型中用作过滤。
type OnlyStrings<T> = T extends string ? T : never;
type RedOrBlue = OnlyStrings<"red" | "blue" | 0 | false>;
// Equivalent to: "red" | "blue"
6、unknown
类型的吸收规则
在联合类型中,unknown
吸收除 any
以外所有类型。
type T10 = unknown | null; // unknown
type T11 = unknown | undefined; // unknown
type T12 = unknown | null | undefined; // unknown
type T13 = unknown | string; // unknown
type T14 = unknown | string[]; // unknown
type T15 = unknown | unknown; // unknown
type T16 = unknown | any; // any
在交叉类型中,每种类型都吸收 unknown
。
type IntersectionType1 = unknown & null; // null
type IntersectionType2 = unknown & undefined; // undefined
type IntersectionType3 = unknown & string; // string
type IntersectionType4 = unknown & number[]; // number[]
type IntersectionType5 = unknown & any; // any
unknown
类型不能直接拿来使用,可以使用 as
断言缩小 unknown
类型的范围,然后才可以用于其他类型的赋值。
const value: unknown = "Hello World";
const someString: string = value as string;
const otherString = someString.toUpperCase(); // "HELLO WORLD"
7、联合类型、交叉类型的插值效果
联合类型,可以产生插值的效果。
type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
等同于
type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
交叉类型,也可以产生插值的效果。
type Brightness = "dark" & "light";
type Color = "blue" & "red";
type BrightnessAndColor = `${Brightness}-${Color}`;
等同于
type BrightnessAndColor = "dark-red" & "light-red" & "dark-blue" & "light-blue"
8、交叉类型影响函数重载的解析顺序
当交叉类型涉及调用签名重载或构造签名重载时便失去了“加法交换律”的性质。因为交叉类型中成员类型的顺序将决定重载签名的顺序,进而将影响重载签名的解析顺序。
interface Clickable {
register(x: any): void;
}
interface Focusable {
register(x: string): boolean;
}
type ClickableAndFocusable = Clickable & Focusable;
type FocusableAndFocusable = Focusable & Clickable;
function foo(
clickFocus: ClickableAndFocusable,
focusClick: FocusableAndFocusable
) {
let a: void = clickFocus.register("foo");
let b: boolean = focusClick.register("foo");
}
此例
- 第 8 行和第 9 行使用不同的成员类型顺序定义了两个交叉类型。
- 第 15 行,调用
register()
方法的返回值类型为void
,说明在ClickableAndFocusable
类型中,Clickable
接口中定义的register()
方法具有更高的优先级。 - 第 16 行,调用
register()
方法的返回值类型为boolean
,说明FocusableAndFocusable
类型中Focusable
接口中定义的register()
方法具有更高的优先级。
此例也说明了调用签名重载的顺序与交叉类型中成员类型的定义顺序是一致的。
9、交叉类型的包含关系
只要交叉类型中任意一个成员类型包含了属性签名 M,那么交叉类型也包含属性签名 M。
interface A {
a: boolean;
}
interface B {
b: string;
}
// 交叉类型 A & B 如下
{
a: boolean;
b: string;
}
10、交叉类型的可选属性
若交叉类型的属性签名 M
在所有成员类型中都是可选属性,那么该属性签名在交叉类型中也是可选属性。否则,属性签名 M
是一个必选属性。
interface A {
x: boolean;
y?: string;
}
interface B {
x?: boolean;
y?: string;
}
// A & B 的结果如下
{
x: boolean;
y?: string;
}
更新日志
2025/8/24 08:17
查看所有更新日志
e7112
-1于