TypeScript 添加 kebab 案例类型形成实际的驼峰案例键

TypeScript add kebab case types form actual camel case keys

这是一个示例输入界面

export interface CssProperties {
    alignContent: number | string | null;
    alignItems: number | string | null;
    alignSelf: number | string | null;
    alignmentBaseline: number | string | null;
}

结果类型应如下所示。

-> 添加烤肉串类型

export interface CssProperties {
    align-content: number | string | null;
    alignContent: number | string | null;
    align-items: number | string | null;
    alignItems: number | string | null;
    align-self: number | string | null;
    alignSelf: number | string | null;
    alignment-baseline: number | string | null;
    alignmentBaseline: number | string | null;
}

更新为 TS4.5+

现在 TypeScript 支持 tail recursion elimination on conditional types 我们可以用一种直接的方式编写 Kebab<T> 来处理最多 ~1000 个字符的字符串:

type Kebab<T extends string, A extends string = ""> =
    T extends `${infer F}${infer R}` ?
    Kebab<R, `${A}${F extends Lowercase<F> ? "" : "-"}${Lowercase<F>}`> :
    A

这是通过一个字符一个字符地迭代,并在每个尚未小写的字符前插入一个 "-" 来实现的(因此像数字这样的无大小写字符不会在它们前面出现破折号),然后将每个字符小写字符.

TypeScript 4.5+ 中的非尾递归逐字符迭代,或 TypeScript 4.4 及更低版本中的任何递归类型达到适度长字符串的递归限制,有时甚至只有二十个左右字符的字符串,而且很奇怪需要变通办法来处理更长的字符串。但是现在我们可以处理字符串文字的时间比我想象的任何人合理需要的时间都要长:

type Testing = Kebab<"itWasTheBestOfTimesItWasTheWorstOfTimesItWasTheAgeOfWisdomItWasTheAgeOfFoolishnessItWasTheEpochOfBeliefItWasTheEpochOfIncredulityItWasTheSeasonOfLightItWasTheSeasonOfDarknessItWasTheSpringOfHopeItWasTheWinterOfDespair">
// type Testing = "it-was-the-best-of-times-it-was-the-worst-of-times-it-was-the-age-of-wisdom-it-was-the-age-of-foolishness-it-was-the-epoch-of-belief-it-was-the-epoch-of-incredulity-it-was-the-season-of-light-it-was-the-season-of-darkness-it-was-the-spring-of-hope-it-was-the-winter-of-despair"

现在我们可以使用 key remapping(也是 TS4.1 的一项功能)轻松地将具有驼峰键的对象转换为具有 kebab 键的对象:

type KebabKeys<T> = { [K in keyof T as K extends string ? Kebab<K> : K]: T[K] };

最后,让我们在您的示例中尝试一下:

export interface CssPropertiesCamel {
    alignContent: number | string | null;
    alignItems: number | string | null;
    alignSelf: number | string | null;
    alignmentBaseline: number | string | null;
}    

type CssPropertiesKebab = KebabKeys<CssPropertiesCamel>;
/* type CssPropertiesKebab = {
    "align-content": number | string | null;
    "align-items": number | string | null;
    "align-self": number | string | null;
    "alignment-baseline": number | string | null;
} */

export interface CssProperties extends CssPropertiesCamel, CssPropertiesKebab { }

看起来不错!我们已经将 CssPropertiesCamel 变成了 CssPropertiesKebab,然后 CssProperties 可以只是这两种类型的合并版本。

Playground link to code

这里有另一种方法:

export interface CssProperties {
    alignContent: number | string | null;
    alignItems: number | string | null;
    alignSelf: number | string | null;
    alignmentBaseline: number | string | null;
}

type ToCebab<T extends string> =
    T extends `align${infer Prefix}`
    ? `aling-${Uncapitalize<Prefix>}`
    : never;

type Result = {
    [P in keyof CssProperties as ToCebab<P>]: CssProperties[P]
} & CssProperties

Playground link

Template literal strings - Uncapitalize

这是我找到的一个解决方案,它使用蹦床技术来延迟打字稿类型,因此它不会引发递归类型错误。

export type KebabCase<S> = S extends `${infer C}${infer T}`
  ? KebabCase<T> extends infer U
    ? U extends string
      ? T extends Uncapitalize<T>
        ? `${Uncapitalize<C>}${U}`
        : `${Uncapitalize<C>}-${U}`
      : never
    : never
  : S