简体   繁体   English

如何使用 TypeScript 进行类型化元编程?

[英]How can I do typed metaprogramming with TypeScript?

I'm creating a function that receives multiple keys and values and should return an object having those keys and their respective values.我正在创建一个 function 接收多个键和值,并应返回具有这些键及其各自值的 object。 The Value types should match the values I passed when I called the function.值类型应该与我调用 function 时传递的值相匹配。

Currently, the code works, but the typings are not exact.目前,该代码有效,但类型不准确。

I tried using the Factory way, hoping that typescript could infer something for me.我尝试使用工厂方式,希望 typescript 可以为我推断出一些东西。

Here is the code for the factory.这是工厂的代码。 Also, there is a playground here.此外,这里还有一个游乐场

const maker = (subModules?: Record<string, unknown>) => {
  const add = <Name extends string, Q>(key: Name, value: Q) => {
    const obj = {[key]: value};
    return maker({
      ...subModules,
      ...obj
    })
  }
  const build = () => {
    return subModules
  }

  return {
    add,
    build
  }
}

const m2 = maker()
  .add('fn', ({a, b}: { a: number, b: number }) => a + b)
  .add('foo', 1)
  .add('bar', 'aaaa')
  .build()
// m2.foo -> 1
// m2.bar -> 'aaaa'
// m2.fn({a: 1, b: 2}) -> 3
m2

Also there is the option for pipeline( playground ) maybe this one could be easier:还有管道( 操场)的选项,也许这个可能更容易:


type I = <T extends any[]>(...obj: T) => { [P in T[number]['key']]:  T[number]['value'] }
const metaMaker: I = <T extends any[]>(...subModules: T) => {
  return subModules.reduce((acc, curr) => {
    const op = {[curr.key]: curr.value}
    return {
      ...acc,
      ...op
    }
  }, {}) as { [P in T[number]['key']]: T[number]['value'] }
}
const m = metaMaker(
  {key: 'fn', value: ({a, b}: { a: number, b: number }) => a + b},
  {key: 'foo', value: 1},
  {key: 'bar', value: 'aaaa'},
)
// m.foo -> 1 
// m.bar -> 'aaaa'
// m.fn({a: 1, b: 2}) -> 3
// m

Similar to @yeahwhat's solution, but this one has added something extra in build .类似于@yeahwhat 的解决方案,但是这个在build中添加了一些额外的东西。 Since you are returning an intersection of many records, it can get messy pretty fast.由于您要返回许多记录的交集,因此它会很快变得混乱。 This extends infer O bit "collapses" the intersection into a single type.extends infer O位,将交叉点“折叠”为单一类型。

const maker = <Submodules extends Record<string, unknown> = {}>(subModules?: Submodules) => {
  const add = <Name extends string, Q>(key: Name, value: Q) => {
    const obj = {[key]: value};
    return maker<Submodules & Record<Name, Q>>({
      ...subModules,
      ...obj
    } as Submodules & Record<Name, Q>)
  }
  
  const build = () => {
    return subModules as Submodules extends infer O ? { [K in keyof O]: O[K] } : never;
  }

  return {
    add,
    build
  }
}

I have also made the second option:我也做了第二个选项:

type Narrow<T> =
    | (T extends infer U ? U : never)
    | Extract<T, number | string | boolean | bigint | symbol | null | undefined | []>
    | ([T] extends [[]] ? [] : { [K in keyof T]: Narrow<T[K]> });

const metaMaker = <T extends { key: string; value: any }[]>(...subModules: Narrow<T>) => {
  return (subModules as T).reduce((acc, curr) => {
    const op = {[curr.key]: curr.value}
    return {
      ...acc,
      ...op
    }
  }, {}) as { [P in T[number]['key']]: Extract<T[number], { key: P }>['value'] }
}

You were pretty close with your original solution, but I have used a special type to narrow the type of the input given, without the need for using as const .您与原始解决方案非常接近,但我使用了一种特殊类型来缩小给定输入的类型,而无需使用as const The piece you were missing is Extract<T[number], { key: P }> to get only the specific value for that key.您缺少的部分是Extract<T[number], { key: P }>获取该键的特定值。 Before you were giving each key all the values.在您为每个键提供所有值之前。

Playground (contains both)游乐场(包含两者)

You can keep track of the initial type in a T generic and combine it with a Record<Name, Q> every time you add a new entry, using an intersection type, like this ( playground ):您可以在T泛型中跟踪初始类型,并在每次添加新条目时将其与Record<Name, Q>组合,使用交集类型,如下所示( 游乐场):

const maker = <T extends Record<string, any>>(subModules?: T) => {
  
  const add = <Name extends string, Q>(key: Name, value: Q) => {
    const obj = {[key]: value} as Record<Name, Q>;
    return maker<T & Record<Name, Q>>({...subModules, ...obj} as T & Record<Name, Q>)
  }

  const build = () => {
    return subModules
  }

  return {
    add,
    build
  }
}

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

相关问题 Typescript - 如何获取类型化数组? - Typescript - How Do I Get A Typed Array? 如何在打字稿中键入管道函数? - How do I typed a pipe function in typescript? 如何在 TypeScript 中实现类型化的“两者”function? - How can I implement a typed “both” function in TypeScript? 如何在 Typescript 中将类型化变量用作具有相同名称的类型? - How can I use typed variables as types with same name in Typescript? 如何在typescript定义中指定松散类型的对象文字? - How can I specify loosely typed object literal in typescript definition? 如何在 TypeScript 中指定类型化的对象文字? - How can I specify a typed object literal in TypeScript? 如何在 typescript 中定义具有类型化数据结构的事件? - How can I define a event in typescript that has a typed data structure? 遇到错误:如何从打字稿中正确使用已翻译并键入的打字稿npm模块? - Getting errors: How can I properly consume my transpiled and typed typescript npm module from typescript? 如何确保在 Typescript 中正确输入了我的 Lit 元素组件道具 - How do I make sure that my Lit element component props are properly typed in Typescript 我如何使这个 typescript function 返回强类型数组而不是 string[] - How do I make this typescript function return strong typed array instead of string[]
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM