← 返回题目列表

exactOptionalPropertyTypes 会改变可选属性的什么语义?

中等 第 30 / 32 题 更新于 2026/07/29
TypeScripttsconfig可选属性

简化版

exactOptionalPropertyTypes 会让可选属性 foo?: T 更精确地表示“属性可以不存在”,而不是默认等同于“属性存在且值可以是 T | undefined”。

开启后,如果你想显式赋值 undefined,需要把属性类型写成 foo?: T | undefined。它能让配置合并、JSON 序列化、PATCH 请求等场景更贴近运行时语义。

详细版

不开启时:

type Options = {
  theme?: "dark" | "light";
};

const options: Options = {
  theme: undefined
};

开启后,上面会报错。因为 theme?: T 表示可以没有 theme,不表示可以显式写 theme: undefined

如果确实允许:

type Options = {
  theme?: "dark" | "light" | undefined;
};

这能区分“没传”和“传了 undefined”。

完整版教学

一、可选属性有两层含义

可选属性首先表示属性可以缺失。

type User = {
  nickname?: string;
};

运行时有两种不同对象:

const a = {};
const b = { nickname: undefined };

这个选项关注的是“属性不存在”和“属性存在但值为 undefined”的区别。

二、默认语义比较宽松

默认情况下,TypeScript 常把可选属性当成读取时可能是 T | undefined,写入时也允许显式写 undefined

const user: User = {
  nickname: undefined
};

这对宽松 JS 兼容友好,但不够精确。

三、开启后的写入更精确

开启 exactOptionalPropertyTypes 后:

type Config = {
  cache?: boolean;
};

const c1: Config = {};
const c2: Config = { cache: true };
// const c3: Config = { cache: undefined };

如果业务允许显式 undefined,要写出来:

type Config = {
  cache?: boolean | undefined;
};

四、读取时仍要处理 undefined

即使开启后,读取可选属性时仍然可能得到 undefined。

function use(config: Config) {
  const cache = config.cache;
}

因为属性可能不存在,读取结果自然可能是 undefined。

操作影响
写入 { key: undefined }更严格
读取 obj.key仍可能是 undefined
判断 "key" in obj可区分是否存在

五、为什么对配置合并有价值

配置对象里,缺失和显式 undefined 可能语义不同。

const defaults = { retry: 3 };
const user = { retry: undefined };

有些合并逻辑会把 undefined 当成覆盖值,有些会忽略。精确可选属性能迫使类型设计说清楚。

六、和接口请求的关系

PATCH 请求常用“字段缺失表示不修改”,而 field: undefined 可能无法序列化,或者被后端解释成清空。

type PatchUser = {
  name?: string;
};

这个类型更适合表示“可以不传 name”。如果允许清空,可能需要 name?: string | null 明确建模。

七、常见误区与追问

  • 误区:可选属性就等于 T | undefined 开启精确选项后,写入语义不再等价。
  • 误区:开启后读取可选属性不需要判空。 属性可能不存在,读取仍可能是 undefined。
  • 误区:显式 undefined 和缺失属性运行时完全一样。 inObject.keys、序列化和合并逻辑可能不同。
  • 误区:所有可清空字段都应该用 undefined。 接口语义常用 null 明确表达清空。
  • 追问:如果确实允许 undefined 怎么写? 写成 foo?: T | undefined
  • 追问:这个选项适合什么项目? 对配置、接口补丁、序列化语义要求清晰的项目。

八、加强记忆

记忆对比:

foo?: T                -> may be absent
foo?: T | undefined    -> may be absent or explicitly undefined

回答时抓住“缺失”和“显式 undefined”的区别,再落到配置合并和 PATCH 请求,会比只背 tsconfig 名称更有说服力。