【问题标题】:Infer types using own properties使用自己的属性推断类型
【发布时间】:2021-08-05 15:47:56
【问题描述】:

我正在尝试找出是否有一种方法可以从实现的属性中推断出接口中的类型。

简化示例:

interface Options {
  type: 'string' | 'number'
  demanded?: boolean
}

interface Command {
  // The parameter options will contain the interpreted version of the options property
  callback: (options: InferOptionTypings<this>) => void
  options: { [key: string]: Options }
}

// Infer the options
// { type: 'string, demanded: false} | { type: 'string' }   => string | undefined
// { type: 'string, demanded: true }                        => string
// { type: 'number', demanded: false} | { type: 'number }   => number | undefined
// { type: 'number, demanded: true }                        => number
type InferOptionTypings<_ extends Command> = ... // here i've been stuck for very long

我已经阅读了 yargs 的类型(这显然是受到 yargs 的启发),但我还没有弄清楚如何使它以这种风格工作或我缺少什么/如果这甚至是可能的。

示例用例:

let command: Command = {
  callback: (options) => {
    options.a // string
    options.b // number | undefined
    options.c // string | undefined
    options.d // error
  },
  options: {
    a: {
      type: 'string',
      demanded: true,
    },
    b: {
      type: 'number',
    },
    a: {
      type: 'string',
    },
  },
}

【问题讨论】:

  • 你能举个例子说明你将如何使用Command.callback吗?
  • 当然可以! (现在在编辑问题中的示例)。虽然我不确定,如果这是你的意思
  • @TonyBorchert 顺便说一句,正在研究一些图书馆或其他东西吗?这里stackoverflow.com/questions/68676771/… 是一个非常相似的问题。我只是想写一篇专门讨论这个问题的文章,我不知道如何命名这个用例
  • 我没有写一个库,它只是一些命令的一些类型,但感谢链接中的额外信息,如果你有这篇文章很高兴阅读它,虽然我想不出任何名字对于此类问题

标签: typescript typing


【解决方案1】:

这是可能的,但为了推断它你应该创建一个函数。

interface Option {
  type: 'string' | 'number'
  demanded?: boolean
}

/**
 * Translates string type name to actual type
 * Logic is pretty straitforward
 */
type TranslateType<T extends Option> =
  T['type'] extends 'string'
  ? string
  : T['type'] extends 'number'
  ? number
  : never;

/**
 * Check if demanded exists
 * if true - apply never, because union of T|never produces T
 * if false - apply undefined
 */
type ModifierType<T extends Option> =
  T extends { demanded: boolean }
  ? T['demanded'] extends true
  ? never
  : T['demanded'] extends false
  ? undefined
  : never
  : undefined

/**
 * Apply TranslateType 'string' -> string
 * Apply ModifierType {demanded:fale} -> undefined or never
 */
type TypeMapping<T extends Option> = TranslateType<T> | ModifierType<T>

/**
 * Apply all conditions to each option
 */
type Mapping<T> = T extends Record<string, Option> ? {
  [Prop in keyof T]: TypeMapping<T[Prop]>
} : never

type Data<Options> = {
  callback: (options: Mapping<Options>) => void,
  options: Options
}
const command = <
  /**
   * Infer each option
   */
  Options extends Record<string, Option>
>(data: Data<Options>) => data

const result = command({
  callback: (options) => {
    type a = typeof options.a
    type b = typeof options.b
    type c = typeof options.c

    options.a // string
    options.b // number | undefined
    options.c // string | undefined
    options.d // error
  },
  options: {
    a: {
      type: 'string',
      demanded: true,
    },
    b: {
      type: 'number',
      demanded: false
    },
    c: {
      type: 'string',
    },
  },
})

我将 cmets 留在了每个类型实用程序下

Playground

更新 无功能:

type WithoutFunction = Data<{
  a: {
    type: 'string',
    demanded: true,
  },
  b: {
    type: 'number',
    demanded: false
  },
  c: {
    type: 'string',
  },
}>

【讨论】:

  • 非常有帮助,我肯定会用这个/这样做,但出于纯粹的好奇,这可以在没有任何功能的情况下完成吗(我不太明白你回答的第一句话/如果有可能或者你必须用函数来做)
  • 虽然这与我的示例不同,但它仍然没有功能,可能是最好的,而且绝对足够好(在我看来,功能比只有接口更好)所以谢谢寻求帮助,即使我问了另一个愚蠢的问题
  • 这不是愚蠢的问题。不幸的是,没有功能是不可能做到的
猜你喜欢
  • 2019-08-31
  • 2020-04-27
  • 1970-01-01
  • 2019-09-28
  • 2017-10-08
  • 1970-01-01
  • 2021-06-06
  • 1970-01-01
  • 2021-06-11
相关资源
最近更新 更多