网站搜索

为什么 TypeScript 泛型会抑制类型错误?


请看以下 TypeScript 示例。

function expectInterface(args: { foo: string }): void {}

function passthrough<T>(fields: T): T {
  return fields
}

expectInterface({
  foo: 'bar',
  badKey: 'baz', // error
})

expectInterface(passthrough({
  foo: 'bar',
  badKey: 'baz', // no error
}))

为什么 passthrough 会导致 badKey 上的类型错误被抑制?

答案#1

badKey 上的错误被抑制,因为 TypeScript 使用的是 结构化类型 系统。这意味着 如果类型 X 至少包含类型 Y 所有的字段,那么类型 X 与类型 Y 兼容。有关更具体的细节,请参见 TypeScript 文档中的类型兼容性部分。

为了清楚起见,我们将你的接口命名为HasFoo和HasFooAndBadKey。

interface HasFoo {
  foo: string;
}

interface HasFooAndBadKey {
  foo: string;
  badKey: string;
}

你可以看到 HasFooAndBadKey 包含字段 foo,这意味着它与类型 HasFoo 兼容。因此这段代码是有效的,并且可以顺利编译:

const withBadKey: HasFooAndBadKey = {
  foo: "bar",
  badKey: "baz"
};

const item: HasFoo = withBadKey;

然而,使用字面量以相同方式分配会失败,并出现错误 Object literal may only specify known properties, and 'badKey' does not exist in type 'HasFoo'.

const other: HasFoo = {
  foo: "bar",
  badKey: "baz" // Error
}

因此,你的错误不是由泛型引起的。你的错误发生的原因是你只能在字面量中指定已知属性。你的泛型直通函数不会导致编译时错误,因为在这种情况下,它返回的是与HasFoo类型兼容的非字面量值。将直通函数显式地类型化为接受HasFoo并传递同一个包含badKey的字面量,将会产生相同的错误。

interface HasFoo {
  foo: string;
}
interface HasFooAndBadKey {
  foo: string;
  badKey: string;
}

function expectInterface(args: HasFoo): void {}

function passthrough<T>(fields: T): T {
  return fields
}

// Expecting literal HasFoo, which can only have a foo field
expectInterface({
  foo: 'bar',
  badKey: 'baz', // error
})

const item: HasFooAndBadKey = {
  foo: 'bar',
  badKey: 'baz'
};

expectInterface(item); // No error

// Non-literal value returned by passthrough. The non-literal
// is type compatible with HasFoo, so no error occurs.
expectInterface(passthrough<HasFooAndBadKey>({
  foo: 'bar',
  badKey: 'baz', // no error
}))

// Now passthrough shows an error becuase we told it to expect a HasFoo,
// and this HasFoo literal cannot contain any other key than 'foo'.
expectInterface(passthrough<HasFoo>({
  foo: 'bar',
  badKey: 'baz', // error
}))