【问题标题】:Select only one key type pair from string key - object type pair list in typescript从字符串键中仅选择一个键类型对 - 打字稿中的对象类型对列表
【发布时间】:2022-01-30 22:50:54
【问题描述】:

我遇到了一个问题,如何从通用的键类型列表中只选择一项。

我用肮脏的方式解决了它,但我希望用更多的好方法来写。

示例)我想为用户创建 dto,并且选择了一种用户类型,但没有选择其他用户类型。


// here 3 classes
class User { .. }
class IntegrationUser { .. }
class Admin {..}

// and they will be matched with keys
export type UserKeyEntityMap = {
  user: User;
  admin: Admin;
  integration: IntegrationUser;
}

// so user type keys is
type UserKey = keyof UserKeyEntityMap;

export type UserResponseDto {
  [ K in keyof UserKeyEntityMap]: UserKeyEntityMap[K];
}

预期工作如下:

// error
const testEmpty = {} as UserResponseDto; 

// Success
const testOnlyHasUser = { user: {} as User } as UserResponseDto; 

// Success
const testOnlyHasAdmin = { admin: {} as Admin } as UserResponseDto; 

// error
const testHaveUserAndAdmin = { user: {} as User; admin: {} as Admin } as UserResponseDto; 

// error
const testHaveUserAndAdminAndIntegration = { user: {} as User; admin: {} as Admin; integration: IntegrationUser } as UserResponseDto; 

这是我的肮脏解决方案

// this cannot be passed test case: testHaveUserAndAdmin
export type UserResponseDto =
  Pick<UserKeyEntityMap, 'user'> |
  Pick<UserKeyEntityMap, 'admin'> | 
  Pick<UserKeyEntityMap, 'integration'>;

有什么漂亮的解决方案?

帮帮我!

【问题讨论】:

  • 您在上面创建新对象,而不是设置类型。我看到一个语法错误:',' expected。应该是:{ user: {} as User, admin: {} as Admin }。关于 dto 创建,我们有两个选择:合并所有属性({...properties from user and admin...})或按类型拆分({user: new User(), admin: new Admin()})
  • @AntonHirov 感谢您的评论。但分号(;)在 Typescript 类型对象中是正确的。我没有说那是集合类型。好吧,也许你误解了 Javascript。

标签: typescript generics typescript-generics


【解决方案1】:

看看distributive-conditional-types,我想这正是你要找的。根据我的经验,这个特性对于 TS 条件类型来说非常重要。

考虑这个例子:

class User { tag = 'User' }
class IntegrationUser { tag = 'IntegrationUser' }
class Admin { tag = 'Admin' }

// and they will be matched with keys
export type UserKeyEntityMap = {
    user: User;
    admin: Admin;
    integration: IntegrationUser;
}

type Distribute<Dict, Keys extends keyof Dict = keyof Dict> = Keys extends any ? Record<Keys, Dict[Keys]> : never

// credits goes to https://stackoverflow.com/questions/65805600/type-union-not-checking-for-excess-properties#answer-65805753
type UnionKeys<T> = T extends T ? keyof T : never;
type StrictUnionHelper<T, TAll> =
    T extends any
    ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

// Record<"user", User> | Record<"admin", Admin> | Record<"integration", IntegrationUser>
type UserResponseDto = StrictUnion<Distribute<UserKeyEntityMap>>

// error
const testEmpty: UserResponseDto = {};

// Success
const testOnlyHasUser: UserResponseDto = { user: {} as User };

// Success
const testOnlyHasAdmin: UserResponseDto = { admin: {} as Admin };

// error
const testHaveUserAndAdmin: UserResponseDto = { user: {} as User, admin: {} as Admin };

// error
const testHaveUserAndAdminAndIntegration: UserResponseDto = { user: {} as User, admin: {} as Admin, integration: {} as IntegrationUser };

Playground

通常,如果您想分发某些东西 - 只需使用 T extends any

查看here类似问题/答案

【讨论】:

  • 感谢您的有用回答。但该类型不会为空或独占一个选择项抛出错误。例如,该类型不能通过 testEmpty 或 testHaveUserAndAdmin。而且我已经意识到 Typescript 还没有像 Java 那样支持很好的泛型。无论如何,您的方式更加清晰和简单。所以谢谢你
  • @cyan-kinesin 不要放弃打字稿。查看我的更新
  • 有什么消息吗?我可以永远等你。嗯,我改变了我的想法,为每个请求创建 DTO 也不错(主要是 DTO 就是这样工作的)。也许我们应该等到 Typescript 更高级
  • @cyan-kinesin 嗨,我不知道如何通过其他方式解决这个问题
【解决方案2】:

哦,我找到了部分满足我的问题的高级方法。

// this type passes testEmpty, testUser, testAdmin but not on testUserAndAdmin or testUserAndAdminAndIntegration
export type UserResponseDto<K> =
  K extends 'user' ?
  Pick<UserKeyEntityMap, 'user'> :
  K extends 'admin' ?
  Pick<UserKeyEntityMap, 'admin'> :
  K extends 'integration' ?
  Pick<UserKeyEntityMap, 'integration'> :
  never;

如果你有更好的解决方案,请教我。

【讨论】:

    猜你喜欢
    • 2019-01-02
    • 1970-01-01
    • 2019-03-13
    • 2020-08-15
    • 2021-04-02
    • 2020-04-19
    • 1970-01-01
    • 2020-01-17
    • 2022-12-03
    相关资源
    最近更新 更多