为什么 TypeScript 记录可分配给具有明显不同类型的部分对象?

Why is a TypeScript record assignable to a partial object with clearly different types?

考虑以下程序。 Playground.

const x: Record<string, string> = { foo: "bar" };
const y: { baz?: number | undefined } = x; // TypeScript doesn't complain

在上面的示例中,我本以为 TypeScript 会抱怨 string 无法分配给 number | undefined。但是,上面的代码类型检查。为什么?

Typescript 是一种结构类型语言,即如果两种类型具有相似的结构,则它们可以相互分配。

// Example 1 - with no Excess property check (Because of implicit narrow type Record<string, string>)

const x: Record<string, string> = { foo: "bar" };

const y: { baz?: number } = x

// Example 2 - with Excess property check

const y1: { baz?: number } = { foo: 'bar' } // Error


// Also to prove Record<string,string> is assignable to { [k: string]: any }

type Assignable = Record<string, string> extends { [k: string]: any  } ? true: false // true

Code Plyaground

在这里当你做 x: Record<string, string> 时,它基本上意味着 x: { [k: string]: string } 比实际的 { foo: "bar" }

更宽

对于显式注释的类型,Excess property 不会进行检查,这意味着我们可以向它提供额外的属性以及所需的属性 baz

但是由于 baz 是部分的并且不需要任何其他多余的属性可以添加到它并且 baz 将被推断为未定义。因此打字稿不会抱怨

为了证明我的观点,我们可以检查一下 Record<string, string> extends { baz? :number } ? true: false 将是 true

但是,如果我们直接尝试分配相同 Record 类型的对象字面量类型,打字稿会抱怨,因为现在确实发生了多余的 属性 检查。

Object literals get special treatment and undergo excess property checking when assigning them to other variables, or passing them as arguments. If an object literal has any properties that the “target type” doesn’t have, you’ll get an error.

旁注:

Record<string, string> 不是此方案的首选类型。 这样做的惯用方法是使用像 { [k: string]: string }

这样的索引签名

因为给定 x: Record<string, string>x.foo 显然是 compile-time 处的字符串,但实际上可能是 string | undefined. 这是 --strictNullChecks 运作方式上的差距