更好维护的 TypeScript

前言

接触了 TypeScript,JavaScript 便成为了备选。

Interface & Type Aliases

Type Aliases,即 “类型别名”,注意它是别名而不是真正的类型。

interfaceopen in new window 从官网看来可以描述:对象、函数、数组、Class。

相似

1. interface extends interface

interface Animal {
  name: string;
}
interface Dog extends Animal {
  say(): string
}

2. type & type

type Animal = {
  name: string;
}
type Dog = Animal & {
  say(): string
}

3. interface extends type

type Animal = {
  name: string;
}
interface Dog extends Animal {
  say(): string
}

4. type & interface

interface Animal {
  name: string;
}
type Dog = Animal & {
  say(): string
}

差异

1. Tuple

type Tuple = [number, string]
interface ITuple {
  0: number
  1: string
}

[1, 'hello', false] as Tuple // ERROR
[1, 'hello', false] as ITuple // OK

2. 合并

type Once = { a: string }
type Once = { b: string }
// Duplicate identifier 'Once'.

interface IOnce {
  a: string
}
interface IOnce {
  b: string
}
// 合并为
// interface IOnce {
//   a: string
//   b: string
// }

3. 工具类型

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

使用 Unknow 替代 any

每当想使用 any 时,都应先考虑 unknown,它是 any 类型对应的安全类型,即在对 unknown 类型的值执行大多数操作之前,我们必须进行某种形式的检查。

Unknown

unknown 类型可以接受任何类型。

let value: unknown
let v1: any
const v2: bigint = 100n

value = false // OK
value = 6 // OK
value = 'blue' // OK
value = [1, 2, 3] // OK
value = ['hello', 10] // OK
value = null // OK
value = undefined // OK
value = new Error('hi') // OK
value = { o: 'hello' } // OK
value = Symbol('hello') // OK
value = v1 // OK
value = v2 // OK

unknown 类型只能赋予 unknownany 类型。

let value: unknown

const v1: any = value // OK
const v2: unknown = value // OK
const v3: boolean = value // ERROR
const v4: number = value // ERROR
const v5: bigint = value // ERROR
const v6: string = value // ERROR
const v7: any[] = value // ERROR
const v8: null = value // ERROR
const v9: undefined = value // ERROR
const v10: never = value // ERROR

联合类型

除了和 any 类型外都返回 unknown 类型。

type U1 = unknown | any // any
type U2 = unknown | number // unknown
type U3 = unknown | string // unknown
type U4 = unknown | any[] // unknown
type U5 = unknown | null // unknown
type U6 = unknown | undefined // unknown
type U7 = unknown | never // unknown

交叉类型

type I1 = unknown & any // any
type I2 = unknown & number // number
type I3 = unknown & string // string
type I4 = unknown & any[] // any[]
type I5 = unknown & null // null
type I6 = unknown & undefined // undefined
type I7 = unknown & never // never

内置的工具类型

想成为一个好的 TypeScript 开发者,那必须熟练使用它的工具类型。

前置条件

泛型open in new window是一切工具类型的基础,可以当作函数中的入参,而想要进一步则还需要借助 Type Manipulationopen in new window

1. Keyof Type Operator

keyof 可用来获取 “对象类型” 的 key,并组成 union

type Point = {
  x: number
  y: number
}

type P = keyof Point
// type P = "x" | "y"

type Mapish = {
  [k: string]: string
}

type M = keyof Mapish
// type M = string | number

2. Typeof Type Operator

在 JavaScript 的 “expression context” 中 typeof 可以返回对应的类型,而在 TypeScript 的 “type context” 中 typeof 同样可以返回对应的类型。

let s = 'hello'
type N = typeof s
// type N = string

function f () {
  return { x: 10, y: 3 }
}
type F = typeof f
// type F = () => {x: number; y: number;}

3. Indexed Access Types

索引类型为 type 增加类似于 JavaScript 对象数组的索引。

// 对象
type Person = {
  age: number
  name: string
  alive: boolean
}

type I1 = Person['age' | 'name'];
// type I1 = string | number

type I2 = Person[keyof Person];
// type I2 = string | number | boolean

type AliveOrName = 'alive' | 'name';
type I3 = Person[AliveOrName];
// type I3 = string | boolean

// 数组
const MyArray = [
  { name: 'Alice', age: 15 },
  { name: 'Bob', age: 23 },
  { name: 'Eve', age: 38 }
]

type Person = typeof MyArray[number];
// type Person = {
//   name: string;
//   age: number;
// }

4. Conditional Types

基本与 JavaScript 的三元运算符一致。

type MessageOf<T> = T extends { message: unknown } ? T['message'] : never;

interface Email {
  message: string
}

interface Dog {
  bark(): void
}

type EmailMessageContents = MessageOf<Email>
// type EmailMessageContents = string

type DogMessageContents = MessageOf<Dog>
// type DogMessageContents = never

5. Mapped Types

映射类型配合上泛型,就造就了工具类型的基石。

其中修饰符可以改变原有约束,- 用于取消,+ 则相反且为默认值:

type CreateMutable<Type> = {
  -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
  readonly id: string;
  readonly name: string;
};

type UnlockedAccount = CreateMutable<LockedAccount>;
// type UnlockedAccount = {
//   id: string;
//   name: string;
// }

type Concrete<Type> = {
  [Property in keyof Type]-?: Type[Property];
};

type MaybeUser = {
  id: string;
  name?: string;
  age?: number;
};

type User = Concrete<MaybeUser>;
// type User = {
//   id: string;
//   name: string;
//   age: number;
// }

对属性进行操作,编辑和移除:

type Getters<Type> = {
  [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};

interface Person {
  name: string;
  age: number;
  location: string;
}

type LazyPerson = Getters<Person>;
// type LazyPerson = {
//   getName: () => string;
//   getAge: () => number;
//   getLocation: () => string;
// }

type RemoveKindField<T> = {
  [K in keyof T as Exclude<K, 'kind'>]: T[K]
};

interface Circle {
  kind: 'circle';
  radius: number;
}

type KindlessCircle = RemoveKindField<Circle>;
// type KindlessCircle = {
//   radius: number;
// }

6. Template Literal Types

很好,就差支持正则了。

7. extends & in

extendsin 都可以作为约束行为,实际使用中是让人疑惑的open in new window,其中一条评论不知是否正确:“extends 总是用在泛型上,in 总是用在索引上”。

在泛型上提前做约束可以解决一些问题:

function prop<T, K extends keyof T> (obj: T, key: K) {
  return obj[key]
}

function prop2<T> (obj: T, key: keyof T) {
  return obj[key]
}

let user = {
  name: 'shanyuhai',
  age: 18
}

let u1 = prop(user, 'name') // u1: string
let u2 = prop2(user, 'age') // u2: string | number

其中的缘由很简单,根据参数来看,u1 返回类型是 T[K],而 u2 返回类型是 T[keyof T]

8. infer

inferextends 条件语句中表示将要推断的类型。

工具类型的使用

1. Partial

Define:

/**
 * 将 T 中的所有的属性都变为可选
 */
type Partial<T> = {
    [P in keyof T]?: T[P]
};

Example:

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'
})

2. Readonly

Define:

/**
 * 将 T 中的所有属性变为只读属性
 */
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

Example:

interface Todo {
  title: string
}

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

todo.title = 'Hello' // ERROR
// Cannot assign to 'title' because it is a read-only property.

3. Record

Define:

/**
 * 将 K 中所有的属性的值转为 T 类型
 */
type Record<K extends keyof any, T> = {
  [P in K]: T
}

Example:

interface PageInfo {
  title: string
}

type Page = 'home' | 'about' | 'contact'

const nav: Record<Page, PageInfo> = {
  about: { title: 'about' },
  contact: { title: 'contact' },
  home: { title: 'home' }
}

4. Pick

Define:

/**
 * 从 T 类型中挑选符合 K 类型的
 */
type Pick<T, K extends keyof T> = {
  [P in K]: T[P]
}

Example:

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

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

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

5. Exclude

Define:

/**
 * 从 U 中移除可以赋值给 K 的类型
 */
type Exclude<U, K> = U extends K ? never : U

Example:

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

6. Omit

Define:

/**
 * 从 T 中移除 K 属性
 */
type Omit<T, K> = {
  [P in Exclude<keyof T, K>]: T[P]
}

Example:

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

type TodoPreview = Omit<Todo, 'description'>

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

7. Extract

Define:

/**
 * 从 T、U 类型中获取交集
 */
type Extract<T, U> = T extends U ? T : never

Example:

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

8. NonNullable

Define:

/**
 * 从 U 类型中移除 null 和 undefined
 */
type NonNullable<U> = U extends null | undefined ? never : U

Example:

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

9. Parameters

Define:

/**
 * 从 T 类型中获取参数类型
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

Example:

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

type T0 = Parameters<() => string>;
// type T0 = []
type T1 = Parameters<(s: string) => void>;
// type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;
// type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;
// type T3 = [arg: {
//   a: number;
//   b: string;
// }]
type T4 = Parameters<any>;
// type T4 = unknown[]
type T5 = Parameters<never>;
// type T5 = never
type T6 = Parameters<string>; // ERROR
// Type 'string' does not satisfy the constraint '(...args: any) => any'.
// type T6 = never
type T7 = Parameters<Function>; // ERROR
// Type 'Function' does not satisfy the constraint '(...args: any) => any'.
// Type 'Function' provides no match for the signature '(...args: any): any'.
// type T7 = never

10. ConstructorParameters

Define:

/**
 * 从 T 类型中获取构造函数的参数类型
 */
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never

Example:

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

type T4 = ConstructorParameters<Function>; // ERROR
// Type 'Function' does not satisfy the constraint 'new (...args: any) => any'.
// Type 'Function' provides no match for the signature 'new (...args: any): any'.
// type T4 = never

11. ReturnType

Define:

/**
 * 从 T 类型中获取返回类型
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : never

Example:

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

type T0 = ReturnType<() => string>;
// type T0 = string
type T1 = ReturnType<(s: string) => void>;
// type T1 = void
type T2 = ReturnType<<T>() => T>;
// type T2 = unknown
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
// type T3 = number[]
type T4 = ReturnType<typeof f1>;
// type T4 = {
// a: number;
// b: string;
// }
type T5 = ReturnType<any>;
// type T5 = any
type T6 = ReturnType<never>;
// type T6 = never
type T7 = ReturnType<string>;
// Type 'string' does not satisfy the constraint '(...args: any) => any'.
// type T7 = any
type T8 = ReturnType<Function>;
// Type 'Function' does not satisfy the constraint '(...args: any) => any'.
// Type 'Function' provides no match for the signature '(...args: any): any'.
// type T8 = any

12. InstanceType

Define:

/**
 * 从 T 类型中获取实例类型
 */
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : never

Example:

class C {
  x = 0;
  y = 0;
}

type T0 = InstanceType<typeof C>;
// type T0 = C
type T1 = InstanceType<any>;
// type T1 = any
type T2 = InstanceType<never>;
// type T2 = never
type T3 = InstanceType<string>;
// Type 'string' does not satisfy the constraint 'new (...args: any) => any'.
// type T3 = any
type T4 = InstanceType<Function>;
// Type 'Function' does not satisfy the constraint 'new (...args: any) => any'.
// Type 'Function' provides no match for the signature 'new (...args: any): any'.
// type T4 = any

13. Required

Define:

/**
 * 将 T 中的所有的属性都变为必选
 */
type Required<T> = {
  [P in keyof T]-?: T[P]
}

Example:

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

const obj: Props = { a: 5 }

const obj2: Required<Props> = { a: 5 }
// Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.

14. ThisParameterType

Define:

/**
 * 从 T 类型获取 this
 */
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown

Example:

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

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

15. OmitThisParameter

Define:

/**
 * 从 T 类型中移除 this
 */
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T

Example:

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

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

console.log(fiveToHex())

16. ThisType

Define:

/**
 * 将 T 注入为 this
 */
interface ThisType<T> { }

Example:

直接看定义一脸懵逼,而在示例中则很明显了,是否想起了被增强的 Vue Options 写法呢?

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // Type of 'this' in methods is 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; // Strongly typed this
      this.y += dy; // Strongly typed this
    },
  },
});

obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);

TypeScript 工具

参考资料

  1. TypeScriptopen in new window
  2. TSConfig Referenceopen in new window
  3. TypeScript Blogsopen in new window
  4. TypeScript Specopen in new window