关于TypeScript你需要知道的二三事

1,197 阅读10分钟

禁用 ts 校验

  • 禁用整个文件的 ts 校验

    // @ts-nocheck放到文件顶部:

    //test.js
    
    // @ts-nocheck
    import axios from 'axios';
    
  • 禁用单行的 ts 校验

    // @ts-ignore放到要禁止校验的代码行上面:

    //test.js
    
    // @ts-ignore
    let a: number = 'a';
    

TS 类型

基础类型

  • boolean、number、string

    基本类型,可以认为和 JS 类型一致。

    const a: boolean = true;
    const b: number = 123;
    const c: string = 'hello';
    
  • any

    任意类型,当一个值是 any 类型时,可以访问它的任何属性,像函数一样调用它,将它分配给任何类型的值。

    any 会导致类型检测失效,应该尽量避免使用。

    let obj: any = { x: 0 };
    // None of the following lines of code will throw compiler errors.
    // Using `any` disables all further type checking, and it is assumed
    obj.foo();
    obj();
    obj.bar = 100;
    obj = 'hello';
    const n: number = obj;
    
  • unknown

    表示不可预先定义的类型,在很多场景下,它可以替代 any 的功能同时保留静态检查的能力,因为 unknown 类型不能调用任何方法,这也是和 any 最大的不同。

    const count = (input: unknown) => {
      // error: Property 'length' does not exist on type 'unknown'
      console.log('count', input.length);
    };
    
  • null、undefined

    表示空值,可以认为和 JS 类型一致。当一个变量初始化为 null 或 undefined 时,该变量的类型为 any。

    //a: any
    const a = undefined;
    //b: any
    const b = null;
    
  • void

    表示函数的返回值为空,意味着不需要关心返回值是什么,这和 undefined 有本质的区别。如下,声明了一个 forEach 的函数,第二个参数 callback 函数的返回值类型定义为 undefined,这导致了错误。

    declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;
    
    const target: number[] = [];
    //Type 'number' is not assignable to type 'undefined'.
    forEach([1, 2, 3], (el) => target.push(el));
    

    事实上我们并不需要关心这个 callback 函数的返回值到底是什么类型,不论是 number 或者 string,因此只需要设置其为 void。

    declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
    
  • array

    数组类型,可以用T[]Array<T>的语法:

    const a: number[] = [1, 2, 3];
    const b: Array<string> = ['1', '2', '3'];
    
  • tuple

    元组类型,与数组类似,但是元组类型赋值的类型、位置、个数需要和定义的类型、位置、个数一致。

    type TupleArr = [string, boolean, number];
    const v: TupleArr = ['a', true, 1];
    
  • enum

    枚举类型,枚举类型的类型名以及枚举值名都用大驼峰式的写法。

    // 初始值默认为 0,后面枚举值递增
    enum LogLevel {
      Error, //0
      Warn, //1
      Info, //2
      Debug, //3
    }
    
    enum LogLevel {
      Error = 5,
      Warn, //6
      Info, //7
      Debug, //8
    }
    
    //字符串枚举每个枚举值都需要写
    enum Status {
      Completed = 'Completed',
      Failed = 'Failed',
      Pending = 'Pending',
    }
    
  • never

    表示没法正常结束返回的类型。

    function fn(): never {
      throw new Error();
      console.log('Hello World');
    }
    
    function fn(x: string | number) {
      if (typeof x === 'string') {
        // do something
      } else if (typeof x === 'number') {
        // do something else
      } else {
        x; // has type 'never'!
      }
    }
    
    type human = 'boy' & 'girl'; // 这两个单独的字符串类型并不可能相交,故human为never类型
    

Union Types

联合类型,表示取值可以为多种类型之一,用 |(或) 分割。

let num: string | number;

type Color = 'red' | 'green' | 'yellow';

interface Fish {
  swim: boolean;
}
interface Bird {
  fly: boolean;
}
type BirdOrFish = Fish | Bird;

Intersection Types

交叉类型,将多个类型合并为一个类型,用 &(且)连接。

interface Fish {
  swim: boolean;
}
interface Bird {
  fly: boolean;
}
type BirdOrFish = Fish & Bird;
const bf: BirdOrFish = {
  swim: true,
  fly: true,
};

泛型

泛型就是泛指的类型,用占位符 T(习惯用 T)来表示某一种类型。

  • 基本使用方法

    // 普通类型定义,T默认值为number
    type Fish<T = number> = { name: string; age: T };
    // 普通类型使用
    const f1: Fish = { name: 'nimo', age: 6 };
    const f2: Fish<string> = { name: 'nimo', age: '6' };
    
    // 类定义
    class Cat<T> {
      private type: T;
      constructor(type: T) {
        this.type = type;
      }
    }
    // 类使用
    const cat: Cat<number> = new Cat<number>(20); // 或简写 const cat = new Cat(20)
    
    swipe([cat, dog]);
    // 函数定义
    const join = <T, P>(first: T, second: P) => {
      return `${first}${second}`;
    };
    // 函数使用
    join<number, string>(1, '2');
    
  • 类型变量

    如下,参数 arg 要访问 length 属性,如果 T 是任意类型,可能导致错,将它视为元素类型为 T 的数组就能解决这一问题。 这可以让我们把泛型变量 T 当做类型的一部分使用,而不是整个类型,增加了灵活性。

    function loggingIdentity<T>(arg: T[]): T[] {
      console.log(arg.length); // Array has a .length, so no more error
      return arg;
    }
    
  • 泛型约束

    如下,参数 arg 要访问 length 属性,如果不加约束就是任意类型,有可能没有 length 属性。

    interface Lengthwise {
      length: number;
    }
    
    function loggingIdentity<T extends Lengthwise>(arg: T): T {
      console.log(arg.length);
      return arg;
    }
    
  • 泛型推导

    如下,test('hello')并没有指定 T 的类型,但是能够从字符串'hello'推断出是 string 类型。

    function test<T>(arg: T): T {
      return arg;
    }
    const num = test<number>(123);
    const output = test('hello'); // type of output will be 'string'
    
  • 泛型推断 infer

    infer 关键词常在条件类型中和 extends 关键词一同出现,表示将要推断的类型,如下: { name: infer U }表示包含属性 name 的对象类型,如果泛型 T 是继承自该类型则返回 U 的类型,U 的类型根据 name 类型自行推断。 Aniaml<{ name: boolean }>符合T extends { name: infer U }因此返回 name 的类型,即布尔类型。

    type Aniaml<T> = T extends { name: infer U } ? U : string;
    type A = Aniaml<number>; // string,number不是一个包含t的对象类型
    type  = Aniaml<{ name: boolean }>; // boolean,因为泛型参数匹配上了,使用了infer对应的type
    type C = Aniaml<{ x: number; name: () => void }>; // () => void,泛型定义是参数的子集,同样适配
    

    ReturnType 的实现:

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

//abstract抽象类不能被实例化
abstract class Father {
  private weight; //private属性,类的外部不可用,继承也不行
  protected money = 10000000; //protected属性,类的外部不可用,继承可以
  public readonly familyName = 'meng'; //public公开属性,readonly只读属性不可修改

  //static 静态方法,不需要 new 就可以直接调用
  static breath() {
    console.log('breath');
  }

  abstract eat: () => void; //抽象方法,所有子类都必须实现抽象方法

  //在构造函数添加访问修饰符可省略顶部(weight)的定义,如private name
  constructor(private name: string, weight: number) {
    this.name = name;
    this.weight = weight;
  }
}

class Son extends Father {
  getMoney() {
    console.log('Money', this.money);
  }

  eat = () => {
    console.log('eat');
  };
}
const Father = new Father('snow', 120); //error,抽象类不能实例化
Father.breath(); //static
const snow = new Son('snow', 120);
console.log(snow.money); //error,protected
snow.familyName = 'zhang'; //error,readonly

Utility Types

  • Readonly<T>

    将 T 类型的所有属性设置为只读。

    interface Bird {
      name?: string;
      fly: boolean;
    }
    
    type ReadonlyBird = Readonly<Bird>;
    
    const b: ReadonlyBird = { name: 'a', fly: true };
    b.fly = false; //error,Cannot assign to 'name' because it is a read-only property.
    
  • Partial<T>

    将类型 T 中所有属性转换为可选属性,返回的类型可以是 T 的任意子集,也就是 Partial 所表达的意思。

    interface Bird {
      name?: string;
      weight: number;
      fly: boolean;
    }
    
    type Dog = Partial<Bird>;
    // =
    type Dog = {
      name?: string | undefined;
      weight?: number | undefined;
      fly?: boolean | undefined;
    };
    

    Partial<T>所表达的意思:

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

    和 Partial 相反,将 T 类型的所有属性设置为必选。

    interface Bird {
      name?: string;
      weight: number;
      fly: boolean;
    }
    
    type Duck = Required<Bird>;
    // =
    type Duck = {
      name: string;
      weight: number;
      fly: boolean;
    };
    
  • Record<K,T>

    声明键为 K 类型,值为 T 类型的对象。

    interface Order {
      type: string;
      price: number;
    }
    
    type OrderMap = Record<string, Order>;
    //等同于
    interface OrderMap {
      [key: string]: Order;
    }
    
  • Pick<T,K>

    在已声明的对象类型中挑选出部分属性组成新的声明对象。

    interface Order {
      type: string;
      price: number;
      deadline: string;
    }
    
    type NewOrder = Pick<Order, 'type' | 'price'>;
    //等同于
    type NewOrder = {
      type: string;
      price: number;
    };
    
  • Omit<T,K>

    与 Pick 相反,在已声明的对象类型去除部分属性,剩余属性组成新的声明对象。

    interface Order {
      type: string;
      price: number;
      deadline: string;
    }
    
    type NewOrder = Omit<Order, 'deadline'>;
    //等同于
    type NewOrder = {
      type: string;
      price: number;
    };
    
  • Exclude<T,U>

    同 Omit 类似,但 Exclude 是从联合类型中排除类型。

    //type Color = 'red'
    type Color = Exclude<'red' | 'green', 'green'>;
    
    interface Fish {
      swim: boolean;
    }
    
    interface Bird {
      fly: boolean;
    }
    
    type BirdOrFish = Fish | Bird;
    
    //等同于type NewBird=Bird
    type NewBird = Exclude<BirdOrFish, Fish>;
    
  • Extract<T,U>

    与 Exclude,同 Pick 类似,但 Extract 是从联合类型中挑选类型。

    Extract 定义:

    //如果 T 中的类型在 U 存在,则返回,否则抛弃(never)。
    type Extract<T, U> = T extends U ? T : never;
    

    示例:

    //type Color==='green'
    type Color = Extract<'red' | 'green', 'green'>;
    
    interface Bird {
      name: string;
      age: number;
      fly: boolean;
    }
    
    interface Fish {
      name: string;
      age: number;
      swim: boolean;
    }
    //等同于type commonKeys = "name" | "age"
    type commonKeys = Extract<keyof Bird, keyof Fish>;
    
  • ReturnType<T>

    返回类型为 T 的函数的返回类型,这个方法对于没有明确提供类型的方法来说太方便了。

    const getNumber = () => 7;
    //type T = number
    type T = ReturnType<typeof getNumber>;
    
  • Parameters<T>

    返回类型为 T 的函数的参数类型所组成的数组。

    type T1 = Parameters<() => number>; // []
    
    type T2 = Parameters<(s: string, n: number) => void>; // [string, number]
    
  • NonNullable<T>

    从联合类型 T 中去除 null 或者 undefined。

    //等同于type T = 'string' | 'number';
    type T = NonNullable<'string' | 'number' | undefined | null>;
    

运算符

  • !

    非空断言运算符。

    const App = () => {
      const divRef = useRef<HTMLDivElement>();
      useEffect(() => {
        divRef.current!.scrollIntoView(); // Mount后才会触发useEffect,current一定有值
      }, []);
      return <div ref={divRef}>App</div>;
    };
    
  • ?.

    可选链运算符。

    a?.v    a?.[index]    func?.(args)
    
  • ??

    空值合并运算符,与||的功能是相似的,区别在于 ??在左侧表达式结果为 null 或者 undefined 时,才会返回右侧表达式,对 false、''、NaN、0 也会生效。

    const f1 = (v: unknown) => {
      const a = v ?? 'hello';
      console.log(a);
    };
    f1(null); //hello
    f1(undefined); //hello
    f1(1); //1
    f1(0); //0
    f1(false); //false
    f1(''); //''
    f1(NaN); //NaN
    
  • _

    数字分隔符,编译出来的代码是没有下划线的。

    //a:number
    const a = 1314_1234_125;
    

操作符

  • as

    断言操作符,用来手动指定一个值的类型,语法为: 值 as 类型<类型>值

    interface User {
      name: string;
      age: number;
    }
    
    const parse = (str: string) => {
      const user = JSON.parse(str);
      console.log((user as User).name);
    };
    

    类型断言只是欺骗 TS 编译器,无法避免运行时的错误,因此要尽量少用。

  • keyof

    keyof 可以获取一个类型的 key:

    const colorMap = {
      red: 'red',
      green: 'green',
    };
    
    type keys = keyof colorMap; //error
    
    /**************分割线****************/
    interface Point {
      x: number;
      y: number;
    }
    
    // type keys = "x" | "y"
    type keys = keyof Point;
    
  • typeof

    typeof 可以用来获取一个变量或对象的类型:

    enum LogLevel {
      Error,
      Warn,
      Info,
      Debug,
    }
    type LogType = typeof LogLevel;
    

    keyof typeof 通常可以结合起来使用:

    const colorMap = {
      red: '#f61a22',
      green: '#309449',
    };
    // type keys = "red" | "green"
    type keys = keyof typeof colorMap;
    
  • 遍历属性 in

    在做类型保护时,类似于数组和字符串的 includes 方法:

    const test = (data: Position | User) => {
      if ('x' in data) {
        console.log(data.x); //100
      } else {
        console.log('...');
      }
    };
    test({ x: 100 });
    

    也有遍历的作用,拿到 ts 类型定义的 Key

    type Fruit = 'apple' | 'orange' | 'banana';
    
    type FruitCount = {
      [key in Fruit]: number;
    };
    
  • instanceof

    判断一个实例是否属于某个类。

    class Bird {
      fly = true;
    }
    
    class Fish {
      swim = true;
    }
    
    const whoAreYou = (animal: Bird | Fish) => {
      if (animal instanceof Bird) {
        console.log('I am a bird');
      } else {
        console.log('I am a fish');
      }
    };
    
  • InstanceType

    返回构造函数类型 T 的实例类型。

    class C {
      x = 0;
      y = 0;
    }
    //等同于 T = C
    type T = InstanceType<typeof C>;
    

区别

interface vs type

  • 函数语法的不同

    type 的函数语法更接近于函数本身的声明。

    // via type
    type Sum = (x: number, y: number) => number;
    
    // via interface
    interface Sum {
      (x: number, y: number): number;
    }
    
  • Extend 方式不同

    interface: 根据关键字 extends 扩展。

    interface Animal {
      name: string;
    }
    
    interface Dog extends Animal {
      honey: boolean;
    }
    

    type: 通过&号扩展。

    type Animal = {
      name: string;
    };
    
    type Dog = Animal & {
      honey: Boolean;
    };
    
  • 声明合并

    interface 可以向现有接口添加新字段,type 不行。

    interface:

    interface Point {
      x: number;
    }
    
    interface Point {
      y: number;
    }
    const pt: Point = { x: 8, y: 9 };
    

    type:

    // Error: Duplicate identifier 'Point'.
    type Point = {
      x: number;
    };
    
    type Point = {
      y: number;
    };
    
  • 重命名基本类型

    interface 不能重命名基本类型,type 可以。 interface:

    //error
    interface NewString = string;
    

    type:

    type NewString = string;
    
  • Union types

    type 可用于定义联合类型。

    type Fruit = 'apple' | 'pear' | 'orange';
    type Vegetable = 'broccoli' | 'carrot' | 'lettuce';
    
    // 'apple' | 'pear' | 'orange' | 'broccoli' | 'carrot' | 'lettuce';
    type HealthyFoods = Fruit | Vegetable;
    
  • Mapped object types

    interface:

    type Fruit = 'apple' | 'orange' | 'banana';
    
    // ERROR:
    interface FruitCount {
      [key in Fruit]: number;
    }
    

    type:

    type Fruit = 'apple' | 'orange' | 'banana';
    
    type FruitCount = {
      [key in Fruit]: number;
    };
    
    const fruits: FruitCount = {
      apple: 2,
      orange: 3,
      banana: 4,
    };
    
  • 函数重载

    interface:

    interface NumLogger {
      log: (val: number) => void;
    }
    //error
    interface StrAndNumLogger extends NumLogger {
      log: (val: string) => void;
    }
    

    type:

    interface NumLogger {
      log: (val: number) => void;
    }
    type StrAndNumLogger = NumLogger & {
      log: (val: string) => void;
    };
    
    const logger: StrAndNumLogger = {
      log: (val: string | number) => console.log(val),
    };
    
    logger.log(1);
    logger.log('hi');
    
  • Tuple types

    type 可以定义元组类型,interface 不行。

    type TupleArr = [string, boolean, number];
    const v: TupleArr = ['a', true, 1];
    

    因此使用 type 的场景如下:

    • 需要重命名原始类型
    • 需要定义函数类型
    • 需要定义联合类型
    • 需要利用 mapped types
    • 需要函数重载
    • 需要使用 tuple types

declare interface vs interface

declare 通常用来声明已经存在的对外导出的类型定义(让 JS 文件能够有类型),而 interface 通常在 TS 项目内部使用。 可以认为没什么区别

tsconfig

如果一个目录下存在一个 tsconfig.json 文件,这意味者这个目录是 TypeScript 项目的根目录。 TypeScript 可以用tsc(TypeScript 编译器)来编译,用 tsconfig.json 文件用来配置编译的相关选项。

安装完 typescript 后就能直接使用 tsc 了:

$ yarn add typescript -g
$ tsc hello.ts

tsc 如果不指定配置文件则从当前目录开始逐级向上查找 tsconfig.json 文件。当然 tsc 也可以指定配置文件:

$ tsc -p tsconfig.build.json

tsconfig.json 配置选项:

//tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "types": ["node"]
  },
  "files": ["common.d.ts"],
  "include": ["src"]
}

files

指定一个包含相对或绝对文件路径的列表。

//tsconfig.json
{
  "files": ["common.d.ts"]
}

include

配置哪些文件需要编译,指定一个文件glob匹配模式列表。

//tsconfig.json
{
  "include": ["src", "common.d.ts"]
}
.
├── scripts                ⨯
│   ├── lint.ts            ⨯
│   └── utils.ts           ⨯
├── src                    ✓
│      └── index.ts        ✓
├── common.d.ts            ✓
├── package.json
├── tsconfig.json
└── yarn.lock

支持的 glob 通配符有:

  • *

    匹配 0 或多个字符(不包括目录分隔符)

  • ?

    匹配一个任意字符(不包括目录分隔符)

  • **/

    递归匹配任意子目录

//tsconfig.json
{
  "include": ["src/**/*", "tests/**/*"]
}
.
├── scripts                ⨯
│   ├── lint.ts            ⨯
│   ├── update_deps.ts     ⨯
│   └── utils.ts           ⨯
├── src                    ✓
│   ├── client             ✓
│   │    ├── index.ts      ✓
│   │    └── utils.ts      ✓
│   ├── server             ✓
│   │    └── index.ts      ✓
├── tests                  ✓
│   ├── app.test.ts        ✓
│   ├── utils.ts           ✓
│   └── tests.d.ts         ✓
├── package.json
├── tsconfig.json
└── yarn.lock

如果 files 和 include 都没有被指定,编译器默认包含当前目录和子目录下所有的 TypeScript 文件。如果指定了 files 或 include,编译器会将它们结合一并包含进来,它们所引用的文件(排除在 exclude 里指定的文件)也会包含进来(即使不在 files 或 include 内)。

需要注意编译器不会去引入那些可能做为输出的文件。比如,我们包含了 index.ts,那么 index.d.ts 和 index.js 会被排除在外,换个名字就可以了。

exclude

配置哪些文件不需要编译,使用 include 引入的文件可以使用 exclude 属性过滤。 而,通过 files 属性明确指定的文件却总是会被包含在内(忽略 exclude)。 如果不指定, exclude 默认情况下会排除 node_modules,bower_components,jspm_packages 和outDir配置属性指定的目录。

//tsconfig.json
{
  "exclude": ["scripts/lint.ts "],
  "include": ["src/**/*", "scripts/**/*"]
}
.
├── scripts                ✓
│   ├── lint.ts            ⨯
│   └── utils.ts           ✓
├── src                    ✓
│      └── index.ts        ✓
├── package.json
├── tsconfig.json
└── yarn.lock

如果显示指定排除 node_modules,node_modules/@types(默认包含,详见 typeRoots) 的内容仍然会被检测。

extends

通过 extends 属性可以继承配置文件,新的配置项 files、include 以及 exclud 都会直接覆盖继承的配置文件对应项,compilerOptions 内部同名属性会被覆盖,不同名属性则会合并。

//tsconfig.json
{
  "compilerOptions": {
    "allowJs": true,
    "esModuleInterop": true,
  },
  "files": ["common.d.ts"],
  "exclude": ["src/index.ts"],
  "include": ["src"]
}

//tsconfig.build.json
{
  "extends": "./tsconfig",
  "compilerOptions": {
    "outDir": "lib"
  }
}
//等同于
{
  "compilerOptions": {
    "allowJs": true,
    "esModuleInterop": true,
    "outDir": "lib"
  },
  "files": ["common.d.ts"],
  "exclude": ["src/index.ts"],
  "include": ["src"]
}

compilerOptions

  • target

    编译的 JS 目标版本,默认是 ES3,可以是 ES3、ES5、ES6/ES2015、ES2016-ES2022、ESNext。现代浏览器支持全部 ES6 的功能,所以 ES6 是一个不错的选择。

    特殊的 ESNext 代表你的 TypeScript 所支持的最高版本。这个配置应当被谨慎使用,因为它在不同的 TypeScript 版本之间的含义不同,并且会导致升级更难预测。

    //tsconfig.json
    {
      "compilerOptions": {
        "target": "ES6"
      }
    }
    
  • noEmit

    是否不生成输出文件,默认为 false,即要生成文件,如果只想编译,不生成文件,则该值应该设置为 true:

    //tsconfig.json
    {
      "compilerOptions": {
        "noEmit": true
      }
    }
    
  • noImplicitAny

    是否不允许隐式的 any,默认为 false, 如果为 true 就必须设置类型,下例的参数 a 就会报错:

    const test = (a) => {
      console.log('test', a.v);
    };
    
    //tsconfig.json
    {
      "compilerOptions": {
        "noImplicitAny": true
      }
    }
    
  • baseUrl

    解析非相对模块名的基准目录,如下: 设置 baseUrl 为.即当前目录为基准目录:

    .
    ├── src
    │   ├── components
    │   │   └── button.tsx
    │   ├── features
    │   │   └── user.tsxcom'
    │   └── index.ts
    └── yarn.lock
    //tsconfig.json
    {
      "compilerOptions": {
        "baseUrl": "."
      }
    }
    

    在 user.tsx 引用就可以写成这样:

    //user.tsx
    //src为'.'指定的基准目录下的目录
    import Button from 'src/components/button';
    //而不是
    import Button from '../components/button';
    
  • outDir

    tsc 编译输出的目录,如果不指定则编译到 ts 文件所在目录。

    //tsconfig.json
    {
      "compilerOptions": {
        "outDir": "lib"
      }
    }
    
  • allowJs

    是否编译 js 文件,默认为 false。

    //tsconfig.json
    {
      "compilerOptions": {
        "allowJs": true
      }
    }
    

    如果为 true,js 文件会被编译到 outDir 目录,不然会报错。

  • esModuleInterop

    esModuleInterop 为 false 时不会处理 esm 模块默认导入的问题,如下:

    //esm
    import React from 'react';
    console.log(React);
    //编译后
    ('use strict');
    exports.__esModule = true;
    var react_1 = require('react');
    //react为commonjs代码,没有导出defalut
    console.log(react_1['default']); //undefined
    

    esModuleInterop 为 true 时,如下: 可以看到通过函数__importDefault可以正确处理。

    // esm
    import React from 'react';
    console.log(React);
    // 编译后
    
    var __importDefault = function (mod) {
      return mod && mod.__esModule ? mod : { default: mod };
    };
    
    var react = __importDefault(require('react'));
    console.log(react['default']);
    
  • jsx

    .tsx文件里支持JSX语法,可以控制 JSX 在 JavaScript 文件中的输出方式。 这只影响 .tsx 文件的 JS 文件输出。有以下几种选项:

    • react

      将 JSX 改为等价的对 React.createElement 的调用并生成 .js 文件。

    • react-jsx

      改为 _jsx 调用并生成 .js 文件。

      import { jsx as _jsx } from 'react/jsx-runtime';
      
      import React from 'react';
      export const helloWorld = () => _jsx('h1', { children: 'Hello world' });
      
    • react-jsxdev

      改为 _jsx 调用并生成 .js 文件。

    • preserve

      不对 JSX 进行改变并生成 .jsx 文件。

    • react-native

      不对 JSX 进行改变并生成 .js 文件。

  • lib

    编译时需要引入的库,默认注入的库为: target ES5:DOM,ES5,ScriptHost target ES6:DOM,ES6,DOM.Iterable,ScriptHost

    {
      "compilerOptions": {
        "lib": ["dom", "dom.iterable", "esnext"]
      }
    }
    
  • typeRoots 和 types

    默认情况下所有可见的@types 包会在编译过程中被包含进来。 node_modules/@types 文件夹下以及它们子文件夹下的所有包都是可见的。

    如果指定了 typeRoots,只有 typeRoots 下面的包才会被包含进来。需要注意的是如果指定了 typeRoots,且 include 不为空,typeRoots 指定的目录不在 include 里则需要将相应的目录放到 include 里:

    .
    ├── src
    │      └── index.ts
    ├── typings
    │      └── index.d.ts
    ├── package.json
    ├── tsconfig.json
    └── yarn.lock
    {
      "compilerOptions": {
        "typeRoots": ["typings", "node_modules/@types"],
        "include": ["src", "typings"]
      }
    }
    

    如果指定了 types,只有被列出来的包才会被包含进来:

    {
      "compilerOptions": {
        "types": ["node", "lodash", "semver"]
      }
    }
    

    指定"types": []则不会引用@types 包。不过,使用 import "foo"语句,TypeScript 仍然会查找 node_modules 和node_modules/@types文件夹来获取 foo 包。

  • skipLibCheck

    是否跳过声明文件(d.ts)的检查,默认值为 false。 如果依赖包和项目本身的 TS 版本不一致,在编译时可能出现错误,可以在 tsconfig.json 关闭依赖包的检查。

    {
      "compilerOptions": {
        "skipLibCheck": true
      }
    }
    
  • declaration

    是否生成声明文件(d.ts),默认值为 false。

    {
      "compilerOptions": {
        "declaration": true
      }
    }
    

    当 declaration 为 true 时,用编译器执行下面的 TypeScript 代码:

    export let helloWorld = 'hi';
    

    将会生成如下这样的 index.js 文件:

    export let helloWorld = 'hi';
    

    以及一个相应的 helloWorld.d.ts:

    export declare let helloWorld: string;
    
  • emitDeclarationOnly

    是否只生成声明文件(d.ts),默认值为 false。需要配合 declaration 使用:

    {
      "compilerOptions": {
        "declaration": true,
        "emitDeclarationOnly": true
      }
    }
    
  • isolatedModules

    是否是独立的模块,默认值为 false,当为 true 时则所有的实现文件必须是模块,也就是它有某种形式的 import/export

    {
      "compilerOptions": {
        "isolatedModules": true
      }
    }
    

    以下文件没有 import 或 export,isolatedModules 为 true 时编译就会报错。

    //test.ts
    const a = 3;
    
  • resolveJsonModule

    是否引入 JSON 文件,默认为 false,如果想引入 JSON 文件,该值应设为 true:

    {
      "compilerOptions": {
        "resolveJsonModule": true
      }
    }
    

    引入 JSON:

    //test.tsx
    import React from 'react';
    import packageInfo from '../package.json';
    
    export const Test = () => <div>{packageInfo.version}</div>;
    
  • moduleResolution

    模块的查找方式,详见

    {
      "compilerOptions": {
        "moduleResolution": "node"
      }
    }
    

    主要有以下三种方式:

    • node

      CommonJS 的查找方式,基本都采用这种方式。

    • node16/nodenext

      TypeScript 4.7 后支持的方式。

    • classic

      TypeScript 1.6 以前的方式。

以上编译选项都是较为常用的,其他选项详见: tsconfigcompiler-options

声明文件

类型声明(Type Declaration)文件是一个以.d.ts作为文件后缀名的 TypeScript 文件。文件中只包含与类型相关的代码,不包含逻辑代码,它的作用是为开发者提供类型信息。

declare

通过 declare 可以声明类型或者变量或者模块等,详见

declare let parseStr: (str: string) => void;
declare function parseStr(selector: string): void;
declare class Animal {
  constructor(private name: string);
  eat(): string;
}
declare enum Directions {
  Up,
  Down,
  Left,
  Right,
}
declare namespace jQuery {
  function ajax(url: string, settings?: object): void;
}

引用声明文件

  • 引用某个模块的声明文件

    假如有一个 js 模块(parser.js),怎么知道它的类型信息呢?在相应的文件夹下提供一个同名文件 parser.d.ts(或 index.d.ts) 的声明文件就可以实现:

    .
    ├── src
    │   └── index.ts
    ├── module
    │   ├── parser.js
    │   ├── index.js
    │   └── parser.d.ts
    ├── package.json
    ├── tsconfig.json
    └── yarn.lock
    

    parser.js 文件:

    export const parseStr = (str) => {
      console.log('str', str);
    };
    

    parser.d.ts 文件:

    export declare const parseStr: (str: string) => void;
    

    src/index.ts 文件中引用 parser 就能够获取 parser.d.ts 文件的声明:

    import { parseStr } from './parser';
    
    parseStr('jkjlk');
    // error: Argument of type 'number' is not assignable to parameter of type 'string'.
    parseStr(123);
    
  • 引用 node_modules 包的声明文件

    如下,有一个 js 包,怎么知道它的类型信息呢?在 package.json main 字段指定的文件夹(lib)下提供一个 index.d.ts 文件或者通过 types 字段直接指定声明文件就可以实现:

    包结构:

    .
    ├── lib
    │   ├── parser
    │   ├── loader
    │   └── index.js
    └── package.json
    

    package.json

    {
      "name": "parser",
      "main": "lib",
      "types": "lib/main.d.ts"
    }
    

    引用 parser 就能够获取 main.d.ts 文件的类型信息。

三斜线指令

三斜线指令是包含单个 XML 标签的单行注释。 注释的内容会做为编译器指令使用。

三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。

/// <reference path="..." />指令是三斜线指令中最常见的一种。reference 指定的 path 属性的值是另一个 ts 文件的路径,用来告诉编译器在编译过程中要引入的额外的文件(全局声明)。

文件结构:

.
├── src
│   └── index.ts
├── typings
│   ├── order.d.ts
│   ├── index.d.ts
│   └── user.d.ts
├── package.json
├── tsconfig.json
└── yarn.lock

配置:

{
  "compilerOptions": {
    "typeRoots": ["typings", "node_modules/@types"]
  },
  "include": ["src", "typings"]
}

typings/index.d.ts

// typings/index.d.ts
/// <reference path="order.d.ts" />
/// <reference path="user.d.ts" />

parse.d.ts

interface Order {
  date: string;
}

user.d.ts

interface User {
  name: string;
}

通过三斜线指令就能愉快的使用相关的类型了:

//src/index.ts
const user: User = { name: 'oo' };
const order: Order = { date: '2012' };

第三方声明文件

通常我们通过安装@types/xxx(DefinitelyTyped 社区提供了很多声明文件包)就能够得到声明文件,事实上这些包会被下载到 node_modules/@types 文件夹下,这也是为什么 typeRoots 的默认值为 node_modules/@types。如果你想在默认值的基础上额外添加一个声明文件夹目录,需要给 typeRoots 指定两个值。

{
  "compilerOptions": {
    "typeRoots": ["node_modules/@types", "typings"]
  }
}

其他

  • tsc 指定 tsconfig 文件

    $ tsc -p tsconfig.build.json
    
  • tsnode 指定 tsconfig 文件运行

    $ ts-node --project tsconfig.build.json hello.ts
    
  • 自动编译

    安装 vscode 插件 TypeScript Auto Compiler 可以根据 tsconfig.json 在代码变更时自动编译代码。