【问题标题】:TypeScript partial of a generic type泛型类型的 TypeScript 部分
【发布时间】:2020-04-04 09:09:27
【问题描述】:

我想将类型定义为显式属性和泛型类型的混合,在匹配键的情况下显式属性优先。以下是我的尝试,但在指示的行上出现错误 - 谁能解释为什么或者这是一个 tsc/编译器错误?

// Takes properties in A and B. For matching properties, the types in A are used.
type Mix<A, B> = {
  [K in keyof B | keyof A]: K extends keyof A
    ? A[K]
    : K extends keyof B
    ? B[K]
    : never
}

type Versionable = { version: number }

function test<T>(): void {
  const version1: Partial<Mix<Versionable, T>>['version'] = 1 // compiles - version type is correctly inferred as number | undefined
  const version2: Partial<Mix<Versionable, T>>['version'] = undefined // compiles
  const version3: Partial<Mix<Versionable, T>>['version'] = '1' // does not compile as expected

  const obj1: Partial<Mix<Versionable, T>> = { version: 1 } // DOES NOT COMPILE.... WHY??
  const obj2: Partial<Mix<Versionable, T>> = { version: undefined } // compiles
  const obj3: Partial<Mix<Versionable, T>> = { version: '1' } // does not compile as expected
  const obj4: Partial<Mix<Versionable, T>> = {} // compiles
  obj4.version = 1 // compiles
}

【问题讨论】:

  • 非常有趣。看起来问题出在泛型 T 上,因为一切都适用于静态类型集。首先想到的是,如果 B 未知,则问题可能在于 A 和 B 之间的交叉点,编译器无法解决此问题。尝试过这种类型,现在没有运气:type Mix&lt;A, B&gt; = { [K in keyof A]: A[K] } &amp; { [K in Exclude&lt;keyof B, keyof A&gt;]: B[K] }
  • 问题是这样的结构有什么意义,因为我们知道你不能在test函数的主体中放置T的任何属性,因为在这个级别是未知的。这意味着此函数始终仅适用于 Partial&lt;Versionable&gt; 类型。你能在这里分享一下 type T 的目的是什么吗?
  • 同一类型的另一个版本是 - type Mix&lt;A, B&gt; = A &amp; Pick&lt;B, Exclude&lt;keyof B, keyof A&gt;&gt;。但问题仍然是test 中类型 T 的目的
  • 嗨,简而言之,我有一个使用泛型 T 参数化的 API,但它对 T 有某些要求,例如可版本化。但是 T extends { version: number } 例如不会削减它,因为我不能在指定 filter/where 子句时做 T 的一部分并将其设置为 { version: 1 } 。 (因为在 T 版本中可能会扩展为比数字更具体的类型,只说数字 2 和 3,因此编译器不会让我将其设置为 1)。因此,除了我需要的属性外,我需要一个全是 T 的类型。希望这是有道理的!

标签: typescript typescript-generics


【解决方案1】:

我认为这里的行为与microsoft/TypeScript#13442 中的行为相同;编译器几乎没有看到可以分配给Partial&lt;SomethingDependingOnAGenericTypeParam&gt; 的具体值,除了具有undefined 类型的属性(这负责obj2 的行为)或空对象(这负责obj4' s 行为)。

这个限制通常是可以辩护的,因为这些分配通常是不安全的:

function unsafeCaught<T extends { a: string }>() {
  const nope: Partial<T> = { a: "" }; // CORRECT error  
}
interface Oops { a: "someStringLiteralType" };
unsafeCaught<Oops>(); // {a: ""} is not a valid Partial<Oops>

(这对obj3 的行为负责。)但是该限制也阻止了某些已知安全的,例如您的,这是专门设计的:

function safeCaught<T extends { a: string }>() {
  const stillNope: { [K in keyof T]?: K extends "a" ? string : T[K] }
    = { a: "" }; // incorrect error
}

(这对obj1 的行为负责。)在这些情况下,编译器根本没有执行此工作所需的分析。 similar issue, microsoft/TypeScript#31070 实际上被认为是一个错误并已修复,但在这种情况下,映射的属性类型是像 number 这样的常量,而在你的情况下它是一个条件类型。并且编译器在验证依赖于未解析的泛型参数的条件类型的可分配性方面已经做得很差。因此,对于您的情况,我会将其归结为 TypeScript 的设计限制,并使用type assertion 来确定您在这种情况下比编译器更了解:

const obj1 = { version: 1 } as Partial<Mix<Versionable, T>>; // okay now

奇怪的是,当您写入属性而不是将对象分配给变量时,此限制会放松,因此您最终会得到完全相同的不合理行为而没有错误:

function unsafeUncaught<T extends { a: string }>(val: Partial<T>) {
  val.a = ""; // uh, wait
}
const oops: Oops = { a: "someStringLiteralType" };
unsafeUncaught(oops);

我是not sure why,但这可能是某处的设计决定。因此,以下内容也不会给您任何错误,但这只是巧合,因为它一开始就没有正确检查:

function safeUncaught<T extends { a: string }>(
  val: { [K in keyof T]?: K extends "a" ? string : T[K] }
) {
  val.a = ""; // okay but coincidentally
}

这可能就是您的version1version2version3 以及obj4.version = 1 工作的原因。


好的,希望对您有所帮助。祝你好运!

Link to code

【讨论】:

  • 谢谢。然后我会把它归结为一个编译器错误,然后向我的小朋友问好......任何! ;)
猜你喜欢
  • 2019-05-30
  • 1970-01-01
  • 2018-09-19
  • 2021-12-06
  • 2019-10-06
  • 2019-09-17
  • 2018-07-03
  • 2020-03-04
  • 2020-01-05
相关资源
最近更新 更多