如何使用打字稿中的查找来推断类型化的 mapValues?

Posted

技术标签:

【中文标题】如何使用打字稿中的查找来推断类型化的 mapValues?【英文标题】:How to infer typed mapValues using lookups in typescript? 【发布时间】:2018-11-22 04:15:42 【问题描述】:

类似于:

How to infer a typed array from a dynamic key array in typescript?

我正在寻找一个通用对象,它接收任意键的映射以查找值,并返回具有类型值的相同键(如类型化 _.mapValues)。

从对象中获取单一类型属性的能力已被记录并有效。对于数组,您需要将重载硬编码为类型化元组,但对于对象,我会收到“重复字符串索引签名”错误。

export interface IPerson 
    age: number;
    name: string;


const person: IPerson = 
    age: 1,
    name: ""


function getProperty<T, K extends keyof T>(o: T, name: K): T[K] 
    return o[name];


const a = getProperty(person, 'age');
// a: number

const n = getProperty(person, 'name');
// n: string

function getProperties<T, K extends keyof T>(obj: T, keys:  [key: string]: K ) 
    const def:  [key: string]: T[K]  = ;
    return Object.entries(keys).reduce((result, [key, value]: [string, K]) => 
        result[key] = getProperty(obj, value);
        return result;
    , def);


const  a2, n2  = getProperties(person, 
    a2: 'name',
    n2: 'age'
);

// Result:
// 
//     a2: string | number, 
//     n2: string | number
// 

// What I'm looking for:
// 
//     a2: string, 
//     n2: number' 
// 

如何使用 typescript 来实现?

【问题讨论】:

抱歉,本想回答您对另一个问题的评论,但忘记了。无论如何@jcalz 也给出了我想的确切答案,不要认为它可以做得更好:-) 【参考方案1】:

只要它在运行时工作,您可以告诉 TypeScript 如何使用 mapped types 重命名键:

type RenameKeys<T, KS extends Record<keyof KS, keyof T>> = [K in keyof KS]: T[KS[K]];

function getProperties<T, KS extends Record<keyof KS, keyof T>>(
    obj: T,
    keys: KS
): RenameKeys<T, KS> 
    const def =  as RenameKeys<T, KS>;
    return (Object.entries(keys) as Array<[keyof KS, any]>)
      .reduce((result, [key, value]) => 
        result[key] = getProperty(obj, value);
        return result;
    , def);

这应该与您在类型系统中所期望的一样。亮点:keys的类型被赋予了一个名为KS的类型参数,它被约束为Record&lt;keyof KS, keyof T&gt;,这或多或少的意思是“我不在乎键是什么,但属性类型需要是来自T 的密钥”。然后,RenameKeys&lt;T, KS&gt; 遍历KS 的键并从T 中提取与它们相关的属性类型。

最后,我需要做一些类型断言...defRenameKeys&lt;T, KS&gt;[key, value]value 的类型我刚刚创建了any,因为类型系统很难验证result[key] 是否是正确的类型。所以这是对实现类型安全性的一种捏造……但getProperties() 的调用者应该很高兴:

const a2, n2 = getProperties(person, 
  a2: 'name',
  n2: 'age'
);
// a2 is string, n2 is number.

Playground link to code

【讨论】:

【参考方案2】:

可能有一种方法可以稍微优化/折叠这种输入,但我有一个 getProperties 的函数原型,我相信它可以实现您所寻找的。​​p>

您的定义中缺少的是一个强大的返回类型定义,它将keys 对象中的特定键与obj 对象中的特定类型之间的关联联系起来。因为缺少了这一点,所以一切都变成了类型的联合,这就是您在上面看到的行为。

我想出的函数类型是:

function getProperties
    <T, K extends keyof T, U extends  [name: string]: K >
        (obj: T, keys: U):
            [V in keyof U]: T[U[V]];

这里重要的部分是返回值类型:[V in keyof U]: T[U[V]]

它指定对于keys对象中的每个键V

    V 将是输出对象中的一个键

    值类型将是来自输入obj 的类型,其中类型来自与U 中的键V 关联的值给出的键。

【讨论】:

以上是关于如何使用打字稿中的查找来推断类型化的 mapValues?的主要内容,如果未能解决你的问题,请参考以下文章

在打字稿中推断抽象类的参数

如何将方法的泛型类型限制为打字稿中的对象?

如何在打字稿中声明函数类型

打字稿中的重载函数类型

如何从打字稿中的标记联合类型中提取类型?

如何理解打字稿中的“属性'名称'在'用户'类型中是私有的”