【问题标题】:Ensure the Uniqueness of Literal Values in a Record Type确保记录类型中文字值的唯一性
【发布时间】:2021-12-30 09:54:00
【问题描述】:

给定以下代码

const VALUES = {
    field1: "fieldA",
    field2: "fieldB",
} as const


export type RecToDU<T> = {
    [K in keyof T]: T[K]
}[keyof T]

type VALUESLiterals = RecToDU<typeof VALUES>

这个结果正确

type VALUESLiterals = "fieldA" | "fieldB"

现在我想检查我的类型中的文字值是否都是唯一的,这样

const VALUE = {
    field1: "fieldA",
    field2: "fieldB",
    field3: "fieldA" // "fieldA" is again a literal value
} as const

type VALUESLiterals = RecToDU<typeof VALUES>

现在将产生never,而不是

type VALUESLiterals = "fieldA" | "fieldB"

as field3 也包含 fieldA 的文字值,它已经由 field1 定义。所以如果有重复的文字值,整个类型应该是never

【问题讨论】:

  • 第二个例子也产生“fieldA” | "fieldB" 不是 never 。你想让它产生 never 吗? typescriptlang.org/play?#code/…
  • @TitianCernicova-Dragomir 是的。类型应该是never
  • 您要禁止唯一值吗?
  • @jcalz 是的,正是我想要的! POut进入答案
  • @jcalz 的版本比我的要干净得多。我没有机会:D

标签: typescript mapped-types


【解决方案1】:

如果T 没有重复的属性值类型,那么您希望RecToDU&lt;T&gt; 成为其所有属性值类型的union;否则你希望它是neverT 的所有属性值类型的并集可以简单地通过 indexing 计算成 Tthe union of its keys: T[keyof T] (see this Q/A) .

因此,您将希望 RecToDU&lt;T&gt; 成为 conditional type 形式的 type RecToDU&lt;T&gt; = XXX extends YYY ? T[keyof T] : nevertype RecToDU&lt;T&gt; = XXX extends YYY ? never : T[keyof T]。那么我们要为XXXYYY 做些什么呢?

这是一种方法:

type RecToDU<T> = unknown extends {
   [K in keyof T]-?: T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never
}[keyof T] ? never : T[keyof T]

让我们来看看这一点:

{ [K in keyof T]-?: T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never }

我们正在做的是mappingT 的键中的每个属性键K,并且对于每个属性值T[K],我们将其与Omit&lt;T, K&gt;[Exclude&lt;keyof T, K&gt;] 进行比较。 The Omit&lt;T, K&gt; utility type 生成一个看起来像 T 但删除了 K 属性的类型;并且the Exclude&lt;X, K&gt; utility type 生成一个类型,该类型从X 中的任何联合成员中过滤掉K。所以Omit&lt;T, K&gt;[Exclude&lt;keyof T, K&gt;]T中所有属性值类型的联合除了K处的属性。

例如,如果T{a: 0, b: 1, c: 2} 并且K"a",那么T[K]0Omit&lt;T, K&gt;{b: 1, c: 2}Exclude&lt;keyof T, K&gt;"b" | "c",所以 Omit&lt;T, K&gt;[Exclude&lt;keyof T, K&gt;]1 | 2。所以在T[K] extends Omit&lt;T, K&gt;[Exclude&lt;keyof T, K&gt;] ? ... 中,我们正在比较0 extends 1 | 2 ? ... 。这是错误的,但如果 c 的值类型为 0 而不是 2,则会变为正确。

所以如果T[K] extends Omit&lt;T, K&gt;[Exclude&lt;keyof T, K&gt;],这意味着K 的属性值在其他一些属性中也是重复的。否则,这意味着K 处的属性值是唯一的。请注意,该条件类型的其余部分是? unknown : never,这意味着对于重复的属性,我们产生the unknown type(吸收联合中所有其他类型的“顶级类型”),对于独特的属性,我们产生the never type( “底部类型”被吸收联合中的所有其他类型)。同样,T{a: 0, b: 1, c: 2},我们将产生 {a: never, b: never, c: never},但对于 T{a: 0, b: 1, c: 0},我们将产生 {a: unknown, b: never, c: unknown}

现在我们来看看

{ [K in keyof T]-?: 
  T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never 
}[keyof T]

这与以前相同,但我们使用keyof T 对其进行索引。因此,我们采用映射类型并获取其值的并集。对于{a: 0, b: 1, c: 2},这是never | never | never,也就是never。但是对于{a: 0, b: 1, c: 0},这是unknown | never | unknown,即unknown。由于unknown 吸收了联合中的所有其他类型,而never 吸收了联合中的所有其他类型,因此never 的唯一方法是每个属性都是唯一的。如果甚至一个属性值是重复的(呃,我想至少必须有两个,也许?也许不是,如果其中一个是另一个的超类型......呃,没关系),那么@987654401 @出来了。

所以我们有一个条件类型,如果任何属性重复,则计算为 unknown,如果它们都是唯一的,则计算为 never。因此:

type RecToDU<T> = unknown extends {
   [K in keyof T]-?: T[K] extends Omit<T, K>[Exclude<keyof T, K>] ? unknown : never
}[keyof T] ? never : T[keyof T]

因为unknown extends XXX 仅在XXX 本身为unknown 时才为真,因此此检查仅在T 具有重复属性时为真,如果T 具有所有唯一属性则为假。对于重复的属性,我们返回never,对于唯一的属性,我们返回T[keyof T]


哇,让我们看看它是否有效:

const VALUES = {
   field1: "fieldA",
   field2: "fieldB",
   field3: "fieldC"
} as const

type VALUESLiterals = RecToDU<typeof VALUES>
// type VALUESLiterals = "fieldA" | "fieldB" | "fieldC"

好的,然后:

const VALUES = {
   field1: "fieldA",
   field2: "fieldB",
   field3: "fieldA"
} as const

type VALUESLiterals = RecToDU<typeof VALUES>
// type VALUESLiterals = never

看起来不错!


请注意,RecToDU&lt;T&gt; 的上述实现仅使用包含非optional 属性且没有index signatures 且其属性值为单个literal types 而非其他类型的联合或交集的对象类型进行了测试。如果这些条件被改变,那么上面实现的RecToDu&lt;T&gt; 可能会产生一些奇怪或不受欢迎的结果。因此,请注意针对您关心的用例进行测试,并在必要时相应地更改定义。

Playground link to code

【讨论】:

    猜你喜欢
    • 2023-04-04
    • 2017-02-25
    • 2014-11-13
    • 2021-07-15
    • 2018-05-09
    • 1970-01-01
    • 2011-09-30
    • 2011-11-17
    • 2016-01-10
    相关资源
    最近更新 更多