【问题标题】:TypeScript: Difference Generic Constraints VS Parameter TypeTypeScript:差异通用约束与参数类型
【发布时间】:2021-07-30 13:10:25
【问题描述】:

我不明白通用约束和普通参数类型之间的区别。 在第一种情况下,我明白我不能调用 si.g(),因为我严格地将 si 键入为 SomeInterface 类型。但是,在第二个示例中 S 扩展 SomeInterface 并且根据我的解释,扩展意味着它需要具有 f() 但可以包含更多属性。但错误是一样的。为什么?

interface SomeInterface {
    f();
}

function test1(si: SomeInterface) {
    si.f()
    // si.g() -> Property 'g' does not exist on type 'SomeInterface'
}

function test2<S extends SomeInterface>(si: S) {
    si.f()
    // si.g() -> Property 'g' does not exist on type 'SomeInterface'
}

【问题讨论】:

  • S extends SomeInterface 表示S 可以是SomeInterface 的子类型,但不能保证子类型将具有g()。可能是interface OtherInterface extends SomeInterface { e(); }
  • S 可以是函数的 调用者 想要的 SomeInterface 的任何子类型。因此,函数的实现者需要编写适用于S的所有可能规范的代码,包括SomeInterface本身,它没有g()方法。
  • @VLAZ 是的,即使在从 SomeInterface 扩展的接口中,也不能保证。这就是函数 test2 中的情况,TS 确保找到 g() 并引发错误,就像 TS 在 test1 中所做的那样。但是除了语法之外,test1 和 test2 有什么区别呢?
  • @user3601578 在这种情况下 - 什么都没有。示例代码对于泛型来说太简单了。

标签: typescript generics parameters


【解决方案1】:

Typescript 需要检查您传递的类型是否包含函数 g。所以你需要做这样的事情:

interface ExtendedInterface extends SomeInterface {
 g();
}

...

function test2<E extends ExtendedInterface>(ei: E) {
    si.f()
    si.g()
}

当然,你也可以这样做:

function test3(ei: ExtendedInterface) {
    ei.g();
    ei.f();
}

这将在功能上与带有泛型的版本相同。

那么有什么区别呢?这里什么都没有,但是泛型以更复杂的代码为代价为您提供了更多功能。例如,泛型允许您声明一个受另一个类型参数约束的类型参数。

【讨论】:

  • 谢谢,但您的代码与以下代码有何不同:function test2(ei: ExtendedInterface) { si.f() si.g() }
【解决方案2】:

...扩展意味着它需要具有 f() 但可以包含更多属性。但错误是一样的。为什么?

因为它可能或可能没有更多属性,如果有,它们可能有任何名称或任何类型。 TypeScript 不知道那些属性是什么,所以在test2 内部,TypeScript 只知道siSomeInterface

您一开始说您不了解类型参数(&lt;S extends SomeInterface&gt;si: S)与仅将类型放在参数上(si: SomeInterface)之间的区别。 在您的具体示例中没有真正的区别;无论哪种方式,TypeScript 都将si 处理为SomeInterface 类型。然而,在一般的情况下,类型参数可以让你做一些你不能通过直接输入参数来做的事情。这是一个例子:

function pluck<Type extends object, Key extends keyof Type>(array: Type[], key: Key) {
    return array.map(element => element[key]);
}

const objects = [{a: 2, b: "s"}, {a: 3, b: "x"}];
const x = pluck(objects, "a"); // `x` is `number[]`
const y = pluck(objects, "b"); // `y` is `string[]`

Playground link

我可能弄错了,但我认为没有类型参数(或类型断言)你不能这样做。

【讨论】:

  • 谢谢,但是“扩展对象”(也称为“扩展 {}”)是从一个空值扩展而来的,基本上允许一切。这就是它被当前 ESLint 配置阻止的原因。
  • @user3601578 - 这只是一个例子,不是答案的重点,而是概念。如果它困扰你,从字面上删除这两个词。 (另请注意,ESLint 配置是意见,而不是事实。)
  • 是的,考虑到对象和 {} 可以有任何键并且键始终是字符串,我可以轻松地将您的通用函数替换为: function pluck(array: object[], key: string) { return array.map(element => element[key]); }
  • @user3601578 - 首先,that doesn't work。但即使它确实如此(如果你将 object 更改为 Record&lt;string, any&gt;,它也不是类型安全的。类型安全是使用 TypeScript 的全部要点。如果你不想要类型安全,只需编写 JavaScript 代码. (另外:属性键并不总是字符串,即使在 JavaScript [Symbol] 中,更不用说具有字符串字面量类型和 keyof 概念的 TypeScript。)
  • @user3601578 - 我根本没有更改任何游乐场默认值:我去了游乐场并复制和粘贴。并且有理由对那些花时间试图帮助你的人无礼。 “如果我打开非空对象,我的代码也会出错。” 你是什么意思?你能分享一个游乐场链接吗?如果代码有问题,我想知道。
猜你喜欢
  • 1970-01-01
  • 2017-05-19
  • 2018-03-30
  • 2020-02-28
  • 1970-01-01
  • 2020-01-15
  • 2021-11-01
  • 2019-09-23
  • 1970-01-01
相关资源
最近更新 更多