Skip to content

Typescript Tricks #123

@xianshenglu

Description

@xianshenglu

How to use recursion

Add generic params to pass recursive params.

Example:

How to count for Array

Use the length of the Array instance. Examples:

How to transform number to string

Use the template literal and infer.

Example: Absolute

How to transform string to number

Use string to compare and Array to count.

Example:

type ToNumber<S extends string, A extends any[] = []> = `${A['length']}` extends S
  ? A['length'] : ToNumber<S, [any, ...A]>
ToNumber<'0'> // 0
ToNumber<'10'> // 10

How to access some parts of string or Array

Use multiple infer keywords.

Example: StringToUnion

How to detect if a string or input value needs transformation

Compare the input with the output using extends.

Example:

type isCapitalizable<S extends string> = S extends Capitalize<S> ? false : true
type foo = isCapitalizable<'Foo'> //false
type bar = isCapitalizable<'bar'> //true

How to add transformation except for the first time

Set default value in generic params.

Example:

type KebabCase<S, P extends string = ''> = S extends `${infer F}${infer L}`
  ? (F extends Lowercase<F> ? `${F}${KebabCase<L, '-'>}` : `${P}${Lowercase<F>}${KebabCase<L, '-'>}`) : S
KebabCase<'FooBarBaz'> // 'foo-bar-baz'
How to test every item type in Array like some and every?

Use Array[number].

Example:

type AnyOf<T extends any[]> = T[number] extends 0 | '' | false | [] | {[key: string]: never}
? false : true;
AnyOf<[0, '', false, [], {}] //false
AnyOf<[0, '', false, [], {x: number}] //true
AnyOf<[0, '', false, [1], {}] //true
infer match mode
type s0 =`` extends `${infer A}${infer B}${infer C}`?C:false //false
type s1 =`a` extends `${infer A}${infer B}${infer C}`?C:false //false
type s2 =`ab` extends `${infer A}${infer B}${infer C}`?C:false // ''
type s3=`abcd` extends `${infer A}${infer B}${infer C}`?C:false //'cd'
type s4=`ab_c` extends `${infer A}_${infer B}${infer C}`?C:false // ''
infer as generic params

Normally, we would use infer like

type GetTypeVal1<T> = T extends { type: infer R }
  ? R : T extends { typeName: infer R2 } ? R2 : T

type TypeVal11 = GetTypeVal1<{ type: string }>
type TypeVal12 = GetTypeVal1<{ type: string, typeName: number }>

However, one day I saw another usage of infer like this

type GetTypeVal2<T> = { type: T } | T | { typeName: T }
type TypeVal21 = { type: string } extends GetTypeVal2<infer T> ? T : any //string
type TypeVal22 = { type: string, typeName: number } extends GetTypeVal2<infer T> ? T : any //string | number

It describes the situations first and then uses the infer as the generic params to get the matched types. A little different from GetTypeVal1. Also, we can change GetTypeVal1 to work like GetTypeVal2.

type GetTypeVal1<T> = T extends { type: infer R, typeName: infer R2 }
  ? R | R2 : T extends { type: infer R3 } ? R3 : T extends { typeName: infer R4 } ? R4 : T

type TypeVal11 = GetTypeVal1<{ type: string }> //string
type TypeVal12 = GetTypeVal1<{ type: string, typeName: number }>  //string | number

Compared with infer in generic params, wouldn't that easier?

However, normally this is not what we want. Also, there is a related problem to look out for. microsoft/TypeScript#32389

How to add properties to an object, items to an array, or union recursively?
type TupleKeys<T extends any[], R extends number[] = []> = T extends [infer S1, ...infer S2]
  ? TupleKeys<S2, [S2['length'], ...R]> : R

type Index = TupleKeys<['macOS', 'Windows', 'Linux']> // [0,1,2]
type TupleKeys<T extends any[]> = T extends [infer S1, ...infer S2]
  ? S2['length'] | TupleKeys<S2> : never
type Index = TupleKeys<['macOS', 'Windows', 'Linux']> // 0 | 2 | 1
type TupleKeys<T extends any[]> = T extends [infer S1, ...infer S2]
  ? {
    [P in `${S2['length']}`]: S2['length']
  } & TupleKeys<S2> : {}

type Index= TupleKeys<['macOS', 'Windows', 'Linux']> // {2:2,1:1,0:0}
Utilities
// type IsArrayEmpty<T extends []> = T["length"] extends 0 ? true : false
// type IsObjectEmpty<T extends {}> = keyof T extends never ? true : false

type IsArrayEmpty<T extends []> = T extends [] ? true : false
type IsObjectEmpty<T extends {}> = T extends { [key: string]: never } ? true : false

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions