类型计算

Keyof 操作符

type Point = { x: number; y: number }
type P = keyof Point // "x" | "y"

type Arrayish = { [n: number]: unknown }
type A = keyof Arrayish // number

type Mapish = { [k: string]: boolean }
type M = keyof Mapish // string | number
typescript

M 类型是 string | number ,因为对于对象来说,虽然规定 k 是 string,但是也可以使用数字作为键。

typeof

console.log(typeof 'xxx') // string

let s = 'hello'
let n: typeof s // string
typescript

typeof 在 javascript 是计算操作符,不过在 ts 中,属于类型操作符(不是运行时执行,coding 时也会检查)。

Partial Type

部分类型,ts 的延伸能力。

interface Todo {
  title: string
  description: string
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return {
    ...todo,
    ...fieldsToUpdate
  }
}

const todo1 = {
  title: 'organize desk',
  description: 'clear clutter'
}
const todo2 = updateTodo(todo1, {
  description: 'throw out trash'
})
typescript

如何实现?

type Partial<T> = {
  [P in keyof T]?: T[P]
}
typescript

Required

interface Props {
  a?: number
  b?: number
}

const obj: Props = { a: 5 }
const obj2: Required<Props> = { a: 5 }
// 类型 "{ a: number; }" 中缺少属性 "b",但类型 "Required<Props>" 中需要该属性。
typescript

如何实现?

type Required<T> = {
  [P in keyof T]-?: T[P]
}
typescript

Readonly

interface Todo {
  title: string
}

const todo: Readonly<Todo> = {
  title: 'Delete inactive users'
}

todo.title = 'Hello'
// 无法为“title”赋值,因为它是只读属性。
typescript

如何实现?

type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}
typescript

Record

记录。描述 key value 值的类型。

interface CatInfo {
  age: number
  breed: string
}

type CateName = 'miffy' | 'boris' | 'mordred'

const cats: Record<CateName, CatInfo> = {
  miffy: { age: 10, breed: 'persian' },
  boris: { age: 5, breed: 'Maine Coon' },
  mordred: { age: 10, breed: 'Britsh Shorthair' }
}
typescript

如何实现?

type Record<K extends keyof any, T> = {
  [P in K]: T
}
typescript

日常使用:

const obj = Record<string, object>
typescript

Pick

interface Todo {
  title: string
  description: string
  completed: boolean
}

type TodoPreview = Pick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
  title: 'Clean room',
  completed: false
}
typescript

如何实现?

type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}
typescript

Exclude

type T0 = Exclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
type T1 = Exclude<'a' | 'b' | 'c', 'a' | 'b'> // 'c'
type T2 = Exclude<string | number | (() => void), Function> // string | number
typescript

如何实现?

type Exclude<T, U> = T extends U ? never : T
typescript

Omit

省略。与 Pick 是相反的操作。

Exclude 操作的是联合类型,Omit 操作的是接口。

interface Todo {
  title: string
  description: string
  completed: boolean
  createdAt: number
}

type TodoPreview = Omit<Todo, 'description'>
const todo: TodoPreview = {
  title: 'Clean room',
  completed: false,
  createdAt: 1681344482208
}

type TodoInfo = Omit<Todo, 'completed' | 'createdAt'>
const todoInfo: TodoInfo = {
  title: 'Pick up kids',
  description: 'Kindergarten closes at 5pm'
}
typescript

如何实现:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
typescript

Extract

type T0 = Extract<'a' | 'b' | 'c', 'a' | 'f'> // 'a'
type T1 = Extract<string | number | (() => void), Function> // () => void
typescript

如何实现?

type Extract<T, U> = T extends U ? T : never
typescript

可以看到,Extract 和 Exclude 是一对组合;Pick 和 Omit 是一对组合。

NonNullable

type T0 = NonNullable<string | number | undefined> // string | number
type T1 = NonNullable<string[] | null | undefined> // string[]
typescript

如何实现:

 type NonNullable<T> = T extends null | undefined ? never : T
typescript

Parmameters

declare function f1(args: { a: number; b: string }) : void

type T0 = Parameters<() => string> // []
type T1 = Parameters<(s: string) => void> // [s: string]
type T2 = Parameters<<T>(args: T) => T> // [args: unknown]
type T3 = Parameters<typeof f1> // [args: { a:number; b: string }]
typescript

如何实现:

type Parameters<T extends (...args: any) => any> = 
	T extends (...args: infer P) => any ? P : never 
typescript

ConstructorParameters

type T0 = ConstructorParameters<ErrorConstructor> // [message?: string | undefiend]
type T1 = ConstructorParameters<FunctionConstructor> // string[]
type T2 = ConstructorParameters<RegExpConstructor> // [pattern: string | RegExp, flags?: string | undefined]
type T3 = ConstructorParameters<any> // unkown[]

type T4 = ConstructorParameters<Function>
// 类型“Function”不满足约束“abstract new (...args: any) => any”。
// 类型“Function”提供的内容与签名“new (...args: any): any”不匹配。
typescript

如何实现:

type ConstructorParameters<T extends abstract new (...args: any) => any> =
  T extends abstract new (...args: infer P) => any ? P : never
typescript

ReturnType

declare function f1(): { a: number; b: string }

type T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
type T2 = ReturnType<<T>() => T> // unkown
type T3 = ReturnType<<T extends U, U extends number[]>() => T> // number[]
type T4 = ReturnType<typeof f1> // { a: number; b: string }
type T5 = ReturnType<any> // any
type T6 = ReturnType<never> // never
type T7 = ReturnType<string> // 类型“string”不满足约束“(...args: any) => any”。
type T8 = ReturnType<Function> // 类型“Function”不满足约束“(...args: any) => any”。
typescript

如何实现:

type ReturnType<T extends (...args: any) => any> = 
	T extends (...args: any) => infer R ? R : any
typescript

InstanceType

class C {
  x = 0
  y = 0
}

type T0 = InstanceType<typeof C> // C
type T1 = InstanceType<any> // any
type T2 = InstanceType<never> // never
type T3 = InstanceType<string> // 类型“string”不满足约束“abstract new (...args: any) => any”。
type T4 = InstanceType<Function> // 类型“Function”不满足约束“abstract new (...args: any) => any”。
typescript

如何实现?

type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (
  ...args: any
) => infer R
  ? R
  : any
typescript

ThisParameterType

function toHex(this: Number) {
  return this.toString(16)
}

function numberToString(n: ThisParameterType<typeof toHex>) {
  return toHex.apply(n)
}
typescript

如何实现?

type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown
typescript

OmitThisParameter

function toHex(this: Number) {
  return this.toString(16)
}

const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5)
console.log(fiveToHex())
typescript

如何实现?

type OmitThisParameter<T> = unknown extends ThisParameterType<T>
  ? T
  : T extends (...args: infer A) => infer R
  ? (...args: A) => R
  : T
typescript

ThisType

提供 this 提示。

type ObjectDescriptor<D, M> = {
  data?: D
  methods: M & ThisType<D & M>
}

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {}
  let methods: object = desc.methods || {}
  return {
    ...data,
    ...methods
  } as D & M
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx
      this.y += dy
    }
  }
})

obj.x = 10
obj.y = 20
obj.moveBy(5, 5)
typescript
// tsconfig.json

{
  "compilerOptions": {
    "target": "es6",
    "lib": ["es6"],
    "esModuleInterop": true,
    "noImplicitThis": true, // 需要打开这个配置
    "downlevelIteration": true,
    "module": "CommonJS"
  }
}
json

如何实现?

interface ThisType<T> {}
typescript

Uppercase/LowerCase

type Greeting = 'Hello, world'
type ShoutGreeting = Uppercase<Greeting> // HELLO, WORLD

type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
type MainID = ASCIICacheKey<'my_app'> // ID-MY_APP

type QuietGreeting = Lowercase<ShoutGreeting> // hello, world
typescript

如何实现?

type Uppercase<S extends string> = intrinsic
type Lowercase<S extends string> = intrinsic
type Capitalize<S extends string> = intrinsic
type Uncapitalize<S extends string> = intrinsic
typescript

intrinsic 代表这个实现是内部实现,不是 ts 直接可以使用。

总结

类型是可以计算的吗?答案当然是肯定的。

  • &
  • ?
  • infer