javascript - 使用 Typescript 将一个对象文字映射到另一个对象文字

标签 javascript typescript functional-programming typescript-typings

是否可以有 Lodash 风格的“mapValues”和“mapKeys”函数,返回映射的对象文字 - 而不是(太)通用的记录类型?

我的意思是:

_.mapValues({a:1, b:2}, (v) => v === 1 ? 'aaa' : 'bbb') 

此代码(Lodash 库)返回 Record<'a' | 'b', 'aaa' | 'bbb'> ,而不是文字类型 {a: 'aaa', b: 'bbb'}

与 Ramda/Fp-ts 函数相同 - 一些类型信息丢失。

最佳答案

我认为 TypeScript 编译器没有足够的支持高阶类型分析来为您执行此操作。我看到的问题:

  • 编译器没有好的方法来推断函数 const mapper = (v: 1 | 2) => v === 1 ? "aaa" : "bbb"属于条件泛型类型,如 <V extends 1 | 2>(v: V) => V extends 1 ? "aaa" : "bbb"或重载函数类型,如 {(v: 1): "aaa", (v: 2): "bbb"} 。如果您希望编译器像这样对待该函数,您必须手动断言或注释该类型。

  • 即使可以推断出这一点,也无法编写像 Apply<typeof f, typeof x> 这样的类型函数。哪里f是一个参数的重载函数或泛型函数,且 x是一个可接受的参数,因此 Apply<typeof f, typeof x>类型为f(x) 。较短:there's no typeof f(x) in TypeScript 。因此,虽然您可以调用mapper(1)并且编译器知道结果的类型为 "aaa" ,您无法在类型系统中表示该知识。这会阻止您执行类似 overloaded function resolution 的操作或generic function resolution在类型系统中。

<小时/>

我能想到的最简单的输入 _.mapValues会给你宽Record -like 类型,你必须 assert如果你想要的话,可以使用更窄的类型:

declare namespace _ {
  export function mapValues<T, U>(
    obj: T,
    fn: (x: T[keyof T]) => U
  ): Record<keyof T, U>;
}
const obj = { a: 1, b: 2 } as const;
type ExpectedRet = { a: "aaa"; b: "bbb" };
_.mapValues(obj, v => (v === 1 ? "aaa" : "bbb")); // Record<"a"|"b", "aaa"|"bbb">
const ret = _.mapValues(obj, v => (v === 1 ? "aaa" : "bbb")) as ExpectedRet;
<小时/>

否则,您必须跳过许多麻烦(手动指定类型、手动将函数声明为重载),最终得到的结果并不比类型断言安全多少,而且要复杂得多:

type UnionToIntersection<U> = (U extends any
  ? (k: U) => void
  : never) extends ((k: infer I) => void)
  ? I
  : never;

declare namespace _ {
  export function mapValues<T, U extends Record<keyof T, unknown>>(
    obj: T,
    fn: UnionToIntersection<{ [K in keyof T]: (x: T[K]) => U[K] }[keyof T]>
  ): U;
}

function mapper(v: 1): "aaa";
function mapper(v: 2): "bbb";
function mapper(v: 1 | 2): "aaa" | "bbb" {
  return v === 1 ? "aaa" : "bbb";
}

const obj = { a: 1, b: 2 } as const;
type ExpectedRet = { a: "aaa"; b: "bbb" };
const ret = _.mapValues<typeof obj, ExpectedRet>(obj, mapper);

不确定这是否值得解释...您必须在调用 _.mapValues 时手动指定输入和预期输出类型因为编译器无法推断输出类型(如上所述)。您必须手动指定 mapper是一个重载函数。输入_.mapValues很复杂,使用 UnionToIntersection 将所需的重载函数描述为将输入值转换为输出值的函数类型的交集。

所以,我会远离这个,只使用类型断言。

<小时/>

希望有帮助;抱歉我没有更满意的答案。祝你好运!

Link to code

关于javascript - 使用 Typescript 将一个对象文字映射到另一个对象文字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57645058/

相关文章:

javascript - 返回加载框架7上的当前页面

javascript - React 上的复杂嵌套组件不起作用

javascript - 移动焦点() - Angular 5

javascript - 使用 FileReader api 上传二进制文件时遇到问题

javascript - jQuery 选项卡/验证提交多个表单

TypeScript、Vue 和实验装饰器

javascript - 方法如何将 null 作为接口(interface)返回

javascript - 在不使用 Promise.all 和 Promise.resolve 的情况下在 JS 中的 promise 之间传递中间数据

java - 使用函数式风格合并两个数组

haskell - 非编程术语中的 Monad