When working with string literal types in TypeScript, the use of the infer
keyword can lead to some intriguing behaviors. Let's delve into an example to explore its nuances:
type Test<T> = T extends `${infer A}${infer B}${infer C}`
? `${A}-${B}-${C}`
: never;
type Test1 = Test<"a">; // never
type Test2 = Test<"ab">; // a-b-
type Test3 = Test<"abcd">; // a-b-cd
Observations:
- The first result is
never
because the string literal type is only one character long, and our type requires at least three characters. - The second result,
a-b-
, may seem unexpected. Theinfer
keyword is greedy, attempting to match as many characters as possible. In this case, it matchesa
toA
,b
toB
, and the remaining empty string toC
. As we can only have one empty string in the template literal type, the result isa-b-
. This behavior becomes clearer with a four-character example:
type Test4Char<T> = T extends `${infer A}${infer B}${infer C}${infer D}`
? `${A}-${B}-${C}-${D}`
: never;
type Test1 = Test4Char<"a">; // never (B, C, and D are empty strings, and only one is allowed)
type Test2 = Test4Char<"ab">; // never (C & D are empty strings, and only one is allowed)
type Test3 = Test4Char<"abc">; // a-b-c- (D is empty string)
type Test4 = Test4Char<"abcd">; // a-b-c-d
- The third result,
a-b-cd
, demonstrates thatC
matchescd
, storing the remaining string inC
.
With this understanding, let's leverage this knowledge to create some intriguing types:
// Get the last character of a string
type LastChar<T extends string> = T extends `${infer A}${infer Last}`
? Last extends ""
? A
: LastChar<Last>
: never;
type TestL1 = LastChar<"abcd">; // 'd'
// Reverse a string
type Reverse<T extends string> = T extends `${infer A}${infer B}`
? `${Reverse<B>}${A}`
: T;
type TestR1 = Reverse<"reverse">; // esrever
// Convert camel case to kebab case
type CamelToKebab<T> = T extends `${infer A}${infer B}`
? `${A extends Capitalize<A> ? `-${Lowercase<A>}` : A}${CamelToKebab<B>}`
: T;
type TestC1 = CamelToKebab<"fooBarBaz">; // foo-bar-baz
type TestC2 = CamelToKebab<"fooBar">; // foo-bar
type TestC3 = CamelToKebab<"foo">; // foo
That's all for now. Happy typing!
Top comments (0)