【问题标题】:How to represent object keys replacement in Typescript definition?如何在 Typescript 定义中表示对象键替换?
【发布时间】:2020-09-14 23:30:06
【问题描述】:

目前我有以下types

type PossibleKeys = number | string | symbol;
type ValueOf<T extends object> = T[keyof T]; 
type ReplaceKeys<T extends Record<PossibleKeys, any>, U extends Partial<Record<keyof T, PossibleKeys>>> = 
  Omit<T, keyof U> & { [P in ValueOf<U>]: T[keyof U] };

...但是,虽然它甚至可以部分工作,但它给出了以下错误:

类型 'U[keyof U]' 不能分配给类型 'string |号码 | 符号'。


A simple demo:

interface Item {
  readonly description: string;
  readonly id: string;
}

interface MyInterface {
  readonly id: string;
  readonly propToReplace: number;
  readonly anotherPropToReplace: readonly Item[];
}

type ReplacedUser = ReplaceKeys<MyInterface, { propToReplace: 'total', anotherPropToReplace: 'items' }>;

ReplacedUser 我可以看到类型几乎是正确的。推断类型为:

{ id: string; total: number | readonly Item[]; items: number | readonly Item[]; }

...在我期待的时候:

{ id: string; total: number; items: readonly Item[]; }

我做错了什么?我首先想知道如何表达P 需要获取U 中传递的 以抑制Typescript 错误,然后获取特定value 的正确类型。

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    最简单的方法是为您的ReplaceKeys 反转U 类型参数:

    type PossibleKeys = number | string | symbol;
    type ReplaceKeys<T extends {}, U extends Record<PossibleKeys, keyof T>> = Omit<T, ValueOf<U>> & {
        [K in keyof U]: T[U[K]]
    };
    

    然后你可以像这样使用它:

    type ReplacedUser = ReplaceKeys<MyInterface, { total: 'propToReplace', items: 'anotherPropToReplace' }>;
    

    如果你不能改变 U 的形状变得有点棘手:

    // Example types from your post
    interface Item {
      readonly description: string;
      readonly id: string;
    }
    
    interface MyInterface {
      readonly id: string;
      readonly propToReplace: number;
      readonly anotherPropToReplace: readonly Item[];
    }
    
    // All possible key types
    type PossibleKeys = number | string | symbol;
    
    // Helper type to get all non-nullable values from type T
    type DefinedValues<T> = NonNullable<T[keyof T]>;
    
    // Helper type for your replacements object - a record whose values are valid keys
    // and whose keys are also present in type T (the input type)
    // 
    // Partial is used to make sure you don't need to pass all keys of T in your replacements
    type Replacements<T extends {}> = Partial<Record<keyof T, PossibleKeys>>;
    
    // Helper type that swaps object keys for values
    type Invert<T extends Replacements<C>, C extends {} = {}> = {
      [N in DefinedValues<T>]: {
        [K in keyof T]: N extends T[K] ? K : never
      }[keyof T]
    }
    
    type ReplacedKeys<T extends {}, R extends Replacements<T>> = Omit<T, keyof R | DefinedValues<R>> & {
      [N in keyof Invert<R>]: {
        [L in keyof R]: N extends R[L] ? (L extends keyof T ? T[L] : never) : never;
      }[keyof R]
    }
    

    请注意,使用第二种方法不会警告您存在重复映射:

    type ReplacedUser = ReplacedKeys<MyInterface, { propToReplace: 'total', anotherPropToReplace: 'total' }>;
    

    检查playground here

    【讨论】:

    • 太好了,谢谢你这么详细的解释!第二种方法是我一直在寻找的:)。
    • 只是为了记录,为了消除我的类型定义中的错误,我应该使用NonNullable&lt;ValueOf&lt;U&gt;&gt;(也许更清晰的错误消息会有所帮助:/),但当然,它不能解决第二个问题问题,上面很好解决了:)
    • 嗯,我刚刚注意到,使用第二种方法,我可以将任何值作为键传递,而不仅仅是keyof T。你知道如何解决这个问题吗?例如:ReplaceKeys&lt;MyInterface, { strangeKey: 'newKey', anotherPropToReplace: 'total' }&gt; 应该在 strangeKey 中出错,但目前没有。
    • 我更新了答案,改变了类型约束的“方向”:) 看看
    • 很好,这行得通!我进行了测试并且它可以工作,但是由于某种原因,当我尝试将它用作函数的 ReturnType 时,它​​没有。你能帮我完成最后一部分吗?我把它放在这个游乐场 -> bit.ly/2Ae5lj4 (我不得不缩短它以适应这里,因为游乐场会生成一个非常长的网址)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-14
    • 2020-12-11
    • 2021-05-30
    • 2021-03-16
    • 2021-12-08
    • 2020-02-06
    • 2016-09-10
    相关资源
    最近更新 更多