文章目录

TypeScript类型映射中的-?移除可选修饰符的用法

发布于 2026-04-28 06:18:34 · 浏览 5 次 · 评论 0 条

TypeScript类型映射中的-?移除可选修饰符的用法

在 TypeScript 中处理对象类型时,经常会遇到属性为可选的情况。但在某些业务场景下(如表单提交前的最终校验),我们需要强制要求这些属性必须存在。手动重新定义一个类型不仅繁琐,还容易导致代码冗余。TypeScript 的映射类型提供了 -? 修饰符,能够高效地将可选属性转换为必需属性。


1. 定义基础可选类型

首先,我们需要一个包含可选属性的基础类型,以便演示如何移除其可选修饰符。

定义一个用户信息接口 UserPartial,其中 name 是必需的,而 ageemail 是可选的。

interface UserPartial {
  id: number;
  name: string;
  age?: number;
  email?: string;
}

此时,如果你尝试创建一个缺少 name 的对象,TypeScript 会报错,但缺少 ageemail 则不会。


2. 理解映射类型语法

要修改属性的修饰符,必须使用映射类型的语法。其核心结构是对键名进行遍历,并在遍历过程中应用修饰符。

分析以下映射类型的基础结构:

type MappedType<T> = {
  [P in keyof T]: T[P];
};

在这个结构中:

  • T 是传入的泛型(即上一步的 UserPartial)。
  • [P in keyof T] 表示遍历 T 中所有的键名。
  • T[P] 表示获取该键名对应的原始类型。

默认情况下,这种写法会保留属性原有的 readonly?(可选)修饰符。要移除这些修饰符,需要在键名遍历部分显式添加操作符。


3. 使用 -? 移除可选修饰符

-? 操作符的作用是告诉 TypeScript:“在生成新类型时,移除键名的可选标记”。这相当于将 key?: Type 转换为 key: Type

编写一个工具类型 RequiredMy,利用 -? 将所有属性变为必需:

type RequiredMy<T> = {
  [P in keyof T]-?: T[P];
};

注意符号的位置,-? 紧跟在遍历变量 P 的后面,且位于中括号 [] 内部。- 号表示减去,? 表示可选修饰符。


4. 验证类型转换效果

将上一步定义的 UserPartial 传入 RequiredMy,生成一个新的全必需类型,并进行验证。

声明一个新的类型别名 UserStrict,应用该工具类型:

type UserStrict = RequiredMy<UserPartial>;

尝试创建一个对象来测试类型约束:

// ❌ 错误:属性 'age' 是必需的,但在类型 '{ id: number; name: string; }' 中缺失。
const testUser1: UserStrict = {
  id: 1,
  name: "Alice"
};

// ✅ 正确:所有属性都已提供
const testUser2: UserStrict = {
  id: 1,
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

此时,TypeScript 会强制检查 ageemail,如果缺少任何一个,代码编译将无法通过。


5. 对比内置 Required 工具类型

实际上,TypeScript 标准库中已经内置了实现相同功能的 Required 工具类型。理解自定义实现有助于掌握映射类型的底层逻辑。

查看标准库中 Required 的定义(逻辑与上述 RequiredMy 完全一致):

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

在实际项目中,通常直接使用内置的 Required 即可,无需重复造轮子。


6. 处理嵌套对象的强制必需

在某些复杂的数据结构中,可选属性可能本身就是一个对象。直接使用 -? 只能处理第一层的属性,无法递归处理深层对象。如果需要深度移除可选修饰符,需要结合条件类型进行递归处理。

编写一个深度必需类型 DeepRequired

type DeepRequired<T> = {
  [P in keyof T]-?: DeepRequiredNonObject<T[P]>;
};

// 辅助类型:如果是对象则继续递归,否则直接返回类型
type DeepRequiredNonObject<T> = T extends object
  ? DeepRequired<T>
  : T;

定义一个包含嵌套对象的复杂接口:

interface Company {
  name: string;
  address?: {
    city?: string;
    street?: string;
  };
}

应用 DeepRequired 进行转换:

type CompanyStrict = DeepRequired<Company>;

const myCompany: CompanyStrict = {
  name: "Tech Corp",
  address: {
    // ❌ 错误:属性 'city' 和 'street' 现在也是必需的
    // city: "New York" 
  }
};

通过这种方式,-? 的威力被扩展到了整个对象树,确保了数据结构的完整性。


7. 修饰符组合使用总结

在映射类型中,修饰符可以组合使用。除了 -?(移除可选),还有 +?(添加可选)、-readonly(移除只读)和 +readonly(添加只读)。默认行为通常是 +readonly+?(如果原属性是可选的,则保留;如果是必需的,保留必需)。

下表展示了不同修饰符对属性类型的影响:

修饰符组合 作用描述 示例结果
+? (默认) 保留或添加可选性 key?: Type
-? 移除可选性,变为必需 key: Type
-readonly 移除只读限制 允许修改 key

编写一个同时修改只读和可选性的类型示例:

type MutableRequired<T> = {
  -readonly [P in keyof T]-?: T[P];
};

interface ReadonlyPartial {
  readonly id?: number;
  readonly name?: string;
}

// 结果:id 和 name 变为非只读且必需
type FinalType = MutableRequired<ReadonlyPartial>;

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文