合并多个TypeScript枚举的泛型函数

合并多个TypeScript枚举的泛型函数

本文介绍了合并多个TypeScript枚举的泛型函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个通用函数来合并多个枚举.希望此功能可以完成以下任务:

I'm attempting to write a generic function to merge multiple enums. The hope is that this function would accomplish the same as the following:

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不允许访问键.类型错误的示例:类型'typeof哺乳动物的属性'Snakes'不存在|typeof爬行动物".

Unfortunately, the union type is not quite right. TypeScript does not allow the keys to be accessed. Example type error: 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 ,这在我的用法中是可能的.

This does allow for key access, but results in a return type of never when the same key exists in more than one enum, which is a possibility in my usage.

是否有可能实现与 Animals:Mammals |功能上相同的解决方案?爬行动物?

推荐答案

假设您要尝试在类型系统中模拟运行时具有冲突属性的散布运算符的作用,则可以通过将我的答案调整为此问题.它开始像这样:

Assuming you want to try to emulate in the type system what the spread operator does at runtime with colliding properties, you could do it by adapting my answer to this question. It starts out like this:

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 |数字} .其他问题的所有警告也适用于此:存在很多极端情况.坦率地说,从 Object.assign()或默认的跨度获得的默认交集不比上面的烂摊子差很多,并且简单得多.我只建议使用 Spread< L,R> 代替 L&R ,如果您确定用例需要的话.

Optional properties are quite the headache, since if I have an object like {a: string} and spread an object of type {a?: number} into it, the resulting object will be an {a: string | number}. All the caveats from the other question apply here: there are very many edge cases. The default intersection you get from Object.assign() or a generic spread is, frankly, not much worse than the mess above and is a lot simpler. I'd only recommend going with Spread<L, R> in place of L & R if you are sure your use case warrants it.

无论如何,继续前进到可变的元组部分.TypeScript 4.1将引入递归条件类型(在 microsoft/TypeScript#40002 中实现)您可以将 Merge< T> 表示为对 Spread< L,R> :

Anyway, moving on to the variadic tuple part. TypeScript 4.1 will introduce recursive conditional types (as implemented in microsoft/TypeScript#40002) so you can represent your Merge<T> as a recursive operation on Spread<L, R>:

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>;
};

这应该可以按预期工作:

This should work as you expect:

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

如果允许碰撞,您将得到希望的结果:

and if you allow for collision, you get what I hope is your desired outcome:

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


这篇关于合并多个TypeScript枚举的泛型函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-06 20:31