我最近遇到了以下打字脚本的情况,这种情况在实践中会经常发生:

interface Options {
  foo?: number;
  bar?: string;
}

const defaultOptions = {
  foo: 123,
  bar: 'abc',
} as const satisfies Options;

function f(options: Options) {
  const fullOptions = { // Type is inferred as {foo: number; bar: string} (regardless of satisfies)
    ...defaultOptions,
    ...options,
  } as const satisfies Required<Options>; // This is not guaranteed and should be marked as error!
  // The actual type is still just Options because the props of defaultOptions may be overwritten.

  console.log(fullOptions);

  return fullOptions.bar.toUpperCase();
}

f({foo: 456}); // Works
f({foo: 456, bar: undefined}) // Runtime error!

Playground Link

正如您所看到的,TypeScrip在satisfies Required<Options>部分中没有抱怨,尽管它应该这样做.这是故意的行为还是一个漏洞?

推荐答案

TypeScript的行为符合预期;请参阅microsoft/TypeScript#57408以获得权威答案.

一般来说,对于optional properties,类型脚本不会区分missingpresent with an 102 value的属性.对于很多目的来说,这是很好的.对于const opts = {foo: 456}const opts = {foo: 456, bar: undefined},opts.bar都是undefined.但在某些情况下,存在明显的差异,object spread的行为就是这样一种情况.

在很长一段时间里,事情就是这样.但有足够多的人对microsoft/TypeScript#13195版的功能请求得到了社区的大量支持感到不满.最终,TypeScrip引入了microsoft/TypeScript#43947中实现的the --exactOptionalPropertyTypes compiler option.在启用该编译器选项的情况下,如果从可选属性执行read操作,您仍将看到undefined,但如果try 将undefined to写入可选属性(除非undefined显式包含在其值类型中),则会出现错误.如果启用该选项,则以下代码将生成编译器警告:

f({foo: 456, bar: undefined}) // error!
// Argument of type '{ foo: number; bar: undefined; }' is not assignable to 
// parameter of type 'Options' with 'exactOptionalPropertyTypes: true'. 
// Type 'undefined' is not assignable to type 'string'.

而且,您不可能意外地用undefined覆盖现有的属性.


因此,如果您真的关心这一点,您可能想要启用--exactOptionalPropertyTypes.请注意,此编译器选项包含在the --strict suite of compiler options中.最初,这将是--strictOptionalProperties并包含在内.但如果你在microsoft/TypeScript#44421阅读讨论,你会发现它被认为是一个太大的突破性变化.问题是,有大量实际代码,包括TypeScrip自己的库,都使用可选属性.但是,既然{x?: string}{x?: string | undefined}在历史上是等价的,那么像{x?: string}这样的现有声明应该怎么办?原作者是打算允许还是不允许undefined?没有办法通过编程来判断.这意味着有人必须仔细判断每一处可选房产,并做出决定.否则,下一次发布的TypeScrip只会在所有地方强制执行"不允许undefined",很多东西都会被 destruct .

因此,无论是好是坏,他们将其从--strict移出,在microsoft/TypeScript#44626将其重新命名为--exactOptionalPropertyTypes,并告诉人们使用它的风险自负.也许有一天生态系统会准备好将旗帜包含在--strict中,但在此之前,除非发生这种情况,否则您需要决定您自己的代码中的好处是否大于风险.

Playground link to code

Typescript相关问答推荐

带有微前端的动态React路由问题

泛型函数类型验证

为什么typescript不能推断类的方法返回类型为泛型''

扩展函数签名中的参数类型,而不使用泛型

如果请求是流,如何等待所有响应块(Axios)

返回同时具有固定键和变量键的对象的函数的返回类型

PrimeNG日历需要找到覆盖默认Enter键行为的方法

如何使用类型谓词将类型限制为不可赋值的类型?

表单嵌套太深

基于泛型的条件接口键

->;Boolean类型的键不能分配给类型Never

将对象的属性转换为正确类型和位置的函数参数

在Cypress中,您能找到表格中具有特定文本的第一行吗?

如何将spread operator与typescripts实用程序类型`参数`一起使用

当数组类型被扩展并提供标准数组时,TypeScrip不会抱怨

声明遵循映射类型的接口

为什么在泛型方法中解析此promise 时会出现类型错误?

为什么我的函数arg得到类型为Never?

使用受保护的路由和入门来响应路由

如何为字符串模板文字创建类型保护