Typescript 使用技巧

88 阅读8分钟

写在前面

使用 typescript也有一段时间了,这里分享一些typescript里类型定义的小技巧

关键字

any & unknown

any 是typescript 的顶级类型。unknown 是any对应的安全类型。
在项目使用中,所有的any都可以使用unknown代替。

let value: any;

// any 的赋值
value = true;             // OK
value = 42;               // OK
value = "Hello World";    // OK
value = [];               // OK
value = {};               // OK
value = Math.random;      // OK
value = null;             // OK
value = undefined;        // OK
value = new TypeError();  // OK
value = Symbol("type");   // OK

// any 的调用
value.foo.bar;  // OK
value.trim();   // OK
value();        // OK
new value();    // OK
value[0][1];    // OK
let value: unknown;

// unknown 的赋值,只能将 unknown 类型的变量赋值给 any 和 unknown
let value1: unknown = value;   // OK
let value2: any = value;       // OK
let value3: boolean = value;   // Error
let value4: number = value;    // Error
let value5: string = value;    // Error
let value6: object = value;    // Error
let value7: any[] = value;     // Error
let value8: Function = value;  // Error

// unknown 的调用
value.foo.bar;  // Error
value.trim();   // Error
value();        // Error
new value();    // Error
value[0][1];    // Error

unknown 在使用时必须要明确类型

const arr: unknown = [1, 2, 3];

(arr as Array<number>).join(',');

if (Array.isArray(arr)) {
  arr.join(',');
}

interface & type

一般来说,interface 与 type 区别很小,比如以下两种写法差不多

interface A {
  a: number;
  b: number;
};

type B = {
  a: number;
  b: number;
}

其中 interface 可以如下合并多个,而 type 只能使用 & 类进行连接。

interface A {
    a: number;
}

interface A {
    b: number;
}

const a: A = {
    a: 3,
    b: 4
}

interface 和 type 都可以用来定义继承的类。
举个例子,实现一个注册自定义事件的功能

//  定义基础 事件 类型
class Action {
  name: string = 'action';

  constructor() {
    this.init();
  }

  init() {
  }

  apply() {
  }
}

type ActionChildClass = new() => Action;
// 等价于 type ActionChildClass = typeof Action;

// or
// interface 可以新增其他属性,
interface ActionChildClass {
  otherPrototype: string;

  new(): Action;
}

// 注册自定义事件的函数,参数 ActionClass 就是继承自 Action 的自定义事件
function registerAction(ActionClass: ActionChildClass) {
  // 实现逻辑
}

registerAction 函数实现注册自定义事件的功能,参数是继承 Action 的类。
使用 type 与 interface 都能实现,interface 还可以新增其他属性,React 里面 ComponentClass 就是使用这种方式定义的。

interface 还可以定义函数:

interface AddFn {
  (a: number, b: number): number;
}

// 等价于
type AddFn = (a: number, b: number) => number;

一般使用在定义重载的函数,例如 Array 的 splice 方法

interface Array<T> {
  splice(start: number, deleteCount?: number): T[];

  splice(start: number, deleteCount: number, ...items: T[]): T[];
}

单纯的函数重载也可以这样写:

// 使用 interface
interface AddFn {
  (a: number, b: number): number;

  (a: number, b: number, c: number, d: number): number;
}

const add1: AddFn = function () {
  return [...arguments].reduce((a, b) => a + b, 0);
};

add1(1, 2); // OK
add1(1, 2, 3); // Error
add1(1, 2, 3, 4); // OK

// 使用 function
function add(a: number, b: number): number;
function add(a: number, b: number, c: number, d: number): number;
function add() {
  return [...arguments].reduce((a, b) => a + b, 0);
}

add(1, 2); // OK
add(1, 2, 3); // Error
add(1, 2, 3, 4);// OK

keyof

keyof 与 Object.keys 略有相似,只不过 keyof 取 interface 的键。

interface Point {
    x: number;
    y: number;
}

// type keys = "x" | "y"
type keys = keyof Point;

假设有一个 object 如下所示,我们需要使用 typescript 实现一个 get 函数来获取它的属性值

const data = {
  a: 3,
  hello: 'world'
}

function get(o: object, name: string) {
  return o[name]
}

我们刚开始可能会这么写,不过它有很多缺点

  1. 无法确认返回类型:这将损失 ts 最大的类型校验功能
  2. 无法对 key 做约束:可能会犯拼写错误的问题

这时可以使用 keyof 来加强 get 函数的类型功能,有兴趣的同学可以看看 _.get 的 type 标记以及实现

function get<T extends object, K extends keyof T>(o: T, name: K): T[K] {
  return o[name]
}

typeof

顾名思义,typeof 代表取某个值的 type,可以从以下示例来展示他们的用法

const a: number = 3

// 相当于: const b: number = 4
const b: typeof a = 4

在一个典型的服务端项目中,我们经常需要把一些工具塞到 context 中,如config,logger,db models, utils 等,此时就使用到 typeof

import logger from './logger'
import utils from './utils'

interface Context extends KoaContect {
  logger: typeof logger,
  utils: typeof utils
}

app.use((ctx: Context) => {
  ctx.logger.info('hello, world')

  // 会报错,因为 logger.ts 中没有暴露此方法,可以最大限度的避免拼写错误
  ctx.loger.info('hello, world')
})

in

in 操作符用于遍历目标类型的公开属性名。类似 for .. in 的机制。

使用于枚举类型

enum Letter {
  A,
  B,
  C
}

type LetterMap = {
  [key in Letter]: string
};

// interface LetterMap {
//   0: string;
//   1: string;
//   2: string;
// }

使用于联合类型:

type Property = 'name'|'age'|'phone';

type PropertyObject = {
  [key in Property]: string
};

// type PropertyObject = {
//   name: string;
//   age: string;
//   phone: string;
// }

使用与基础类型

type StringKey = {
  [key in string]: string;
};

// type StringKey = {
//   [key: string]: string;
// };

type NumberKey = {
  [key in number]: string;
};

// type NumberKey = {
//   [key: number]: string;
// };

never

never类型是 Typescript 中的底层类型。代表从不会出现的值。

举个例子:

interface Foo {
  type: 'foo';
}

interface Bar {
  type: 'bar';
}

type All = Foo|Bar;

function handleValue(val: All) {
  switch (val.type) {
    case 'foo': {
      // 这里 val 被收窄为 Foo
      break;
    }
    case 'bar': {
      // 这里 val 被收窄为 Bar
      break;
    }
    default: {
      // 这里 val 是 never
      const exhaustiveCheck: never = val;
      break;
    }
  }
}

注意在 default 里面我们把被收窄为 never 的 val 赋值给一个显示声明为 never 的变量。
如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事改了 All 的类型:

type All = Foo|Bar|Baz;

如果他忘记了再 handleValue 里面加上针对 Baz 的处理逻辑,这个时候在 default 里面 val 会被收窄为 Baz
导致无法赋值给 never,产生一个编译错误。
所以通过这个办法,你可以确保 handleValue 总是穷尽了所有 All 的可能类型。

is

使用 is 来判定值的类型

function isArray<T>(val: unknown): val is Array<T> {
  // 判断是否数组的实现
  return !!val;
}

function doSomething(data: unknown) {
  if (isArray(data)) {
    // 这里已经被识别为 Array 类型,可以使用数组方法
    data.forEach((v) => {
    });
  } else {
    // 这里会报错,无法识别 data 类型
    data.forEach((v) => {
    });
  }
}

Condition Type

类似于 js 中的 ?: 运算符,可以使用它扩展一些基本类型

type isTrue<T> = T extends true ? true : false
// 相当于 type t = false
type t = isTrue<number>

// 相当于 type t = false
type t1 = isTrue<false>

infer

infer 可以用来进行类型推测

type ParamType<T> = T extends (param: infer P) => any ? P : T;

interface User {
  name: string;
  age: number;
}

type FunC = (user: User) => void;

function log(msg: string) {
  console.log(msg);
}

type Param = ParamType<FunC>; // Param = User
type ParamLog = ParamType<typeof log>; // ParamLog = string

infer P 表示待推断的类型变量
整句表示 如果 T 能赋值给 (param: infer P) => any,则结果是 (param: infer P) => any 类型中的参数 P ,否则返回 T

再举个例子,反解 Promise

type PromiseType<T> = (...args: any[]) => Promise<T>;
type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;

async function stringPromise() {
  return 'string promise';
}

async function numberPromise() {
  return 1;
}

type ExtractStringPromise = UnPromisify<typeof stringPromise>; // string
type ExtractNumberPromise = UnPromisify<typeof numberPromise>; // number

readonly

readonly 可以标记属性只读。

interface Foo {
  readonly bar: number;
  readonly bas: number;
}

const foo: Foo = { bar: 1, bas: 2 };

foo.bas = 444; // TS2540: Cannot assign to 'bas' because it is a read-only property.

内置类型

Partial

作用是将属性变为可选项,定义:

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

示例:

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type FooPartial = Partial<Foo>;

// type FooPartial = {
//   name?: string;
//   age?: number;
//   phone?: number;
// }

Required

作用是将属性变为必选项,定义:

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

示例:

interface Foo {
  name?: string;
  age?: number;
  phone?: number;
}

type FooRequired = Required<Foo>;

// type FooPartial = {
//   name: string;
//   age: number;
//   phone: number;
// }

Readonly

作用是将属性变为只读,定义:

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

示例:

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type FooReadonly = Readonly<Foo>;

// type FooReadonly = {
//   readonly name: string;
//   readonly age: number;
//   readonly phone: number;
// }

Exclude

作用是从 T 中排除 U,定义:

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

示例:

interface Foo1 {
  name: string;
  phone: number;
}

interface Foo2 {
  name: string;
  age: number;
}

// 从 name,age 中排除 name,phone
type Ttt = Exclude<keyof Foo2, keyof Foo1>; // age

Extract

作用是从 T 中提取出 U,定义:

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

示例:

interface Foo1 {
  name: string;
  phone: number;
}

interface Foo2 {
  name: string;
  age: number;
}

// 从 name,age 中提取 name,phone
type Ttt = Extract<keyof Foo2, keyof Foo1>; // name

Record

作用是将 K 中的所有属性的值转化为 T 类型,定义:

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

示例:

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type Foo1 = Record<keyof Foo, string>;

// interface Foo1 {
//   name: string;
//   age: string;
//   phone: string;
// }

Pick

作用是挑选出一些属性,定义:

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

示例:

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type FooPick = Pick<Foo, 'name'>;

// type FooPick = {
//   name: string;
// }

Omit

作用是排除一些属性,定义:

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

示例:

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type FooOmit = Omit<Foo, 'name'>;

// type FooOmit = {
//   age: number;
//   phone: number;
// }

NonNullable

作用是排除 null 与 undefined 类型,定义:

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

示例:

type T = 'name'|null|undefined;

type Name = NonNullable<T>; 
// type Name = 'name';

Parameters

作用是提取函数的参数类型,定义:

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

示例:

function add(a: number, b: string) {
  return a + b;
}

type Params = Parameters<typeof add>;
// type Params = [number, string];

ConstructorParameters

作用是提取类构造函数的参数类型,定义:

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

示例:

class Parent {
  readonly name: string;

  constructor(name: string) {
    this.name = name;
  }
}

type ConstructorParams = ConstructorParameters<typeof Parent>;
// type ConstructorParams = [string];

ReturnType

作用是提取函数的返回值类型,定义:

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

示例:

function add(a: number, b: string) {
  return a + b;
}

type AddReturnType = ReturnType<typeof add>;
// type AddReturnType = string;

InstanceType

作用是获取构造函数的实例类型,定义:

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
class Parent {
  readonly name: string;

  constructor(name: string) {
    this.name = name;
  }
}

type ParentInstance = InstanceType<typeof Parent>;
// type ParentInstance = Parent;

自定义增强类型

PartialSome

作用是选择一部分变为可选,定义:

type PartialSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Partial<Pick<T, K>>;

示例:

// 选择一部分变为可选
type PartialSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Partial<Pick<T, K>>;

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type FooPartialSome = PartialSome<Foo, 'age'>;

// type FooPartialSome = {
//   name: string;
//   age?: number;
//   phone: number;
// }

RequiredSome

作用是选择一部分变为必选,定义:

type RequiredSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Required<Pick<T, K>>;

示例:

// 选择一部分变为必选
type RequiredSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Required<Pick<T, K>>;

interface Foo {
  name?: string;
  age?: number;
  phone?: number;
}

type FooRequiredSome = RequiredSome<Foo, 'age'>;

// type FooRequiredSome = {
//   name?: string;
//   age: number;
//   phone?: number;
// }

ReadonlySome

作用是选择一部分变为只读,定义:

type ReadonlySome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Readonly<Pick<T, K>>;

示例:

// 选择一部分变为只读
type ReadonlySome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Readonly<Pick<T, K>>;

interface Foo {
  name: string;
  age: number;
  phone: number;
}

type FooReadonlySome = ReadonlySome<Foo, 'name'>;

// type FooReadonlySome = {
//   readonly name: string;
//   age: number;
//   phone: number;
// }

Mutable

作用是移除属性只读状态,定义:

type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

示例:

type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

interface Foo {
  readonly name: string;
  readonly age: number;
  readonly phone: number;
}

type FooMutable = Mutable<Foo>;
// type FooMutable = {
//   name: string;
//   age: number;
//   phone: number;
// }

MutableSome

作用是移除一部分属性只读状态,定义:

type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

type MutableSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Mutable<Pick<T, K>>;

示例:

type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

type MutableSome<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>&Mutable<Pick<T, K>>;

interface Foo {
  readonly name: string;
  readonly age: number;
  readonly phone: number;
}

type FooMutableSome = MutableSome<Foo, 'phone'>;
// type FooMutable = {
//   readonly name: string;
//   readonly age: number;
//   phone: number;
// }

**