打字稿映射泛型

Typescript mapped generics

我正在尝试让 TS 泛型映射到新对象。简而言之,我正在尝试转换:

{ 
  key: { handler: () => string },
  key2: { hander: () => number },
}

至:

{ key: string, key2: number }

完整示例:

type programOption = {
  validator: () => unknown
}

type programConfig<T extends {[key: string]: programOption} = {}> = {
  options: T,
  handler: (data: mapProgramConfig<T>) => void,
}

type mapProgramConfig<T extends {[key: string]: programOption}> = {
  [K in keyof T]: ReturnType<programOption['validator']>
}

type mapProgramConfigHardcoded<T> = {
  fruit: string,
  animal: number
}

class Program {
  constructor (config: programConfig) {}
}

const foo = new Program({
  options: {
    'fruit': { validator: () => 'asdf' },
    'animal': { validator: () => 42 },
  },
  handler: ({fruit, animal, thing}) => {

  },
});

如果您将 programConfig 类型中的 mapProgramConfig 替换为 mapProgramConfigHardcoded,就可以看到我正在尝试做的事情,但我似乎无法让它工作一般情况。

ts playground link

考虑这个解决方案:

type ProgramOption<T> = {
  validator?: () => T
}


type Convert<Obj extends Record<string, ProgramOption<any>>> = {
  [Prop in keyof Obj]: Obj[Prop]['validator'] extends () => infer Return ? Return : never
}
const program = <
  Keys extends PropertyKey,
  ValidatorValues extends string | number,
  Options extends Record<Keys, ProgramOption<ValidatorValues>>,
  Handler extends (data: Convert<Options>) => void,
  >(data: { options: Options, handler: Handler },) => {
  return data
}

const foo = program({
  options: {
    'fruit': { validator: () => 'string' },
    'animal': { validator: () => 42 },
  },
  handler: (obj) => {
    obj.animal // 42
    obj.fruit // 'string'
  }
});

Playground

为了推断 handler 属性 中的 obj 参数,您需要推断所有嵌套的键和值。

Keys - 引用嵌套对象的 fruitanimalValidatorValues - 指的是Validator return类型 Options - 指整个options 属性 Handler - 相应地指代 handler

我已经使用 Convert 实用程序类型遍历 Options 类型并获取所有 return 类型的 validator 属性

如果您对函数参数推断感兴趣,可以查看我的 article

我使用了 program 函数而不是 Program class 因为 Type parameters cannot appear on a constructor declaration

您只需要将 programConfig 接口上定义的泛型扩展到 Program class:

class Program<T extends {[key: string]: programOption} = {}> {
  constructor (config: programConfig<T>) {}
}

你可以这样定义

type programConfig<T extends Record<string, any>> = {
  options: {
    [K in keyof T]: {
      validator: () => T[K]
    }
  },
  handler: (data: T) => void, // ??? should be {optionKey: inferred return type}
}

class Program<T extends Record<string, any> = Record<string, any>> {
  constructor(config: programConfig<T>) { }
}

并且当您调用它时,泛型将仅根据必要的数据(键和数据类型)进行推断

const foo: Program<{
    fruit: string;
    animal: number;
}>

TS Playground