英文:
A problem with getting different Modules based on the type of Module passed in
问题
我想传入一个不同类型的数组,以便我可以获得子模块的不同组合。
但我传入单一类型是可以的,当我传入多种类型时,它编译错误。
我该如何更改这个?
英文:
I want to pass in an array of different types so that I can get different combinations of submodules.
But I pass in a single type which is fine, and when I pass in multiple types, it compiles incorrectly.
How do I change this?
export enum Module {
'Video' = 0,
'Chat',
}
export interface ModulesMaps {
[Module.Video]: VideoModule;
[Module.Chat]: ChatModule;
}
export interface VideoModule {
getVideoModule(): string;
}
export interface ChatModule {
getChatModule(): string;
}
export interface CommonModule {
init(): void;
}
export type Core<T extends Module> = CommonModule & ModulesMaps[T]
export function createClient<T extends Module>(modules: T[]): Core<T>{
// fake code
return undefined as unknown as Core<T>;
}
let client1 = createClient([Module.Video]);
client1.getVideoModule();
client1.init();
let client2 = createClient([Module.Chat]);
client2.getChatModule();
client2.init();
let client3 = createClient([ Module.Chat | Module.Video ]);
client3.getVideoModule(); //compile error
client3.getChatModule(); //compile error
client3.init();
Playground : typescriptlang.org playground
I want to pass in an array of different types so that I can get different combinations of submodules.
But I pass in a single type which is fine, and when I pass in multiple types, it compiles incorrectly.
How do I change this?
答案1
得分: 2
通过以下方式重写:
export type Core<T extends Module> = CommonModule & { foo: ModulesMaps[T] }
export function createClient<T extends Module>(modules: T[]): Core<T> & unknown {
throw 0;
}
let client3 = createClient([Module.Chat, Module.Video]);
// let client3: CommonModule & {
// foo: VideoModule | ChatModule;
// }
你可能会看到一个联合类型。你必须将它改为交叉类型以使其工作:
// 要么将这个包作为依赖项添加,要么复制粘贴所需的类型。不需要授信。
// https://github.com/sindresorhus/type-fest/blob/main/source/union-to-intersection.d.ts
import { UnionToIntersection } from 'type-fest'
export type Core<T extends Module> = CommonModule & UnionToIntersection<ModulesMaps[T]>
export function createClient<T extends Module>(modules: T[]): Core<T> & unknown {
// `& unknown`将`Core<Module>`解包为`CommonModule & VideoModule & ChatModule`
// 这是为了可读性,生产环境中可以将其删除
throw 0;
}
let client3 = createClient([Module.Chat, Module.Video]);
// let client3: CommonModule & VideoModule & ChatModule
英文:
By rewriting as
export type Core<T extends Module> = CommonModule & { foo: ModulesMaps[T] }
export function createClient<T extends Module>(modules: T[]): Core<T> & unknown {
throw 0;
}
let client3 = createClient([Module.Chat, Module.Video]);
// let client3: CommonModule & {
// foo: VideoModule | ChatModule;
// }
you may see a union there. You must change it to intersection to work
// Either add this package as a dependency or copy-paste the needed types. No credit required.
// https://github.com/sindresorhus/type-fest/blob/main/source/union-to-intersection.d.ts
import { UnionToIntersection } from 'type-fest'
export type Core<T extends Module> = CommonModule & UnionToIntersection<ModulesMaps[T]>
export function createClient<T extends Module>(modules: T[]): Core<T> & unknown {
// `& unknown` unwraps `Core<Module>` into `CommonModule & VideoModule & ChatModule`
// this is for understandability, feel free to remove it for production
throw 0;
}
let client3 = createClient([Module.Chat, Module.Video]);
// let client3: CommonModule & VideoModule & ChatModule
答案2
得分: 1
感谢 @Dimava
我已经找到两种解决这个问题的方法。
1. 使用 type-fest
中的 UnionToIntersection
by @Dimava
export type UnionToIntersection<Union> = (
// `extends unknown` 始终成立,并用于将 `Union` 转换为分布式条件类型。
Union extends unknown
// 函数参数的联合会形成一个交集,因此将联合类型用作函数的唯一参数。
? (distributedUnion: Union) => void
// 这不会发生。
: never
// 推断 `Intersection` 类型,因为 TypeScript 将函数的联合参数表示为该联合的交集。
) extends ((mergedIntersection: infer Intersection) => void)
? Intersection
: never;
export enum Module {
'Video' = 0,
'Chat',
}
export interface ModulesMaps {
[Module.Video]: VideoModule;
[Module.Chat]: ChatModule;
}
export interface VideoModule {
getVideoModule(): string;
}
export interface ChatModule {
getChatModule(): string;
}
export interface CommonModule {
init(): void;
}
export type Core<T extends Module> = CommonModule & UnionToIntersection<ModulesMaps[T]>
export function createClient<T extends Module>(modules: T[]): Core<T> & unknown{
throw 0;
}
let client1 = createClient([Module.Video]);
client1.getVideoModule();
client1.init();
let client2 = createClient([Module.Chat]);
client2.getChatModule();
client2.init();
let client3 = createClient([ Module.Chat, Module.Video ]);
client3.getVideoModule(); //编译错误
client3.getChatModule(); //编译错误
client3.init();
2. 使用元组类型
export type ModuleSelect<T extends readonly Module[], TModuleSelect extends Record<Module, any>> = |
T extends [infer F extends Module, ...infer L extends readonly Module[]] ? TModuleSelect[F] & ModuleSelect<L, TModuleSelect> : unknown;
export enum Module {
'Video' = 0,
'Chat',
}
export interface ModulesMaps {
[Module.Video]: VideoModule;
[Module.Chat]: ChatModule;
}
export interface VideoModule {
getVideoModule(): string;
}
export interface ChatModule {
getChatModule(): string;
}
export interface CommonModule {
init(): void;
}
export type Core<T extends Array<Module>> = CommonModule & ModuleSelect<T, ModulesMaps>;
export function createClient<T extends Module[]>(_modules: [...T]): Core<T> {
throw 0;
}
const client1 = createClient([Module.Video]);
client1.getVideoModule();
client1.init();
const client2 = createClient([Module.Chat]);
client2.getChatModule();
client2.init();
const client3 = createClient([Module.Chat, Module.Video]);
client3.getVideoModule();
client3.getChatModule();
client3.init();
英文:
Thank you for @Dimava
I have found two ways to solve the problem.
1. Using UnionToIntersection
in type-fest
by @Dimava
export type UnionToIntersection<Union> = (
// `extends unknown` is always going to be the case and is used to convert the
// `Union` into a [distributive conditional
// type](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types).
Union extends unknown
// The union type is used as the only argument to a function since the union
// of function arguments is an intersection.
? (distributedUnion: Union) => void
// This won't happen.
: never
// Infer the `Intersection` type since TypeScript represents the positional
// arguments of unions of functions as an intersection of the union.
) extends ((mergedIntersection: infer Intersection) => void)
? Intersection
: never;
export enum Module {
'Video' = 0,
'Chat',
}
export interface ModulesMaps {
[Module.Video]: VideoModule;
[Module.Chat]: ChatModule;
}
export interface VideoModule {
getVideoModule(): string;
}
export interface ChatModule {
getChatModule(): string;
}
export interface CommonModule {
init(): void;
}
export type Core<T extends Module> = CommonModule & UnionToIntersection<ModulesMaps[T]>
export function createClient<T extends Module>(modules: T[]): Core<T> & unknown{
throw 0;
}
let client1 = createClient([Module.Video]);
client1.getVideoModule();
client1.init();
let client2 = createClient([Module.Chat]);
client2.getChatModule();
client2.init();
let client3 = createClient([ Module.Chat, Module.Video ]);
client3.getVideoModule(); //compile error
client3.getChatModule(); //compile error
client3.init();
2. Use tuple types
// export type ModuleSelect<T extends Array<Module>, TModuleSelect extends Record<Module, any>> = (0 extends keyof T
// ? TModuleSelect[T[0]]
// : Record<string, never>) &
// (1 extends keyof T ? TModuleSelect[T[1]] : Record<string, never>) &
// (2 extends keyof T ? TModuleSelect[T[2]] : Record<string, never>) &
// (3 extends keyof T ? TModuleSelect[T[3]] : Record<string, never>) &
// (4 extends keyof T ? TModuleSelect[T[4]] : Record<string, never>) &
// (5 extends keyof T ? TModuleSelect[T[5]] : Record<string, never>) &
// (6 extends keyof T ? TModuleSelect[T[6]] : Record<string, never>) &
// (7 extends keyof T ? TModuleSelect[T[7]] : Record<string, never>) &
// (8 extends keyof T ? TModuleSelect[T[8]] : Record<string, never>) &
// (9 extends keyof T ? TModuleSelect[T[9]] : Record<string, never>) &
// (10 extends keyof T ? TModuleSelect[T[10]] : Record<string, never>);
export type ModuleSelect<T extends readonly Module[], TModuleSelect extends Record<Module, any>> = |
T extends [infer F extends Module, ...infer L extends readonly Module[]] ? TModuleSelect[F] & ModuleSelect<L, TModuleSelect> : unknown;
export enum Module {
'Video' = 0,
'Chat',
}
export interface ModulesMaps {
[Module.Video]: VideoModule;
[Module.Chat]: ChatModule;
}
export interface VideoModule {
getVideoModule(): string;
}
export interface ChatModule {
getChatModule(): string;
}
export interface CommonModule {
init(): void;
}
export type Core<T extends Array<Module>> = CommonModule & ModuleSelect<T, ModulesMaps>;
export function createClient<T extends Module[]>(_modules: [...T]): Core<T> {
throw 0;
}
const client1 = createClient([Module.Video]);
client1.getVideoModule();
client1.init();
const client2 = createClient([Module.Chat]);
client2.getChatModule();
client2.init();
const client3 = createClient([Module.Chat, Module.Video]);
client3.getVideoModule();
client3.getChatModule();
client3.init();
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论