类型层级与底部类型
类型层级与底部类型是TypeScript类型系统中两个关键概念。理解它们有助于构建更健壮的类型约束,特别是在处理复杂类型关系和边界情况时。类型层级决定了类型之间的兼容性规则,而底部类型则代表了类型系统中最底层的类型。
类型层级的基本概念
TypeScript中的类型层级类似于集合论中的包含关系。当类型A可以赋值给类型B时,我们说A是B的子类型,或者说B是A的超类型。这种关系构成了类型系统的骨架。
let a: string = "hello";
let b: String = a; // 有效,因为string是String的子类型
基本类型的层级关系如下:
never
<: 所有类型- 字面量类型 <: 对应的原始类型
- 原始类型 <: 对应的包装类型
- 所有类型 <:
unknown
顶部类型与底部类型
在类型层级中,unknown
是顶部类型(top type),它可以接受任何类型的值。而never
是底部类型(bottom type),它不能接受任何值。
function acceptAny(value: unknown) {
// 可以接受任何参数
}
function rejectAll(value: never) {
// 不能接受任何实际参数
}
底部类型never
的特殊性在于:
- 它是所有类型的子类型
- 没有值可以赋值给
never
类型 - 函数返回
never
表示永远不会正常返回
类型兼容性规则
TypeScript使用结构化类型系统,类型兼容性基于类型的结构而非声明。这导致了一些有趣的层级关系:
interface Point {
x: number;
y: number;
}
interface Point3D extends Point {
z: number;
}
let p1: Point = { x: 0, y: 10 };
let p2: Point3D = { x: 0, y: 10, z: 20 };
p1 = p2; // 有效,Point3D是Point的子类型
联合类型与交叉类型的层级
联合类型和交叉类型在类型层级中表现出不同的行为:
type A = { a: number };
type B = { b: string };
// 交叉类型产生子类型
type AB = A & B; // AB <: A, AB <: B
// 联合类型产生超类型
type AOrB = A | B; // A <: AOrB, B <: AOrB
never的实际应用场景
底部类型never
在实际开发中有几个重要用途:
- 表示不可能出现的分支:
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function handleShape(shape: Circle | Square) {
switch (shape.kind) {
case "circle":
return shape.radius * Math.PI;
case "square":
return shape.sideLength ** 2;
default:
return assertNever(shape); // 确保所有情况都被处理
}
}
- 过滤联合类型:
type OnlyStrings<T> = T extends string ? T : never;
type Result = OnlyStrings<"a" | "b" | 1 | 2>; // "a" | "b"
- 表示永不返回的函数:
function infiniteLoop(): never {
while (true) {}
}
类型推断中的never
在条件类型推断中,never
会表现出特殊行为:
type TryInfer<T> = T extends { a: infer A } ? A : never;
type Test1 = TryInfer<{ a: string }>; // string
type Test2 = TryInfer<{}>; // never
类型层级与条件类型
条件类型利用类型层级来实现复杂的类型操作:
type IsSubtype<T, U> = T extends U ? true : false;
type Test1 = IsSubtype<string, String>; // true
type Test2 = IsSubtype<"literal", string>; // true
type Test3 = IsSubtype<never, unknown>; // true
分布式条件类型中的never
当never
出现在分布式条件类型中时,它会被自动过滤:
type ToArray<T> = T extends any ? T[] : never;
type Test = ToArray<string | number>; // string[] | number[]
type TestNever = ToArray<never>; // never
类型层级与映射类型
映射类型处理never
时也有特殊行为:
type FilterProperties<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K]
};
interface Example {
name: string;
age: number;
flag: boolean;
}
type StringProps = FilterProperties<Example, string>; // { name: string }
类型谓词中的never
在用户定义的类型保护中,never
可以用来表示不可能的情况:
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(item => typeof item === "string");
}
function processValue(value: string[] | number[]) {
if (isStringArray(value)) {
// value是string[]
} else {
// value是number[]
// 如果isStringArray类型谓词写得不正确,这里可能残留never类型
}
}
类型参数约束中的never
never
可以用作类型参数约束来表示"不允许任何类型":
function forbidden<T extends never>() {}
forbidden(); // 错误:需要类型参数
forbidden<string>(); // 错误:string不满足never约束
递归类型中的never
在处理递归类型时,never
常作为终止条件:
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type Test = DeepReadonly<{ a: { b: number } }>;
// { readonly a: { readonly b: number } }
类型层级与函数类型
函数参数类型是逆变的,而返回类型是协变的:
type Func = (arg: string) => void;
let func1: Func = (arg: string) => {};
let func2: Func = (arg: "literal") => {}; // 错误
let func3: Func = (arg: unknown) => {}; // 有效
类型层级与类继承
类继承关系也遵循类型层级规则:
class Animal {
move() {}
}
class Dog extends Animal {
bark() {}
}
let animal: Animal = new Dog(); // 有效
// animal.bark(); // 错误:Animal类型上没有bark方法
类型断言与层级
类型断言可以绕过类型检查,但需要满足一定的层级关系:
let value = "hello" as unknown as number; // 不推荐,但技术上可行
类型层级与模板字面量类型
模板字面量类型也参与类型层级:
type Color = "red" | "green" | "blue";
type UpperColor = Uppercase<Color>; // "RED" | "GREEN" | "BLUE"
let color: Color = "red";
let upperColor: UpperColor = "RED";
// color = upperColor; // 错误
// upperColor = color; // 错误
类型层级与索引访问
通过索引访问类型时,never
会出现在不存在的属性上:
type Example = { a: string; b: number };
type A = Example["a"]; // string
type C = Example["c"]; // 错误:属性"c"不存在
type All = Example[keyof Example]; // string | number
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:类型谓词与自定义类型守卫
下一篇:类型编程最佳实践