2025最全TS手写题之partial/Omit/Pick/Exclude/Readonly/Required

随着 TS 在工作中使用的越来越广泛,面试的时候面试官也都会加上一两个 TS 的问题来了解候选人对于 TS 的熟悉程度,其中就有不少手写题目,比如笔者在字节的一次二面,面试官就问到了我如何实现一个 Pick,在小红书的一面,面试官问到了我如何实现一个 Omit。

接下来,让我们一起看看有哪些常见 TS 手写题吧。

手写 pick

Pick<T, K> ==> 从类型 T 中选出属性 K,构造成一个新的类型。

前置知识:type: 作用就是给类型起一个新名字,支持基本类型 、联合类型、元组 及其他任何你需要的手写类型,常用于联合类型,与接口一样,用来描述对象或函数的类型。

手写实现:

typescript 复制代码
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

使用示范:

ts 复制代码
// 从 User 中挑出 stature 来定义类型
interface User {
  stature: number
  age: number
}
const people: Pick<User, "stature"> = {
  stature: 185
}

手写 Exclude

Exclude 是 TypeScript 的一种类型操作符,用于从类型 T 中排除掉指定的类型 K。其定义为:

typescript 复制代码
type Exclude<T, K> = T extends K ? never : T;

这个定义中,使用了 TypeScript 的条件类型,它主要的义务是允许我们在类型系统中进行运算和推断。

具体来说,在这个条件类型中,我们首先声明了一个泛型类型参数 T 和 K。然后,使用了 extends 关键字将 T 与 K 进行对比。如果 T 是 K 的子类型,则返回 never 类型;否则返回 T 类型。

为什么要返回 never 呢?因为 never 代表的是永远不可能出现的类型,相当于一个空集合。这样,在使用 Exclude 操作符时,我们就可以将 T 中与 K 相同的类型排除掉,从而得到一个新的类型。

例如,假设我们有以下两个类型:

typescript 复制代码
type A = "a" | "b" | "c" | "d";
type B = "b" | "d" | "e" | "f";

我们可以使用 Exclude 操作符将 B 中包含的类型从 A 中排除掉,得到一个新的类型 C:

typescript 复制代码
type C = Exclude<A, B>; // "a" | "c"

因为 A 中包含了四个类型,而 B 中包含了 "b" 和 "d",所以将这两个类型从 A 中排除后,剩余的类型就是 "a" 和 "c"。因此,新的类型 C 就是 "a" 或者 "c"

手写 partial

Partial 是 TypeScript 内置的一个类型操作符,它用于将某个类型中每个属性设置为可选属性,这表示这些属性的值可以是 undefined 或者省略。

下面是 Partial 的代码:

typescript 复制代码
type Partial<T> = {
  [P in keyof T]?: T[P];
};

这个定义使用了 TypeScript 中的映射类型(Mapped Types)和索引访问类型(Index Access Types)。

首先,声明一个泛型类型 T 作为待操作的类型。然后,使用了映射类型语法,声明一个新类型,其属性名为 P,该属性名必须是 T 的属性名之一,属性值为该属性名在 T 类型中对应的类型。并且使用 ? 符号来将所有属性设置为可选属性。

该类型操作符的作用是让某个类型中的每个属性都变成可选属性,从而灵活地处理类型的使用,特别是在一些属性不是必需的场景下。

上面的示例中,我们先声明了一个 Person 接口,然后使用 Partial 操作符将其转化为可选类型 PartialPerson,接着创建三个可选类型的变量 person1、person2、person3。在 person1 中,只设置了 name 属性,而 age 属性是可选的,其值默认为 undefined;在 person2 中,没有设置任何属性,两个属性都是可选的,其值默认为 undefined;在 person3 中,设置了 name 和 age 两个必选属性,其值和 Person 类型一致。

手写 Omit

Omit 是 TypeScript 的一种类型操作符,用于从类型 T 中删去指定的属性 K。其定义为:

typescript 复制代码
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

这个定义中,使用了 TypeScript 的类型推导机制和 Exclude 操作符。

首先,我们声明了两个泛型类型参数 T 和 K,其中 T 表示要操作的类型,K 表示要删去的属性名。

然后,使用了 keyof T 操作符,获取了 T 类型的所有属性名组成的联合类型。接着,使用 Exclude 操作符将 K 从这个联合类型中排除掉,得到一个新的属性名联合类型。最后,使用 Pick 操作符从 T 类型中选取删去了属性 K 后的属性集合。

例如,假设我们有以下类型:

typescript 复制代码
interface Person {
  name: string;
  age: number;
  address: {
    city: string;
    street: string;
  };
}

type PersonNameAndAge = Omit<Person, "address">;

PersonNameAndAge 将会是一个新的类型,它只包含 Person 中的 name 和 age 两个属性。可以通过以下方式检查它的类型:

typescript 复制代码
const person: PersonNameAndAge = { name: "Alice", age: 30 };
person.name = "Bob"; // 可以修改
person.address = { city: "Shanghai", street: "Nanjing Road" }; // 报错,address 属性不存在

因此,Omit 操作符可以方便地帮助我们从一个类型中删去指定的属性,得到一个新的类型。

手写 Readonly

Readonly 是 TypeScript 内置的一个类型操作符,它用于将某个类型中每个属性设置为只读属性,这表示这些属性的值不能被修改。

下面是 Readonly 的代码:

typescript 复制代码
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

这个定义使用了 TypeScript 中的映射类型(Mapped Types)和索引访问类型(Index Access Types)。

首先,声明一个泛型类型 T 作为待操作的类型。然后,使用了映射类型语法,声明一个新类型,其属性名为 P,该属性名必须是 T 的属性名之一,属性值为该属性名在 T 类型中对应的类型。并且使用 readonly 关键字将属性设置为只读属性。

该类型操作符的作用是保护类型中的属性免于被错误的修改,特别是防止在多个地方引用该类型中的同一个属性时产生冲突。

以下是一个使用 Readonly 操作符的示例:

typescript 复制代码
interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

let person: ReadonlyPerson = { name: "Alice", age: 30 };
person.name = "Bob"; // 报错,因为 name 是只读属性
person.age = 40; // 报错,因为 age 是只读属性
person = { name: "Carol", age: 50 }; // 可以修改整体属性值

上面的示例中,我们先声明了一个 Person 接口,然后使用 Readonly 操作符将其转化为只读类型 ReadonlyPerson,接着创建一个只读类型的变量 person,并赋初值为 { name: "Alice", age: 30 }。由于 ReadonlyPerson 中的每个属性都是只读属性,所以我们不能修改 person 中的任何属性。如果要修改整个数据对象,我们需要分配一个新对象来覆盖 person。

手写 Required

Required 是 TypeScript 内置的一个类型操作符,它用于将某个类型的所有可选属性都转换为必选属性。

下面是 Required 的代码:

typescript 复制代码
type Required<T> = {
  [P in keyof T]-?: T[P];
};

这个定义使用了 TypeScript 中的映射类型(Mapped Types)和索引访问类型(Index Access Types)。

首先,声明一个泛型类型 T 作为待操作的类型。然后,使用了映射类型语法,声明一个新类型,其属性名为 P,该属性名必须是 T 的属性名之一,属性值为该属性名在 T 类型中对应的类型。并在属性名前使用 -? 符号来将所有属性设置为必选属性。

该类型操作符的作用是保证某个类型中的每个属性都必须有值,从而避免在使用该类型的地方出现意料之外的错误或者异常情况。

下面是一个使用 Required 操作符的示例:

typescript 复制代码
interface Person {
  name?: string;
  age?: number;
}

type RequiredPerson = Required<Person>;

let person: RequiredPerson = { name: "Alice", age: 30 };
person.name = "Carol"; // 正常
person.age = 50; // 正常
person = { }; // 报错,因为 name 和 age 是必选属性

上面的示例中,我们先声明了一个 Person 接口,然后使用 Required 操作符将其转化为必选类型 RequiredPerson,接着创建一个必选类型的变量 person,并赋初值为 { name: "Alice", age: 30 }。由于 RequiredPerson 中的每个属性都是必选属性,所以我们必须设置 person 中的所有属性。如果我们忘记设置某个属性,或者设置错误的属性名,就会在编译期间发生错误,避免出现运行期错误。

相关推荐
Dovis(誓平步青云)2 分钟前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
穆梓兰煊4 分钟前
TS5.7 vs 裸JS:AI应用少3坑
前端
嘎嘎风5 分钟前
MiniMind 学习笔记之 07 模型怎么学会猜词(旋钮是怎么被拧动的)
算法·源码阅读
大连好光景7 分钟前
ReAct的原理?与CoT的区别是啥?
react·cot
协皓家具13 分钟前
2026年广州岛台吧台椅厂家有啥新变化
大数据·运维·python·devops
榆瓷13 分钟前
闲着没事,我把 WebGPU 的样板代码封成了一个库
前端
大连好光景15 分钟前
Agent完整工作过程
react·agent完整工作流程
1360967572320 分钟前
页面打不开不是 Nginx 的错
前端·后端
yuniko-n20 分钟前
【Java】字符串相关坑点
java·开发语言·python
Terra.K22 分钟前
Spring AI day1(SSE+AI)
java·人工智能·spring·springai