TypeScript中内置类型的实现(一)

353 阅读5分钟

前言

在实际开发者,部分ts内置类型只有在高版本的ts中才能使用。在低版本ts中我们可以自定义内置类型来达到完全一样的效果,同时有利于我们对ts更深层次的理解,快速提升ts编程能力。

简单的内置类型实现

1. MyPick

// MyPick类型定义
type MyPick<T, K extends keyof T> = {[Item in K]: T[Item]}
// 使用
interface Todo {
  title: string
  description: string
  completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
// checked pass
const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}

2. MyReadonly

关键点:申明readonly属性

// MyReadonly的类型定义
type MyReadonly<T> = {readonly [P in keyof T]: T[P]}
// 使用
interface Todo {
  title: string
  description: string
}
const todo: MyReadonly<Todo> = {
  title: "Hey",
  description: "foobar"
}
// checked no pass
todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property

2.1 MyReadonly2

说明:升级版readonly;实现一个通用MyReadonly2<T, K>,它带有两种类型的参数T和K。
仅对指定的属性设置仅读,K指定应设置为Readonly的T的属性集。如果未提供K,则应使所有属性都变为只读,就像普通的Readonly一样。

// 类型定义
type MyReadonly2<T, K = keyof T> = {
  readonly [Key in keyof T as Key extends K ? Key : never] : T[Key]
} & {
  [Key in keyof T as Key extends K ? never : Key] : T[Key]
}
// 使用
interface Todo {
  title: string
  description: string
}
MyReadonly2<Todo, 'title'> // expected {readonly title: string; description: string}

关键点:使用交叉类型和as语法

3. TupleToObject

说明:传入一个元组类型,将这个元组类型转换为对象类型,这个对象类型的键/值都是从元组中遍历出来。
关键点:T[number]把数组转成联合类型

// 类型定义
type TupleToObject<T extends readonly string[]> = {[P in T[number]]: P}
// 使用
const tuple = ['test', 'a', 'b'] as const
type result = TupleToObject<typeof tuple>
// expected { test: 'test', a: 'a', b: 'b'}

4. First

说明:实现一个通用First,它接受一个数组T并返回它的第一个元素的类型。

// 类型定义【选其一】
type First<T extends any[]> = T extends [infer R, ...infer O] ? R : never
type First<T extends any[]> = T['length'] extends 0 ? never : T[0]
type First<T extends any[]> = T extends [] ? never : T[0]
type First<T extends any[]> = T[0] extends T[number] ? T[0] : never
// 使用
type A1 = ['a', 'b', 'c']
type F1 = First<A1> // expected 'a'

5. Length

// 类型定义
type Length<T extends any[]>=T['length']
// 使用
type A = ['a', 'b', 'c']
type L = Length<A> // expected 3

6. Exclude

说明:实现内置的Exclude <T, U>类型,返回排除类型U的类型

// 类型定义
type MyExclude<T, U> = T extends U ? never : T
// 使用
type AlphabetType = 'a'|'b'|'c'|'d'
type AlphabetTypeExclude = MyExclude<AlphabetType, 'a'|'b'> // expected 'c'|'d'

关键点:是如何移除U,T中的某一项如果在U中(T extends U),返回never移除U,否则返回T。 never和联合类型返回的结果会忽略掉never,extends+三元对于联合类型有遍历的含义存在。

7. Awaited

说明:拿到Promise结果的类型

// 类型定义
type MyAwaited<T> = T extends Promise<infer P> ? MyAwaited<P> : T
// 使用
const a = Promise.resolve('a')
type CurMyAwaited = MyAwaited<typeof a> // expected string

8. If

说明:根据条件返回类型
实现一个 IF 类型,它接收一个条件类型 C ,一个判断为真时的返回类型 T ,以及一个判断为假时的返回类型 F。 C 只能是 true 或者 false, T 和 F 可以是任意类型。

// 类型定义
type If<C extends boolean, T, F> = C extends true ? T : F
// 使用
type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

9. Concat

说明:在类型系统里实现 JavaScript 内置的 Array.concat 方法,这个类型接受两个参数,返回的新数组类型应该按照输入参数从左到右的顺序合并为一个新的数组。

// 类型定义[选其一]
type Concat<T extends any[] , U extends any[]> = [...T, ...U]
type Concat<T extends unknown[], U extends unknown[]> = 
  T extends [...infer R] 
    ? U extends [...infer S] 
      ? [...R, ...S] : never : never
// 使用
type Result = Concat<[1], [2]> // expected to be [1, 2]

关键点:通过...解构泛型变量

10. Includes

说明:在类型系统里实现 JavaScript 的 Array.includes 方法,这个类型接受两个参数,返回的类型要么是 true 要么是 false。

// 类型定义[选其一]
type Includes<T extends any[], P> = P extends T[number] ? true : false
type Includes<T extends readonly any[], U> = T extends [infer F, ...infer R] 
 ? Equal<U, F> extends true 
 ? true
 : Includes<R, U> : false;
 // 使用
 Includes<['a', 'b', 'c'], 'd'> // expected false

11. Push

说明:在类型系统里实现通用的 Array.push

// 类型定义
type Push<T extends any[], K> = [...T, K]
type PushFlat<T extends any[], U> = U extends any[] ? [...T, ...U] : [...T, U extends boolean ? boolean : U];
 // 使用
type Result = Push<[1, 2], '3'> // expected [1, 2, '3']

12. unshift

// 类型定义
type Unshift<T extends any[], K>=[K, ...T]
// 使用
type Result = Unshift<[1, 2], 0> // expected [0, 1, 2,]

13. MyParameters

说明:实现内置的 Parameters 类型,而不是直接使用它,可参考TypeScript官方文档。

// 类型定义
type MyParameters<T> = T extends (...args: infer U) => unknown ? U : never
// 使用
const func = (a:string, b: number)=> a+b
MyParameters<typeof func> // expected [a: string, b: number]

关键点:使用extends构造函数类型,使用infer推断参数类型

14. ReturnType

说明:不使用ts的 ReturnType 实现 TypeScript 的 ReturnType 泛型。

// 类型定义
type MyReturnType<T extends (...args: any[])=> any> = T extends (...args: any[])=> infer K ? K : never
// 使用
const fn = (v: boolean) => {
  if (v)
    return 1
  else
    return 2
}
type A = MyReturnType<typeof fn> // 导出类型为 "1 | 2"

15. MyOmit

说明:实现 TypeScript 的 Omit<T, K> 泛型。

// 类型定义
type MyOmit<T extends {[key:string]: any}, K extends keyof T> = {[X in keyof T as X extends K ? never : X]: T[X]}
// 使用
interface Todo {
  title: string
  description: string
  completed: boolean
}
MyOmit<Todo, 'completed' | 'description'> // expected {title: string}

16. 深度 Readonly

说明:实现 TypeScript 的 Omit<T, K> 泛型。

// 类型定义
type DeepReadonly<T> ={
  readonly [K in keyof T]: keyof T[K] extends never ? T[K] : DeepReadonly<T[K]>
}
// 使用
type X = { 
  x: { 
    a: 1
    b: 'b'
  }
  y: 'y'
}
DeepReadonly<X>
// 结果
type Expected = { 
  readonly x: { 
    readonly a: 1
    readonly b: 'b'
  }
  readonly y: 'y' 
}

关键点:运用ts深度递归类型

相关链接

ts官方文档
type-challenges

后续会继续完善常用的ts类型定义,如果觉得有帮助,不妨点赞、关注支持一下。如文章有不足之处、疑问或建议,希望能在下方👇🏻 留言,非常感谢。

作者: tager
相关文章地址:https://juejin.cn/user/4353721776234743/posts
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。