简体   繁体   English

打字稿:通用对象映射函数

[英]Typescript: Generic object mapping function

For the following function which is similar to [].map but for objects对于以下类似于[].map函数,但用于对象

function mapObject(f, obj) {
  return Object.keys(obj).reduce((ret, key) => {
    ret[key] = f(obj[key])
    return ret
  }, {})
}

is there a way to type it so that the following works?有没有办法输入它以便以下工作?

interface InputType {
  numberValue: number
  stringValue: string
}

interface OutputType {
  numberValue: string
  stringValue: number
}

const input: InputType = {
  numberValue: 5,
  stringValue: "bob@gmail.com",
}

function applyChanges(input: number): string
function applyChanges(input: string): number
function applyChanges(input: number | string): number | string {
  return typeof input === "number" ? input.toString() : input.length
}

const output: OutputType = mapObject(applyChanges, input) // <-- How to get the correct 'OutputType'

This works, but is very specific to the applyChanges function这有效,但非常特定于applyChanges函数

type MapObject<T> = {
  [K in keyof T]: T[K] extends number
    ? string
    : T[K] extends string ? number : never
}

function mapObject<F extends FunctionType, T>(f: F, obj: T): MapObject<T>

Is there a more general solution?有没有更通用的解决方案?

There is a signature in the typescript 2.1 release notes . typescript 2.1 发行说明中有一个签名。

Combined with your code I end up with:结合您的代码,我最终得到:

function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> {
    return Object.keys(obj).reduce((ret, key) => {
        const k = key as K;
        ret[k] = f(obj[k]);
        return ret
    }, {} as Record<K, U>)
}

Yes, you can use a lambda type to describe the type of the input and output of f , and then add a constraint that the input type of f , here called A , must be part of the type of the values of the type of obj , somewhat obscurely referred to as O[keyof O]是的,您可以使用 lambda 类型来描述f的输入和输出的类型,然后添加一个约束,即f的输入类型(这里称为A )必须是obj类型的值的类型的一部分,有点模糊地称为O[keyof O]

function mapObject<A extends O[keyof O], B, O>(f: (a: A) => B, obj: O) {
  return Object.keys(obj).reduce((ret, key) => {
    ret[key] = f(obj[key])
    return ret
  }, {})
}

As suggested here , you may introduce a type alias to improve readability when using keyof:正如此处所建议的,您可以在使用 keyof 时引入类型别名以提高可读性:

type valueof<T> = T[keyof T]

You would need higher-kinded types to properly describe the type transformation performed by mapObject in terms of that performed by f .您需要更高级的类型才能根据f执行的类型正确描述mapObject执行的类型转换。 If you use my favorite mini-library for faking higher-kinded types, you can set things up like this:如果你使用我最喜欢的迷你库来伪造更高级的类型,你可以这样设置:

// Matt's mini "type functions" library

const INVARIANT_MARKER = Symbol();
type Invariant<T> = { [INVARIANT_MARKER](t: T): T };

interface TypeFuncs<C, X> {}

const FUN_MARKER = Symbol();
type Fun<K extends keyof TypeFuncs<{}, {}>, C> = Invariant<[typeof FUN_MARKER, K, C]>;

const BAD_APP_MARKER = Symbol();
type BadApp<F, X> = Invariant<[typeof BAD_APP_MARKER, F, X]>;
type App<F, X> = [F] extends [Fun<infer K, infer C>] ? TypeFuncs<C, X>[K] : BadApp<F, X>;

// Scenario

// https://github.com/Microsoft/TypeScript/issues/26242 will make this better.    
function mapObject<F, B>() {
  return function <O extends { [P in keyof O]: B }>
    (f: <X extends B>(arg: X) => App<F, X>, obj: O): {[P in keyof O]: App<F, O[P]>} {
    return Object.keys(obj).reduce((ret, key) => {
      const key2 = <keyof O>key;
      ret[key2] = f(obj[key2])
      return ret
    }, <{[P in keyof O]: App<F, O[P]>}>{})
  };
}

const F_applyChanges = Symbol();
type F_applyChanges = Fun<typeof F_applyChanges, never>;
interface TypeFuncs<C, X> { 
  [F_applyChanges]: X extends number ? string : X extends string ? number : never;
}

// Take advantage of the lax checking of overload signatures.  With
// https://github.com/Microsoft/TypeScript/issues/24085, we may be able
// to type check the body of applyChanges based on the first signature.
function applyChanges<X extends number | string>(input: X): App<F_applyChanges, X>
function applyChanges(input: number | string): number | string {
  return typeof input === "number" ? input.toString() : input.length;
}

interface InputType {
  numberValue: number
  stringValue: string
}

interface OutputType {
  numberValue: string
  stringValue: number
}

const input: InputType = {
  numberValue: 5,
  stringValue: "bob@gmail.com",
}

const output: OutputType = mapObject<F_applyChanges, number | string>()
  (applyChanges, input);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM