TypeScript001-类型训练营


题目

Day1.实现内置类型

1.首字母大写 CapitalizeString

// 实现
// type CapitalizeString<T> = T extends `${infer R}${infer K}` ? `${Uppercase<R>}${K}` : T
type CapitalizeString<T extends string | number> = T extends string ? Capitalize<T> : T

// 使用
type a1 = CapitalizeString<'handler'>   // Handler
type a2 = CapitalizeString<'parent'>    // Parent
type a3 = CapitalizeString<233>         // 233

// 测试
let s1: a1 = 'Handler'
let s2: a2 = 'Parent'
let s3: a3 = 233

2.获取字符串字面量中的第一个字符 FirstChar

// 实现
type FirstChar<T> = T extends `${infer E}${infer _S}` ? E : never

// 使用
type A = FirstChar<'BFE'>   // 'B'
type B = FirstChar<'dev'>   // 'd'
type C = FirstChar<''>      // never

// 测试
let s1: A = 'B'
let s2: B = 'd'
function f(): C {
    throw new Error('报错')
}

3.获取字符串字面量中的最后一个字符 LastChar

用数组存储, 最后拿数组最后一位

// 实现
type LastChar<T extends string, A extends string[] = []> = T extends `${infer S}${infer U}`
    ? LastChar<U, [...A, S]>
    : A extends [...infer _L, infer R]
    ? R
    : never

// 使用
type A = LastChar<'BFE'>    // 'E'
type B = LastChar<'dev'>    // 'v'
type C = LastChar<''>       // never

// 测试
let s1: A = 'E'
let s2: B = 'v'
function f(): C {
    throw new Error('报错')
}

4.字符串转换为元组类型 StringToTuple

// 实现
type StringToTuple<T extends string, A extends string[] = []> = T extends
`${infer S}${infer U}`
    ? StringToTuple<U, [...A, S]>
    : A

// 使用
type A = StringToTuple<'BFE.dev'> // ['B', 'F', 'E', '.', 'd', 'e','v']
type B = StringToTuple<''> // []

// 测试
let tuple1: A = ['B', 'F', 'E', '.', 'd', 'e', 'v']
let tuple2: B = []

5.将字符串类型的元素转换为字符串字面量类型 TupleToString

// 实现
type TupleToString<T, S extends string = ''> = T extends [infer L, ...infer R]
    ? L extends `${infer LL}` ? TupleToString<R, `${S}${LL}`> : ''
    : S

// 使用
type A = TupleToString<['a', 'b', 'c']> // 'abc'
type B = TupleToString<[]>              // ''
type C = TupleToString<['a']>           // 'a'

// 测试
let s1: A = 'abc'
let s2: B = ''
let s3: C = 'a'

6.复制字符T为字符串类型,长度为C RepeatString<T,C>

// 实现 [利用length记录递归次数]
type RepeatString<
    T extends string,
    N extends number,
    S extends string = '',
    A extends any[] = []
> = A['length'] extends N
    ? S
    : RepeatString<T, N, `${S}${T}`, [1, ...A]>

// 使用
type A = RepeatString<'a', 3> // 'aaa'
type B = RepeatString<'a', 0> // ''

// 测试
let s1: A = 'aaa'
let s2: B = ''

7.将字符串字面量类型按照指定字符,分割为元组。 SplitString

// 实现
type SplitString<
  T extends string, 
  S extends string,
  A extends any[] = []
> = T extends "" 
  ? []
  : T extends `${infer L}${S}${infer R}`
    ? SplitString<R, S, [...A, L]>
    : [...A, T]

// 使用
type A1 = SplitString<'handle-open-flag', '-'>        // ["handle", "open", "flag"]
type A2 = SplitString<'handle.open.flag1', '.'>       // ["handle", "open", "flag1"]
type A3 = SplitString<'open.flag', '-'>               // ["open.flag"]

// 测试
let a1: A1 = ["handle", "open", "flag"]
let a2: A2 = ["handle", "open", "flag1"]
let a3: A3 = ["open.flag"]

8.计算字符串字面量类型的长度 LengthOfString

// 实现
type LengthOfString<
  T,
  A extends any[] = []
> = T extends `${infer L}${infer R}`
  ? LengthOfString<R, [...A, L]>
  : A['length']

// 使用
type A = LengthOfString<'BFE.dev'>  // 7
type B = LengthOfString<''>         // 0

// 测试
let s1: A = 7
let s2: B = 0

9.驼峰命名转横杠命名 KebabCase

// 实现
// 使用三个类型实现
type KebabCaseIterator<
  T extends string,
  A extends any[] = []
> = T extends ""
  ? A
  : T extends `${infer L}${infer R}`
    ? KebabCaseIterator<R, [...A, L extends Uppercase<L> ? `-${Lowercase<L>}` : L]>
    : never

type joinString<T> = T extends [infer L, ...infer R]
  ? L extends string
    ? `${L}${joinString<R>}`
    : never
  : ""

type removeFirstSeparator<T> = T extends `-${infer S}` ? S : T
type KebabCase<T extends string> = removeFirstSeparator<
    joinString<
      KebabCaseIterator<T>
    >
  >

    // 使用
type A = KebabCase<'HandleOpenFlag'>           // handle-open-flag
type B = KebabCase<'OpenFlag'>                 // open-flag

// 测试
let s1: A = "handle-open-flag"
let s2: B = "open-flag"

10.横杠命名转化为驼峰命名 CamelCase

// 实现
type CamelCase<T> = JoinArray<SplitString<T, '-'> >

type SplitString<
  T, 
  Separator extends string,
  A extends any[] = []
> = T extends `${infer L}${Separator}${infer R}`
  ? SplitString<R, Separator, [...A, L]>
  : [...A, T]

type JoinArray<T, S extends string = ''> = T extends [infer L, ...infer R]
  ? L extends string
    ? JoinArray<R, `${S}${Capitalize<L>}`>
    : S
  : S

// 使用
type A = CamelCase<"handle-open-flag">; // HandleOpenFlag
type B = CamelCase<"open-flag">; // OpenFlag
  
// 测试
let s1: A = "HandleOpenFlag"
let s2: B = "OpenFlag"

11.字符串拼接 JoinString

// 实现
type JoinString<
  T extends string, 
  K extends string, 
  Separator extends string
> = T extends ""
      ? K extends "" 
        ? never
        : K
      : K extends ""
        ? T
        : `${T}${Separator}${K}`

// 使用
type S1 = JoinString<"top", "title", ".">; // top.title
type S2 = JoinString<"", "title", ".">;    // title
type S3 = JoinString<"top", "", ".">;      // top
type S4 = JoinString<"", "", ".">;         // never

// 测试
let s1: S1 = "top.title"
let s2: S2 = "title"
let s3: S3 = "top"
function s4(): S4 {
  throw new Error('ccc')
}

11.得到对象中的值访问字符串 ObjectAccessPaths

// 简单来说,就是根据如下对象类型:
/*
{
    home: {
        topBar: {
            title: '顶部标题',
            welcome: '欢迎登录'
        },
        bottomBar: {
            notes: 'XXX备案,归XXX所有',
        },
    },
    login: {
        username: '用户名',
        password: '密码'
    }
}
*/
// 得到联合类型:
/*
home.topBar.title | home.topBar.welcome | home.bottomBar.notes | login.username | login.password
*/

// 实现
type JoinString<
  T extends string, 
  K extends string, 
  Separator extends string
> = T extends ""
      ? K extends "" 
        ? never
        : K
      : K extends ""
        ? T
        : `${T}${Separator}${K}`

type ObjectAccessPaths<
  T extends Record<string, any>,
  Pre extends string = '',
  K = keyof T
> = 
  K extends keyof T
    ? K extends string
      ? T[K] extends Record<string, any>
        ? ObjectAccessPaths<T[K], JoinString<Pre, K, '.'>>
        : JoinString<Pre, K, '.'>
      : never
    : never



// 使用
function createI18n<T>(obj: T): (path: ObjectAccessPaths<T>) => string {
  return (path) => ''
}
const i18n = createI18n({
	home: {
		topBar: {
			title: "顶部标题",
		},
		bottomBar: {
			notes: "XXX备案,归XXX所有",
		},
	},
	login: {
		username: "用户名",
	},
});

// 测试
i18n('home.bottomBar.notes')
i18n('home.topBar.title')
i18n('login.username')

// i18n('home.login.abc')              // error,不存在的属性
// i18n('home.topBar')                 // error,没有到最后一个属性

12.定义组件的监听事件类型 ComponentEmitsType

Day2.实现内置类型

1.计算元组类型的长度 LengthOfTuple

// 实现
type LengthOfTuple<T extends any[]> = T["length"]

// 使用
type A = LengthOfTuple<['B', 'F', 'E']>   // 3
type B = LengthOfTuple<[]>                // 0

// 测试
let n1: A = 3
let n2: B = 0

2.元组类型中的第一个元素 FirstItem

// 实现
type FirstItem<T extends any[]> = T extends [infer L, ...infer R] ? L : never

// 使用
type A = FirstItem<[string, number, boolean]> // string
type B = FirstItem<['B', 'F', 'E']>           // 'B'

// 测试
let n1: A = 'ccc'
let n2: B = 'B'

3.元组类型中的最后一个元素 LastItem

// 实现
type LastItem<T extends any[]> = T extends [...infer L, infer R] ? R : never

// 使用
type A = LastItem<[string, number, boolean]>  // boolean
type B = LastItem<['B', 'F', 'E']>            // 'E'
type C = LastItem<[]>                         // never

// 测试
let s1: A = 'ccc'
let s2: B = 'E'
function test(): C {
  throw new Error('ccc')
}

4.移除元组类型中的第一个类型 Shift

// 实现
type Shift<T extends any[]> = T extends [infer L, ...infer R] ? R : T

// 使用
type A = Shift<[1, 2, 3]> // [2,3]
type B = Shift<[1]>       // []
type C = Shift<[]>        // []

// 测试
let a1: A = [2,3]
let a2: B = []
let a3: C = []

5.在元组类型T中添加新的类型I Push

// 实现
type Push<T extends any[], V> = [...T, V]

// 使用
type A = Push<[1,2,3], 4> // [1,2,3,4]
type B = Push<[1], 2>     // [1, 2]

// 测试
let a1: A = [1,2,3,4]
let a2: B = [1, 2]

6.反转元组 ReverseTuple

// 实现
type ReverseTuple<
  T extends any[], 
  A extends any[] = []
> = T extends [infer L, ...infer R]
  ? ReverseTuple<R, [L, ...A]>
  : A

// 使用
type A = ReverseTuple<[string, number, boolean]>  // [boolean, number, string]
type B = ReverseTuple<[1, 2, 3]>                  // [3,2,1]
type C = ReverseTuple<[]>                         // []

// 测试
let a1: A = [true, 1, 'a']
let a2: B = [3,2,1]
let a3: C = []

7.拍平元组 Flat

// 实现
type Flat<
  T,
  A extends any[] = []
> = T extends [infer L, ...infer R]
  ? [...(L extends any[] ? Flat<L> : [L]), ...Flat<R>, ...A]
  : T

// 使用
type A = Flat<[1, 2, 3]>                  // [1,2,3]
type B = Flat<[1, [2, 3], [4, [5, [6]]]]> // [1,2,3,4,5,6]
type C = Flat<[]>                         // []
type D = Flat<[1]>                        // [1]

// 测试
let a1: A = [1,2,3]
let a2: B = [1,2,3,4,5,6]
let a3: C = []
let a4: D = [1]

8.复制类型T为C个元素的元组类型 Repeat

// 实现
type Repeat<
  T extends any, 
  K extends number,
  A extends any[] = []
> = A['length'] extends K
  ? A
  : Repeat<T, K, [T, ...A]>

// 使用
type A = Repeat<number, 3>  // [number, number, number]
type B = Repeat<string, 2>  // [string, string]
type C = Repeat<1, 1>       // [1]
type D = Repeat<0, 0>       // []

// 测试
let a1: A = [1, 2, 3]
let a2: B = ['a', 'a']
let a3: C = [1]
let a4: D = []

9.保留元组类型T中的A类型 Filter

// 实现
type Filter<
  T extends any[], 
  K,
  A extends K[] = []
> = T extends [infer L, ...infer R]
  ? Filter<R, K, [L] extends [K] ? [...A, L] : [...A]>
  : A

// 使用
type A = Filter<[1, 'BFE', 2, true, 'dev'], number>  // [1, 2]
type B = Filter<[1, 'BFE', 2, true, 'dev'], string>  // ['BFE', 'dev']
type C = Filter<[1, 'BFE', 2, any, 'dev'], string>   // ['BFE', any, 'dev']

// 测试
let a1: A = [1, 2]
let a2: B = ['BFE', 'dev']
let a3: C = ['BFE', true, 'dev']

10.找出E类型在元组类型T中的下标 FindIndex<T,E>

// 实现
type Equal<T, K> = [T] extends [K]
	? [K] extends [T]
		? keyof T extends keyof K
			? keyof K extends keyof T
				? true
				: false
			: false
		: false
	: false;
  
type FindIndex<T extends any[], K> = T extends [...infer L, infer R]
  ? Equal<R, K> extends true
    ? L['length']
    : FindIndex<L, K>
  : never


// 使用
type Test = [any, never, 1, '2', true]
type A = FindIndex<Test, 1> // 2
type B = FindIndex<Test, 3> // never

// 测试
let a1: A = 2
function a2(): B {
  throw new Error('ccc')
}

11.元组类型转换为枚举类型 TupleToEnum

// 实现
// 判断类型是否相等的时候都加[]
type Equal<T, K> = [T] extends [K]
	? [K] extends [T]
		? keyof T extends keyof K
			? keyof K extends keyof T
				? true
				: false
			: false
		: false
	: false;
  
type FindIndex<T extends any[], K> = T extends [...infer L, infer R]
  ? Equal<R, K> extends true
    ? L['length']
    : FindIndex<L, K>
  : never

type TupleToEnum<T extends any[], K extends boolean = false> = { 
  [key in T[number]]: K extends true ? FindIndex<T, key> : key
}


// 使用
// 默认情况下,枚举对象中的值就是元素中某个类型的字面量类型
type A = TupleToEnum<["MacOS", "Windows", "Linux"]>
// -> { readonly MacOS: "MacOS", readonly Windows: "Windows", readonly Linux: "Linux" }

// 如果传递了第二个参数为true,则枚举对象中值的类型就是元素类型中某个元素在元组中的index索引,也就是数字字面量类型
type B = TupleToEnum<["MacOS", "Windows", "Linux"], true>
// -> { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }

// 测试
let obj1: A =  {
  MacOS: "MacOS",
  Windows: "Windows",
  Linux: "Linux"
}

let obj2: B =  {
  MacOS: 0,
  Windows: 1,
  Linux: 2
}

12.截取元组中的部分元素 Slice

// 实现
type Slice<
  A extends any[], 
  S extends number, 
  E extends number = A['length'],
  Pre extends any[] = [],
  SA extends any[] = [],
  EA extends any[] = []
> = A extends [infer L, ...infer R]
  ? SA['length'] extends S
    ? EA['length'] extends E
      ? [...Pre, L]
      : Slice<R, S, E, [...Pre, L], SA, [...EA, null]>
    : Slice<R, S, E, Pre, [...SA, null], [...EA, null]>
  : Pre


// 使用
type A1 = Slice<[any, never, 1, '2', true, boolean], 0, 2>          // [any,never,1]                    从第0个位置开始,保留到第2个位置的元素类型
type A2 = Slice<[any, never, 1, '2', true, boolean], 1, 3>          // [never,1,'2']                    从第1个位置开始,保留到第3个位置的元素类型
type A3 = Slice<[any, never, 1, '2', true, boolean], 1, 2>          // [never,1]                        从第1个位置开始,保留到第2个位置的元素类型
type A4 = Slice<[any, never, 1, '2', true, boolean], 2>             // [1,'2',true,boolean]             从第2个位置开始,保留后面所有元素类型
type A5 = Slice<[any], 2>                                           // []                               从第2个位置开始,保留后面所有元素类型
type A6 = Slice<[], 0>                                              // []                               从第0个位置开始,保留后面所有元素类型

// 测试

13.删除并且替换部分元素 Splice

// 实现
type Splice<
  A extends any[], 
  S extends number, 
  L extends number,
  V extends any[] = [],
  Pre extends any[] = [],
  SA extends any[] = [],
  LA extends any[] = []
> = A extends [infer First, ...infer Rest]
  ? SA['length'] extends S
    ? LA['length'] extends L
      ? [...Pre, ...V, ...A]
      : Splice<Rest, S, L, V, Pre, SA, [...LA, null]>
    : Splice<Rest, S, L, V, [...Pre, First], [...SA, null], LA>
  : Pre


// 使用
type A1 = Splice<[string, number, boolean, null, undefined, never], 0, 2>                   // [boolean,null,undefined,never]               从第0开始删除,删除2个元素
type A2 = Splice<[string, number, boolean, null, undefined, never], 1, 3>                   // [string,undefined,never]                     从第1开始删除,删除3个元素
type A3 = Splice<[string, number, boolean, null, undefined, never], 1, 2, [1, 2, 3]>        // [string,1,2,3,null,undefined,never]          从第1开始删除,删除2个元素,替换为另外三个元素1,2,3

// 测试

Day3.实现内置类型 (联合类型以及对象类型)

1.获取对象类型中的可选属性的联合类型 OptionalKeys

// 实现
type ExcludeUndefined<T> = { [key in keyof T]: Exclude<T, undefined> }

type OptionalKeys<T, K = keyof T> = K extends keyof T
  ? undefined extends ExcludeUndefined<T>[K]
    ? K
    : never
  : never

// 使用
type a1 = OptionalKeys<{ foo: number | undefined, bar?: string, flag: boolean }>    // bar
type a2 = OptionalKeys<{ foo: number, bar?: string }>                               // bar
type a3 = OptionalKeys<{ foo: number, flag: boolean }>                              // never
type a4 = OptionalKeys<{ foo?: number, flag?: boolean }>                            // foo|flag
type a5 = OptionalKeys<{}> 

// 测试

2.获取对象类型中的可选属性的联合类型 PickOptional

// 实现
type ExcludeUndefined<T> = { [key in keyof T]: Exclude<T, undefined> }

type OptionalKeys<T, K = keyof T> = K extends keyof T
  ? undefined extends ExcludeUndefined<T>[K]
    ? K
    : never
  : never

type PickOptional<T> = Pick<T, OptionalKeys<T>>

// 使用
type a1 = PickOptional<{ foo: number | undefined, bar?: string, flag: boolean }>        // {bar?:string|undefined}
type a2 = PickOptional<{ foo: number, bar?: string }>                                   // {bar?:string}
type a3 = PickOptional<{ foo: number, flag: boolean }>                                  // {}
type a4 = PickOptional<{ foo?: number, flag?: boolean }>                                // {foo?:number,flag?:boolean}
type a5 = PickOptional<{}>                                                              // {}

// 测试

3.获取对象类型中的必须属性的联合类型 RequiredKeys

// 实现
type ExcludeUndefined<T> = { [key in keyof T]: Exclude<T, undefined> }

type RequiredKeys<T, K = keyof T> = K extends keyof T
  ? undefined extends ExcludeUndefined<T>[K]
    ? never
    : K
  : never


// 使用
type a1 = RequiredKeys<{ foo: number | undefined, bar?: string, flag: boolean }>        // foo|flag
type a2 = RequiredKeys<{ foo: number, bar?: string }>                                   // foo
type a3 = RequiredKeys<{ foo: number, flag: boolean }>                                  // foo|flag
type a4 = RequiredKeys<{ foo?: number, flag?: boolean }>                                // never
type a5 = RequiredKeys<{}>                                                              // never

// 测试

4.保留一个对象中的必须属性 PickRequired

// 实现
type ExcludeUndefined<T> = { [key in keyof T]: Exclude<T, undefined> }

type RequiredKeys<T, K = keyof T> = K extends keyof T
  ? undefined extends ExcludeUndefined<T>[K]
    ? never
    : K
  : never

type PickRequired<T> = Pick<T, RequiredKeys<T>>

// 使用
type a1 = PickRequired<{ foo: number | undefined, bar?: string, flag: boolean }>        // {foo:number|undefined,flag:boolean}
type a2 = PickRequired<{ foo: number, bar?: string }>                                   // {foo:number}
type a3 = PickRequired<{ foo: number, flag: boolean }>                                  // {foo:number,flag:boolean}
type a4 = PickRequired<{ foo?: number, flag?: boolean }>                                // {}
type a5 = PickRequired<{}>                                                              // {}

// 测试

5.合并两个对象类型T以及K,如果属性重复,则以K中属性类型为准; Merge

// 实现
// 对象属性重叠和不重叠这些优先考虑内置函数
type Merge<T, E> = { [K in Exclude<keyof T, keyof E>]: T[K] } & E

// 使用
type obj1 = { el: string, age: number }
type obj2 = { el: HTMLElement, flag: boolean }
type obj3 = Merge<obj1, obj2>   // {el:HtmlElement,age:number,flag:boolean}

// 测试
const a = {...{} as obj3}
console.log(a.el.scrollTop, a.age.toFixed(0), a.flag.valueOf())
// console.log(a.el.charAt(0))     // error

6.判断是否为never类型 IsNever

// 实现
type IsNever<T> = [T] extends [never] ? true : false

// 使用
type A = IsNever<never> // true
type B = IsNever<string> // false
type C = IsNever<undefined> // false
type D = IsNever<any> // false

// 测试

7.判断是否为没有属性的对象类型{} IsEmptyType

// 实现
type IsEmptyType<T> = string extends T
  ? keyof T extends never
    ? T extends {} 
      ? true 
      : false
    : false
  : false

// 使用
type A = IsEmptyType<string>    // false
type B = IsEmptyType<{ a: 3 }>  // false
type C = IsEmptyType<{}>        // true
type D = IsEmptyType<any>       // false
type E = IsEmptyType<object>    // false 对象类型(包含number、string、boolean等)
type F = IsEmptyType<Object>    // false 大写Object是一个全局变量
type G = IsEmptyType<unknown>   // false

// 测试

8.判断是否为any类型 IsAny

// 实现
type IsAny<T> = unknown extends T 
  ? [T] extends [string]
    ? true
    : false  
  : false

// 使用
type A = IsAny<string> // false
type B = IsAny<any> // true
type C = IsAny<unknown> // false
type D = IsAny<never> // false

// 测试

9.实现Connect类型,能够自动地转化Redux Module对象中的函数类型 Redux Connect

10.有且只有一个属性 UnionToBooleanProps

11.将联合类型转换为交叉类型 UnionToIntersection

// 实现
// 使用函数取出联合类型的值
type UnionToIntersection<T> = (T extends any ? (t: T) => any : never) extends (r: infer R) => any
  ? R
  : never

// 使用
type A = UnionToIntersection<{a: string} | {b: string} | {c: string}> 
// {a: string} & {b: string} & {c: string}

// 测试

X12.取出联合类型中的任意一个类型 UnionPop

// 实现
// ((x: 1) => void) & ((x: 2) => void) & ((x: 3) => void) 利用重载函数只取最后一个的特性
type UnionPop<U> = (
	(U extends any ? (k: (x: U) => void) => void : never) extends (
		k: infer I
	) => void
		? I
		: never
) extends (a: infer A) => void
	? A
	: never;

// 使用
type a = 1 | 2 | 3;
type b = UnionPop<a>; // 3

// 测试

13.联合类型转换为元组类型 UnionToTuple


文章作者: Alex
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Alex !
  目录