【问题标题】:How to define properties in a Typescript interface with dynamic elements in the key name?如何在 Typescript 界面中使用键名中的动态元素定义属性?
【发布时间】:2021-03-26 04:19:02
【问题描述】:

我有一个对象,它可以有 n 数量的属性,每个属性都相同,但名称中有 n 值。

例子:

const obj = {
  'data-element-0': 'something',
  'data-element-1': 'something else',
  'data-element-2': 'something as well',
  'data-element-3': 'something to feel included',
};

有没有什么方法可以比仅仅使用更具体地定义这个接口

interface Obj {
  [key: string]: string;
}

【问题讨论】:

  • 你的限制是什么?索引是否需要从0 开始?他们需要是整数吗?它们是否需要是连续的整数,没有间隔?等等等等。
  • 如果它们需要连续且没有间隙,是否有合理的最大值n 或者它可能是无限的?
  • 你为什么不使用string[]

标签: typescript mapped-types


【解决方案1】:

你可以这样做:

type Key = `data-element-${1|2|3|4|5|6|7|8|9|0}`

const obj:Record<Key, string> = {
    'data-element-0': 'something',
    'data-element-1': 'something else',
    'data-element-2': 'something as well',
    'data-element-3': 'something to feel included',
    'data-element-yu': 'something to feel included', // error
};

更新 我还让助手使用双数,从:0-99:

type NonZeroDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

type NumberHelper = {
  [P in NonZeroDigit]: {
    [Z in NonZeroDigit]: `${P}${Z}`
  }
}

type NestedValues<T extends Record<string, Record<string, string>>> = {
  [P in keyof T]: P extends string ? Values<T[P]> : never
}
type Values<T> = T[keyof T]

type RemoveTrailingZero<T extends string> = T extends `${infer Fst}${infer Snd}` ? Fst extends `0` ? `${Snd}` : `${Fst}${Snd}` : never;

type Numbers_99 = RemoveTrailingZero<Values<NestedValues<NumberHelper>>>

更新

这里有一个用于生成从 0 到 99999 的数字范围的工具

type Values<T> = T[keyof T]

type LiteralDigits = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type NumberString<T extends number> = `${T}`

type AppendDigit<T extends number | string> = `${T}${LiteralDigits}`

type MakeSet<T extends number> = {
    [P in T]: AppendDigit<P>
}

type RemoveTrailingZero<T extends string> = T extends `${infer Fst}${infer Rest}` ? Fst extends `0` ? RemoveTrailingZero<`${Rest}`> : `${Fst}${Rest}` : never;

type From_1_to_999 = RemoveTrailingZero<Values<{
    [P in Values<MakeSet<LiteralDigits>>]: AppendDigit<P>
}>>

type By<V extends NumberString<number>> = RemoveTrailingZero<Values<{
    [P in V]: AppendDigit<P>
}>>

type From_1_to_99999 =
    | From_1_to_999
    | By<From_1_to_999>
    | By<From_1_to_999
        | By<From_1_to_999>>

Demo

更新 3

如果你还想生成文字数字,而不是字符串数字,你可以使用这段代码,已经无耻地从here盗取了代码

type PrependNextNum<A extends Array<unknown>> = A['length'] extends infer T ? ((t: T, ...a: A) => void) extends ((...x: infer X) => void) ? X : never : never;

type EnumerateInternal<A extends Array<unknown>, N extends number> = { 0: A, 1: EnumerateInternal<PrependNextNum<A>, N> }[N extends A['length'] ? 0 : 1];

type Enumerate<N extends number> = EnumerateInternal<[], N> extends (infer E)[] ? E : never;

type Result = Enumerate<43> // 0 | 1 | 2 | ... | 42

*2021 年 9 月 8 日更新

从 TS 4.5 开始,参见Tail recursion PR,可以生成更长的数字范围。

查看示例:

type MAXIMUM_ALLOWED_BOUNDARY = 999

type ComputeRange<
    N extends number,
    Result extends Array<unknown> = [],
    > =
    (Result['length'] extends N
        ? Result
        : ComputeRange<N, [...Result, Result['length']]>
    )

const ComputeRange = (N: number, Result: number[] = []): number[] => {
    if (Result.length === N) {
        return Result
    }
    return ComputeRange(N, [...Result, Result.length])
}
// 0 , 1, 2 ... 998
type NumberRange = ComputeRange<MAXIMUM_ALLOWED_BOUNDARY>[number]

Related question

Playground

【讨论】:

  • 我正在想办法把它变成双数,例如:10|11|12|13 ....
  • 你可以像this 那样做,但我不确定这是否符合用例。如果 OP 关心间隙或想要一个无限的最大尺寸,那么它会变得更毛
  • 还有data-element-${number},但它接受非整数值。好处是没有内置的最大值或巨大的联合类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-27
  • 2019-07-11
  • 1970-01-01
  • 2012-12-06
  • 1970-01-01
  • 2018-08-13
  • 1970-01-01
相关资源
最近更新 更多