【问题标题】:Generic function to merge multiple TypeScript enums用于合并多个 TypeScript 枚举的通用函数
【发布时间】:2021-02-09 22:58:03
【问题描述】:

我正在尝试编写一个通用函数来合并多个枚举。希望这个函数能完成以下相同的功能:

enum Mammals {
  Humans = 'Humans',
  Bats = 'Bats',
  Dolphins = 'Dolphins',
}

enum Reptiles {
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards',
}

const Animals = {
 ...Mammals,
 ...Reptiles,
}

type Animals = Mammals | Reptiles;

第一次尝试:

export const mergeEnums = <T extends any[]>(...enums: T): T[number] => {
  return {
    ...enums,
  };
};

// Results in Animals: typeof Mammals | typeof Reptiles
const Animals = mergeEnums(Mammals, Reptiles);

不幸的是,联合类型不太正确。 TypeScript 不允许访问密钥。示例类型错误:Property 'Snakes' does not exist on type 'typeof Mammals | typeof Reptiles'.

第二次尝试:

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
  k: infer I,
) => void
  ? I
  : never;

export const mergeEnums = <T extends any[]>(
  ...enums: T
): UnionToIntersection<T[number]> => {
  return {
    ...enums,
  } as UnionToIntersection<T[number]>;
};

// Results in Animals: typeof Mammals & typeof Reptiles
const Animals = mergeEnums(Mammals, Reptiles);

这确实允许密钥访问,但是当同一个密钥存在于多个枚举中时,返回类型为never,这在我的使用中是一种可能性。

是否有可能实现与Animals: Mammals | Reptiles功能相同的解决方案?

【问题讨论】:

  • “当同一个键存在于多个枚举中时,这在我的使用中是一种可能性”似乎是一个奇怪的边缘情况;你为什么要这样做或允许这样做?无论如何,除了可变参数输入,它看起来像this question。我很乐意写一个新的答案,它使用递归对一组输入进行处理,但我并不认为它比UnionToIntersection 有很大的改进,除非你能清楚地说明你为什么想成为允许枚举键以这种方式发生冲突。
  • 我同意 jcalz。当相同的键出现在两个枚举中时,您会会发生什么?它的值不能同时是第一个枚举另一个枚举的值,因为两个不相同的原语没有重叠。 never 是在这种情况下唯一有意义的类型。
  • 呃,我还是写了一个答案????
  • ????我正在将来自各种金融交易所的受支持资产代码合并到一个全球代码列表中,作为我实际使用的一部分。

标签: javascript typescript enums typescript4.0


【解决方案1】:

假设您想尝试在类型系统中模拟扩展运算符在运行时使用冲突属性所做的事情,您可以通过调整我对this question 的回答来做到这一点。开头是这样的:

type OptionalPropertyNames<T> =
  { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];

type SpreadProperties<L, R, K extends keyof L & keyof R> =
  { [P in K]: L[P] | Exclude<R[P], undefined> };

type Id<T> = T extends infer O ? { [K in keyof O]: O[K] } : never

// Type of { ...L, ...R }
type Spread<L, R> = Id<
  // Properties in L that don't exist in R
  & Pick<L, Exclude<keyof L, keyof R>>
  // Properties in R with types that exclude undefined
  & Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>
  // Properties in R, with types that include undefined, that don't exist in L
  & Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>
  // Properties in R, with types that include undefined, that exist in L
  & SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;

可选属性非常令人头疼,因为如果我有一个像{a: string} 这样的对象并将{a?: number} 类型的对象传播到其中,则生成的对象将是{a: string | number}。其他问题的所有警告都适用于此:有很多极端情况。坦率地说,您从Object.assign() 获得的默认交集或通用传播并不比上面的混乱差多少,而且要简单得多。如果您确定您的用例需要,我只建议使用 Spread&lt;L, R&gt; 代替 L &amp; R


不管怎样,继续讨论可变元组部分。 TypeScript 4.1 将引入递归条件类型(在microsoft/TypeScript#40002 中实现),因此您可以将Merge&lt;T&gt; 表示为Spread&lt;L, R&gt; 上的递归操作:

type Merge<T extends readonly any[]> = 
  T extends readonly [infer H, ...infer R] ? Spread<H, Merge<R>> : {}

export const mergeEnums = <T extends any[]>(
  ...enums: T
) => {
  return {
    ...enums,
  } as any as Merge<T>;
};

这应该可以如您所愿:

enum Mammals {
  Humans = 'Humans',
  Bats = 'Bats',
  Dolphins = 'Dolphins',
}

enum Reptiles {
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards',
}

const Animals = mergeEnums(Mammals, Reptiles);
type Animals = typeof Animals[keyof typeof Animals];

const Animals = mergeEnums(Mammals, Reptiles);
/* const Animals: {
    readonly Humans: Mammals.Humans;
    readonly Bats: Mammals.Bats;
    readonly Dolphins: Mammals.Dolphins;
    readonly Snakes: Reptiles.Snakes;
    readonly Alligators: Reptiles.Alligators;
    readonly Lizards: Reptiles.Lizards;
} */
type Animals = typeof Animals[keyof typeof Animals];
// type Animals = Mammals | Reptiles

如果你允许碰撞,你会得到我希望是你想要的结果:

enum Reptiles {
  Humans = 'They Have Discovered That We Are Alien Invaders; KILL THEM ALL',
  Snakes = 'Snakes',
  Alligators = 'Alligators',
  Lizards = 'Lizards',
}

const Animals = mergeEnums(Mammals, Reptiles);
/* const Animals: {
    readonly Bats: Mammals.Bats;
    readonly Dolphins: Mammals.Dolphins;
    readonly Humans: Reptiles.Humans;
    readonly Snakes: Reptiles.Snakes;
    readonly Alligators: Reptiles.Alligators;
    readonly Lizards: Reptiles.Lizards;
} */

type Animals = typeof Animals[keyof typeof Animals];
// type Animals = Mammals.Bats | Mammals.Dolphins | Reptiles

Playground link to code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-05-19
    • 2018-07-06
    • 2021-10-10
    • 2016-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多