高阶类型体系专题测试
考察知识点
- 类型兼容性: 结构子类型、方差、 bivariance
- 类型守卫进阶: 自定义类型守卫、类型谓词、标签联合
- 条件类型深入: infer 高级用法、类型推断、递归类型
- 类型系统原理: 类型检查算法、编译时类型、运行时类型
- 高级类型模式: 类型级编程、Church 编码、类型计算
关于 keyof 操作符在 TypeScript 中的行为,以下哪些说法是正确的?
A. keyof string[] 的结果包含 number 类型,因为数组索引是数字
B. keyof readonly number[] 的结果与 keyof number[] 完全相同
C. 当一个类包含 private 字段时,keyof 该类的结果仍然只包含 public 成员的键
D. keyof typeof someObject 可以获取对象值的类型,而不仅仅是键的联合类型
考虑以下代码:
function greet(name: string): void {}
class User { constructor(public name: string) {} }
const obj = { a: 1, b: 2, c: 3 };
enum Color { Red, Green, Blue }
关于 typeof 操作符的使用,以下哪些说法是正确的?
A. typeof greet 的类型是 (name: string) => void
B. typeof User 的类型是一个构造函数类型,可以使用 new 实例化
C. typeof obj.a 的类型是 number
D. typeof Color.Red 的类型是 number 而不是 Color
const config = {
host: 'localhost',
port: 8080,
ssl: true,
timeout: 3000
};
type ConfigKey = !!1_答案!!;
// ConfigKey 应为 "host" | "port" | "ssl" | "timeout"
请填写正确的类型表达式,使 ConfigKey 类型为对象 config 的所有键的联合类型。
以下代码存在类型错误,请分析错误原因并提供修复方案:
const routes = {
getUsers: '/api/users',
getUserById: '/api/users/:id',
createUser: '/api/users'
};
// 目标:创建一个类型安全的 getRoute 函数
// 只接受 routes 对象中存在的键,并返回对应的路径字符串
function getRoute(routeName: keyof routes): string {
return routes[routeName];
}
// 期望的调用:
// getRoute('getUsers') // 正确
// getRoute('getProducts') // 应报类型错误
请指出代码中的错误,并给出正确的实现。
关于 TypeScript 中的索引访问类型(Indexed Access Types),以下哪些说法是正确的?
A. 给定 type Arr = [string, number, boolean],Arr[0] 的类型是 string
B. 给定 type Obj = { a: { b: { c: number } } },Obj['a']['b']['c'] 的类型是 number
C. 索引访问类型 T[K] 中,K 必须是 keyof T 的子类型,否则会报类型错误
D. 给定 type M = { [key: string]: number },M[string] 的类型是 number
考虑以下类型定义:
interface APIResponse {
data: { users: User[]; posts: Post[] };
meta: { page: number; total: number };
error?: string;
}
type DataSection = APIResponse['data'];
type MetaPage = APIResponse['meta']['page'];
type AllKeys = keyof APIResponse;
type PossibleValues = APIResponse[keyof APIResponse];
关于上述类型推导,以下哪些说法是正确的?
A. DataSection 的类型是 { users: User[]; posts: Post[] }
B. MetaPage 的类型是 number
C. AllKeys 的类型是 "data" | "meta" | "error"
D. PossibleValues 的类型是 { users: User[]; posts: Post[] } | { page: number; total: number } | string | undefined
interface EventMap {
click: { x: number; y: number };
keydown: { key: string; code: number };
resize: { width: number; height: number };
}
function handleEvent<K extends keyof EventMap>(
eventName: K,
handler: (event: !!1_答案!!) => void
): void {
// ...
}
请填写正确的类型表达式,使得 handler 的参数类型能够根据 eventName 的类型自动推导为对应的事件数据类型。例如,当 eventName 为 "click" 时,event 参数应为 { x: number; y: number } 类型。
以下代码存在类型问题,请分析原因并修复:
interface Database {
users: { id: number; name: string }[];
posts: { id: number; title: string }[];
comments: { id: number; text: string }[];
}
type TableName = keyof Database;
function getRecord<Table extends TableName>(
db: Database,
table: Table,
id: number
): Database[Table][number] {
return db[table].find(record => record.id === id);
}
// 问题:上述函数的返回类型推导是否正确?
// find 方法返回 T | undefined,但返回类型声明为 Database[Table][number]
// 请修复类型不匹配问题
请指出类型不匹配的根本原因,并提供至少一种修复方案。
关于 TypeScript 中的类型守卫(Type Guards),以下哪些说法是正确的?
A. 类型谓词 x is T 的返回值必须是 boolean,否则 TypeScript 会报错
B. asserts x is T 守卫函数不需要返回值,当断言失败时应抛出异常
C. 使用 in 操作符进行类型收缩时,TypeScript 会自动将对象类型收窄为包含该属性的类型
D. instanceof 类型守卫可以用于任何接口类型,不需要右侧是可构造的类
考虑以下代码:
type Shape = Circle | Rectangle | Triangle;
interface Circle { kind: 'circle'; radius: number }
interface Rectangle { kind: 'rectangle'; width: number; height: number }
interface Triangle { kind: 'triangle'; base: number; height: number }
function getArea(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return 0.5 * shape.base * shape.height;
default:
const _exhaustive: never = shape;
return _exhaustive;
}
}
关于上述代码,以下哪些说法是正确的?
A. 如果向 Shape 联合类型中添加 Square 类型但不添加对应的 case 分支,TypeScript 会在 default 行报类型错误
B. _exhaustive: never = shape 利用了 TypeScript 的穷举检查模式,确保所有联合类型分支都被处理
C. 将 default 分支改为 throw new Error() 后,_exhaustive 行可以省略,因为 TypeScript 知道该分支不会返回
D. 如果删除 default 分支,函数仍然能正确处理所有 Shape 类型,因为 TypeScript 的控制流分析会自动推导
interface AdminUser {
role: 'admin';
permissions: string[];
username: string;
}
interface GuestUser {
role: 'guest';
username: string;
}
type User = AdminUser | GuestUser;
// 自定义类型守卫:检查用户是否拥有特定权限
function isAdmin(user: User): !!1_答案!! {
return 'permissions' in user && Array.isArray(user.permissions);
}
function checkAccess(user: User) {
if (isAdmin(user)) {
// 此处 user 类型应被收窄为 AdminUser
console.log(user.permissions); // 合法
}
}
请填写正确的类型注解,使 isAdmin 函数成为自定义类型守卫函数,在 if 分支内 TypeScript 能自动将 user 类型收窄为 AdminUser。
以下两种类型守卫写法在处理数据验证时有何区别?请分析各自的类型推导行为和使用场景:
// 写法一:is 类型谓词
function isString(value: unknown): value is string {
return typeof value === 'string';
}
// 写法二:asserts 类型谓词
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Expected a string');
}
}
// 使用场景
function processA(input: unknown) {
if (isString(input)) {
console.log(input.toUpperCase());
}
}
function processB(input: unknown) {
assertIsString(input);
console.log(input.toUpperCase());
}
请说明:
- 两种写法在类型收窄行为上的差异
- 各自适用的编程场景
asserts谓词的返回值要求
关于 TypeScript 条件类型 T extends U ? X : Y,以下哪些说法是正确的?
A. 条件类型中的 extends 与泛型约束中的 extends 含义相同,都表示子类型关系判断
B. T extends U ? X : Y 中的 X 和 Y 可以是类型推导表达式,包含对 T 和 U 的引用
C. 当 T 是联合类型时,条件类型会自动对联合类型的每个成员分别应用条件判断,这称为分布式条件类型
D. 嵌套条件类型如 A extends B ? C extends D ? E : F : G 的解析顺序是从左到右,等价于 (A extends B ? (C extends D ? E : F) : G)
考虑以下条件类型定义:
type NonNullable<T> = T extends null | undefined ? never : T;
type Flatten<T> = T extends any[] ? T[number] : T;
type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N;
type IfNever<T, Y, N> = [T] extends [never] ? Y : N;
关于上述定义,以下哪些说法是正确的?
A. NonNullable<string | null | undefined> 的结果是 string
B. Flatten<string[] | number> 的结果是 string | number
C. IfAny 利用了 TypeScript 中 any 类型与任何类型交叉后仍为 any 的特性
D. IfNever 使用 [T] 元组包裹是为了阻止条件类型的分布式行为
// 定义一个条件类型:如果 T 是字符串则返回模板字符串类型,否则返回 T 本身
type Stringify<T> = T extends string ? !!1_答案!! : T;
// 期望行为:
// Stringify<'hello'> => `hello` (模板字面量类型)
// Stringify<number> => number
// Stringify<string> => string
请填写正确的类型表达式,使 Stringify<T> 在 T 是字符串字面量类型时返回模板字面量类型 `${T}`,将字面量提升为模板类型。
TypeScript 内置的 Exclude<T, U> 和 Extract<T, U> 工具类型的定义如下:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
请分析:
- 当
T = 'a' | 'b' | 'c',U = 'a' | 'b'时,Exclude<T, U>和Extract<T, U>分别得到什么结果?推导过程是什么? - 为什么这两个类型定义中使用了
never,但最终结果中不会出现never? - 如果将定义改为非分布式写法
[T] extends [U] ? never : T,结果会有什么不同?
关于 TypeScript 4.1+ 引入的映射类型 as 子句(键重映射),以下哪些说法是正确的?
A. type Mapped<T> = { [K in keyof T as Uppercase<string & K>]: T[K] } 可以将对象的所有键转换为大写
B. 在 as 子句中可以返回 never 来过滤掉某些键
C. as 子句中可以使用条件类型来根据键名或值类型进行条件过滤和重映射
D. { [K in keyof T as K extends on${infer R} ? R : never]: T[K] } 可以提取以 on 开头的键并去除 on 前缀
考虑以下映射类型定义:
type CreateMutable<T> = {
-readonly [K in keyof T]: T[K];
};
type Concrete<T> = {
[K in keyof T]-?: T[K];
};
type Getters<T> = {
readonly [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type PartialBy<T, K extends keyof T> = {
[P in keyof T as P extends K ? never : P]: T[P];
} & {
[P in K]?: T[P];
};
关于上述定义,以下哪些说法是正确的?
A. CreateMutable<{ readonly name: string }> 会移除 readonly 修饰符,name 变为可变属性
B. Concrete<{ name?: string }> 会移除可选修饰符,name 变为必填属性
C. Getters<{ name: string }> 生成 { readonly getName: () => string }
D. PartialBy<{ a: number; b: string }, 'a'> 的类型是 { b: number } & { a?: number }
interface User {
name: string;
age: number;
active: boolean;
}
// 定义一个映射类型,使 User 的所有属性值类型变为可选(允许 undefined)
type OptionalValues<T> = {
[K in keyof T]: !!1_答案!!
};
// 期望行为:
// type Result = OptionalValues<User>;
// Result 应为 { name: string | undefined; age: number | undefined; active: boolean | undefined }
请填写正确的类型表达式,使 OptionalValues<T> 将类型 T 的每个属性值类型扩展为包含 undefined 的联合类型。
TypeScript 内置的 Pick<T, K> 和 Omit<T, K> 工具类型定义如下:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
请分析:
Pick类型中的K extends keyof T约束的作用是什么?如果去掉这个约束会有什么问题?Omit如何通过组合Pick和Exclude实现"排除指定键"的功能?- 如果直接使用映射类型写
Omit,如下所示,与原定义有什么区别?TypeScripttype OmitDirect<T, K extends keyof T> = { [P in keyof T as P extends K ? never : P]: T[P]; };
📝 发现内容有误?点击此处直接编辑
长按或扫描二维码,立即体验