Ввод функции mapShape в TypeScript - PullRequest
       39

Ввод функции mapShape в TypeScript

1 голос
/ 18 февраля 2020

Я хочу использовать такую ​​служебную функцию:

const out = mapShape(
  { foo: 1, bar: '2', baz: 'hello' },
  { foo: x => String(x), bar: x => parseInt(x) }
)
// outputs { foo: '1', bar: 2 }

Есть ли способ параметризации ее в TypeScript, чтобы тип вывода был таким?

{ foo: string, bar: number }

Я пытался сделать это:

export default function mapShape<
  I extends Record<any, any>,
  X extends { [K in keyof I]: (value: I[K], key: K, object: I) => any }
>(
  object: I,
  mapper: Partial<X>
): {
  [K in keyof I]: ReturnType<X[K]>
} {
  const result: any = {}
  for (const key in mapper) {
    if (Object.hasOwnProperty.call(mapper, key)) {
      result[key] = (mapper[key] as any)(object[key], key, object)
    }
  }
  return result
}

Однако тип TS выводит для out значение { foo: any, bar: any }; он не выводит определенные c типы для свойств.

Следующее дает правильный тип вывода, я просто не уверен, что смогу его параметризовать:

const mappers = {
  foo: x => String(x),
  bar: x => parseInt(x),
}
type outputType = {
  [K in keyof typeof mappers]: ReturnType<typeof mappers[K]>
}
// { foo: string, bar: number }

Ответы [ 2 ]

3 голосов
/ 18 февраля 2020

Я думаю, что типизация, которая ведет себя лучше всего, выглядит примерно так:

function mapShape<T extends { [K in keyof U]?: any }, U>(
    obj: T,
    mapper: { [K in keyof U]: K extends keyof T ? (x: T[K]) => U[K] : never }
): U {
    const result: any = {}
    for (const key in mapper) {
        if (Object.hasOwnProperty.call(mapper, key)) {
            result[key] = (mapper[key] as any)(obj[key], key, obj)
        }
    }
    return result
}

Я использую вывод из сопоставленных типов , чтобы позволить типу вывода быть U и mapper объект, который будет гомоморфным c отображенным типом на ключах U.

Это создает желаемый тип вывода для out, в то же время выводя типы параметров в свойствах обратного вызова аргумента mapper:

const out = mapShape(
    { foo: 1, bar: '2', baz: 'hello' },
    { foo: x => String(x), bar: x => parseInt(x) }
)
/* const out: {
    foo: string;
    bar: number;
} */

Это также должно препятствовать добавлению свойств в преобразователь, которые не существует в отображаемом объекте:

const bad = mapShape(
    { a: 1 },
    { a: n => n % 2 === 0, x: n => n } // error!
    // ------------------> ~  ~ <----------
    // (n: any) => any is             implicit any
    // not never
)

Хорошо, надеюсь, это поможет вам продолжить; удачи!

Детская площадка ссылка на код

0 голосов
/ 18 февраля 2020

После экспериментов с ответом @jcalz я получил следующую версию в стиле lodash/fp:

export default function mapShape<U extends Record<any, (...args: any) => any>>(
  mapper: U
): <T extends { [K in keyof U]?: any }>(
  obj: {
    [K in keyof T]?: K extends keyof U ? Parameters<U[K]>[0] : any
  }
) => { [K in keyof U]: ReturnType<U[K]> } {
  return (obj: any) => {
    const result: any = {}
    for (const key in mapper) {
      if (Object.hasOwnProperty.call(mapper, key)) {
        result[key] = mapper[key](obj[key], key, obj)
      }
    }
    return result
  }
}

mapShape({
  foo: (x: number) => String(x),
  bar: (x: string) => parseInt(x),
})({
  foo: 1,
  bar: '2',
  baz: 'hello',
})
...