# TypeScript 使用技巧

# js 补充类型定义

# 声明文件

  • 作用:是给 js 代码补充类型标注。这样在 ts 编译环境下就不会提示 js 文件“缺少类型”。
  • 使用方式:
    • 单独发布声明文件,@types/xxx(scope = @types,xxx 是我们的包名)
    • 发布的包中自带声明文件,package.json 中在 types 中定义我们具体的声明文件入口。如果入口在根目录下且名字叫 index.d.ts 则不用指定 types。

# declare

在声明文件中,通过 declare 我们可以标注 js 全局变量的类型。

declare var n: number;
declare let s: string;
declare const o: object;
declare function f(s: string): number;
declare enum dir {
  top,
  right,
  bottom,
  left,
}

1
2
3
4
5
6
7
8
9
10
11

# namespace

  • 代表声明的是一个对象

    declare namespace A {
      var n: number;
      var s: string;
      var f: (s: string) => number;
    }
    
    A.n = 1;
    A.s = "1";
    A.f(1);
    
    interface A {
      n: number;
      s: string;
      f: (s: string) => number;
    }
    
    declare const a: A;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  • 我们一般不会用命名空间来声明一个对象,一般都是为了声明一个声明对象,防止因为定义太多重复

    declare namespace Food {
      type A = Window;
      interface Fruits {
        taste: string;
        hardness: number;
      }
    
      interface Meat {
        taste: string;
        heat: number;
      }
    }
    
    /// <reference path='a.d.ts'/>
    let meat: Food.Meat;
    let fruits: Food.Fruits;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    export namespace Food {
      type A = Window;
      interface Fruits {
        taste: string;
        hardness: number;
      }
    
      interface Meat {
        taste: string;
        heat: number;
      }
    }
    
    import { Food } from "./a.d";
    let meat: Food.Meat;
    let fruits: Food.Fruits;
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 或者用于给已经定义的类型补充定义(想象一下 es6 的 class),以 antd table 为例子

// 假代码
function Column() {}
function ColumnGroup() {}
function Summary() {}

function Table() {
  this.defaultProps = { rowKey: "key" };
  this.SELECTION_ALL = "SELECT_ALL";
  this.SELECTION_INVERT = "SELECT_INVERT";
  this.SELECTION_NONE = "SELECT_NONE";
}
Table.prototype.Column = Column;
Table.prototype.ColumnGroup = ColumnGroup;
Table.prototype.Summary = Summary;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
declare function Table<RecordType extends object = any>(
  props: TableProps<RecordType>
): JSX.Element;
declare namespace Table {
  var defaultProps: {
    rowKey: string;
  };
  var SELECTION_ALL: "SELECT_ALL";
  var SELECTION_INVERT: "SELECT_INVERT";
  var SELECTION_NONE: "SELECT_NONE";
  var Column: typeof import("./Column").default;
  var ColumnGroup: typeof import("./ColumnGroup").default;
  var Summary: typeof import("rc-table/lib/Footer/Summary").default;
}
export default Table;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  • 我们的框架中给 React 补充类型定义

    declare namespace React {
      interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
        styleName?: string;
        ["data-opid"]?: number;
        ["data-content"]?: string;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 在 ts 中使用,上述都是给 js 补充声明文件。不过这种很少用。

    namespace aaa {
      const b = 1;
    
      export function a() {
        console.log(b);
      }
    }
    export default aaa;
    
    import aaa from "./a";
    aaa.a();
    aaa.b; // 报错
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # module

主要是对应 import A from 'a',当我们使用的第三方库不存在声明文件的时候,我们需要自己补充一个申明文件。

declare module "*.css";
declare module "*.less";
declare module "*.scss";
declare module "*.bmp";
declare module "*.gif";
declare module "*.jpg";
declare module "*.jpeg";
declare module "*.png";
declare module "*.webp";

declare module "moment" {
  import { Dayjs } from "dayjs";

  namespace moment {
    type Moment = Dayjs;
  }
  // 等价于 export default moment; 不过使用这种方式必须开启 "esModuleInterop": true
  export = moment;

  // 如果包是umd 模式,没么这个包可以通过 import 导入
  // 也可以通过 script 导入
  // 这种写法就是为了 script 导入时候,可以全局使用
  export as namespace moment;
}

// 第一种
import { Moment } from "moment";
let a: Moment;

// 第二种
let a: moment.Moment;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 内置函数

# 从 T 中移除 K

Exclude<T, K>

type A = 1 | "2" | 2 | 3;
type B = 1 | 3;
type C = Exclude<A, B>; // 2 | "2"
1
2
3

# 交集

Extract<T, K>

与上边相反,求的是交集

type A = 1 | "2" | 2 | 3;
type B = 1 | 3;
type C = Extract<A, B>; // 1 | 3
1
2
3

# 从选出 T 中选择 K

Pick<T, K>

interface Base {
  a: number;
  b: string;
  d: number;
}

interface C extends Pick<Base, "a" | "b"> {
  c: number;
}

let c: C = { a: 1, b: "1", c: 1 };

interface C extends Pick<Base, "a"> {
  c: number;
}

let c: C = { a: 1, b: "1", c: 1 }; // 报错不存在 b

// 报错 Base 中没有 m
interface C extends Pick<Base, "a" | "m"> {
  c: number;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 从 T 中移除 K

Omit<T, K>

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

interface C extends Omit<Base, "a"> {
  c: number;
}
interface C2 extends Omit<Base, "a" | "b"> {
  c: number;
}
1
2
3
4
5
6
7
8

# 从 T 中类型变为只读

Readonly<T>

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

type B = Readonly<A>;

let b: B = {
  a: 1,
  b: "2",
};

b.a = 1; // 报错
1
2
3
4
5
6
7
8
9
10
11
12
13

# 将 T 类型都变为可选

Partial<T>

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

type B = Partial<A>;

let b1: B = { a: 1 };
let b2: B = { b: "1" };
let b3: B = { a: 1, b: "1" };
1
2
3
4
5
6
7
8
9
10

# 将 T 类型都变为必选

Required<T>

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

type B = Required<A>;

let b1: B = { a: 1 }; // 报错
let b2: B = { b: "1" }; // 报错
let b3: B = { a: 1, b: "1" };
1
2
3
4
5
6
7
8
9
10

# Record

Record<K, T> 两者等价

interface Base {
  [key: string]: any;
}

type Base = Record<string, any>;
1
2
3
4
5

# 使用频率最高的

  • Record
  • Partial
  • Omit

# 延伸

# 范型

interface A<T> {
  value: T;
}

// 报错,value 类型是 number
const a: A<number> = { value: "1" };
1
2
3
4
5
6

# 范型约束

interface A<T extends object> {
  value: T;
}

// 报错 number 不满足 object 的约束
type B = A<number>;
1
2
3
4
5
6
interface A<T extends { children?: T[] }> {
  value: T;
}

interface B {
  a: number;
  b: number;
}
// 报错 B 不满足 { children?: T[] } 的约束
type X = A<B>;

interface C {
  a: number;
  b: number;
  children?: C[];
}
type Y = A<C>;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 范型默认值

interface A<T> {
  value: T;
}

// 报错必须传递一个类型参数
const a: A = { value: 1 };

interface B<T = any> {
  value: T;
}

const b: B = { value: 1 };
const c: B = { value: "1" };
// 错误 value 不是 number 类型
const d: B<number> = { value: "1" };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# typeof/keyof

interface A {
  a: number;
  b: string;
}
const a: A = { a: 1, b: "1" };

(Object.keys(a) as (keyof A)[]).forEach((key) => {
  console.log(a[key]);
});

const b = { a: 1, b: "1" };
(Object.keys(b) as (keyof typeof b)[]).forEach((key) => {
  console.log(b[key]);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14