TypeScript (简称 ts)是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发。 超集:也就是说 typescript有javascript没有的功能用来增强javascript 。 ts=type + javascript 。
目标:主要学习ts里面的原始类型、字面量类型、数组类型、函数类型、类类型、接口类型、类型别名、联合与交叉类型、枚举类型、泛型等类型元素,以及类型推断、类型断言、类型缩小、类型放大等特性。
TS官网(opens new window)
一张图来说明TypeScript和JavaScript的关系:
ts完全支持js,可以直接转换:
JavaScript:边解释边执行,错误只有在运行的时候才能发现
TypeScript:先编译再执行,在写的时候就会发现错误了(ts不能直接执行,需要先编译成 js )
ts优点:有利于规范我们的代码、代码编译阶段能更好的发现错误、强类型语言
安装node (v16.12.0)
nvm install 16.17.1
nvm use 16.17.1
全局安装:
npm i -g [email protected]
独立安装:
npm i typescript 或者 yarn add typescript -D
检查安装版本:
tsc -v 或者 tsc --version
执行:
tsc xxx.ts
tsc xxx.ts --watch
终端->运行任务->显示所有任务->tsc:监视
tsc -p tsconfig.json --watch
#直接进行监听,当ts发生变化,就会自动转换
如果不加export {},会报「全局范围的扩大仅可直接嵌套在外部模块中或环境模块声明中」错误。增加export{}其实也就是为了让这个声明文件变成模块声明文件,而不是一个全局声明文件。
每次都使用tsc命令编译单个文件会比较麻烦,所以我们可以做一个统一配置
执行命令初始化一个tsconfig.json文件
tsc -init
"outDir": "./js", //输出文件夹
let str:string = "zhangsan";
str=100; //如果有报错,说明ts环境配好了
如果还有个js文件中也写了同样代码,如果也会有对应报错,可以打开vscode设置,搜索validate,找到typescript配置取消勾选即可。
就是给变量添加类型约束, 可以显示标记出代码中的意外行为,从而降低了发生错误的可能性
语法:
let 变量名: 类型 = 初始值 let age: number = 18
string,number,boolean,symbol,null,undefined
语法简单基本和 js 差别不大 symbol 类型 , 不是 ts 中新增的,是es6里面新增的,用来表示 一个 唯一值
export {}; // 第一行增加这个是为了使文件里的变量不污染全局
let num: number = 1; // number
let str: string = "2"; // string
let bool: boolean = true; // boolean
let sy: symbol = Symbol(); // symbol
let undef: undefined = undefined; // undefined
let nul:null = null; // null
let vd: void = undefined; //没有返回值 可以把undefined类型赋值给void类型,但是反过来不行
// 函数没有返回值,那么函数的返回值类型就是void
function fn(): void {
return undefined;
}
注意:
void只用在函数没有返回值的情形下。
undefined和null最大的价值主要体现在接口类型上,表示可缺省、未定义的属性;null表示对象或者属性是空值。
这个可以先有个印象,后面说到接口会讲
单纯声明 undefined 或者 null 类型的变量是无比鸡肋的,上面的例子只是说明原始类型
如果不写类型,typescript是可以推断类型的,但注意let、const的区别
小object:代表的是非原始类型的类型,也就是不能是string,number,boolean,symbol 严格模式:多包括null,undefined
let obj1: object = 3; // 报错
let obj2: object = "3"; // 报错
let obj3: object = true; // 报错
let obj4: object = null; // 报错
let obj5: object = undefined; // 报错
let obj6: object = Symbol(); // 报错
let obj7: object = {a: 1, b: '2'};
let obj8: object = [1, 2, 3];
大Object :代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object,严格模式下不包括null,undefined。{}空对象类型和大 Object 一样。
let obj1: Object = 3;
let obj2: Object = "3";
let obj6: Object = Symbol();
let obj3: Object = true;
let obj4: Object = null; // 报错
let obj5: Object = undefined; // 报错
let obj7: Object = { a: 1, b: "2" };
let obj8: Object = [1, 2, 3];
注意: 1. 官方文档说可以使用小 object 代替大 Object,但是我们仍要明白大 Object 并不完全等价于小 object。 2. 上面的例子看起来,大Object是小object的父类型,但并不是!!!真实的情况是大Object才是小object的子类型
数组类型的定义:
let arr1: Array = [1, 2, 3];
arr1.push('3'); // 报错
arr1.push(5);
let arr2: string[] = ['4', '5', 'a'];
arr2[3] = '6';
我们知道数组中元素的数据类型都一般是相同的(any[] 类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组。
元组中允许存储不同类型的元素,元组可以作为参数传递给函数。
元组是一种特殊的数组。有两点特殊之处
它约定了的元素个数
它约定了特定索引对应的数据类型
let tuple = [value1,value2,value3,…value n]
字面量(literal)是用于表达源代码 (opens new window)中一个固定值的表示法(notation)【字面意思】
字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。
let x3: 1 | 3 = 1;
x3 = 2; // 报错
x3 = 3;
let x4:'hello' = 'hello';
x4 = 'hi'; // 报错
注意:
TypeScript 支持 3 种字面量类型:string字面量类型、number字面量类型、boolean字面量类型**
有何用?
字面量类型一般是配合联合类型一起使用的, 用来表示一组明确的可选值列表。 (角色、性别、错误类型等)
type role = "admin" | "readonly" | "editor"
let curr:role = "admin";
可以把“|”类比为 JavaScript 中的逻辑或 “||”,只不过前者表示可能的类型。
联合类型表示一个值可以是几种类型之一。 我们用竖线( |)分隔每个类型,所以 number | string | boolean表示一个值可以是 number, string,或 boolean。
let age: number | string = 20;
交叉类型是将多个类型合并为一个类型
在 TypeScript 中,还存在一种类似逻辑与行为的类型——交叉类型(Intersection Type),它可以把多个类型合并成一个类型,合并后的类型将拥有所有成员类型的特性。使用“&”操作符来声明交叉类型(并集)。
// 思考这里有一个值满足m的类型要求吗?
let m : string & number;
let zs: { name: string; age: number } & { height: number } = {
name: "张三",
age: 20,
height: 180,
};
联合、交叉类型本身就可以直接组合使用,这就涉及 |、& 操作符的优先级问题。联合操作符 | 的优先级低于交叉操作符 &,同样,我们可以通过使用小括弧 () 来调整操作符的优先级,这个和js一样。
let m:{ id: number } & { name: string } | { id: string } & { name: number };
m = {
id: 1,
name: ''
}
m = {
id: '',
name: 1
}
any 指的是一个任意类型,它是官方提供的一个选择性绕过静态类型检测的作弊方式。
非常不建议使用;Any is Hell(Any 是地狱)
unknown 是 TypeScript 3.0 中添加的一个类型,它主要用来描述类型并不确定的变量。和any的区别就是会进行类型检测。
let unk: unknown;
let x = 1;
let y = "2";
if (x) {
unk = x;
} else {
unk = y;
}
// 使用unknown后,typescript会做类型检测
unk.toFixed(2); // 报错
// any会绕过类型检测,所以下面不会有问题提示
let an1: any;
an1.toFixed(2);
// 通过缩小类型可以通过类型检测
if (typeof unk === 'number') {
unk.toFixed(2);
}
注意:
1. 可以把任何类型的值赋值给unknown,但是unknown类型的值只能赋值给any或者unknown;
2. unknown比any好的地方,还有一个就是它可以通过缩小类型的手段类确定类型
never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。
function error():never{
throw new Error("出错了")
}
function fn2():never{
return error(); //推断的返回值类型为never
}
function fn3():never{
while(true){
}
}
注意:
如果函数里是死循环,返回值类型也是never
never 是所有类型的子类型
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换。它没有运行时的影响,只是在编译阶段起作用。
类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string";
let strLength: number = (someValue).length;
另一个为as语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。
在es6的module中,as可用来取别名
import {numA as myNumA} from './import.js'
TypeScript 不仅能帮助前端改变思维方式,还能强化面向接口编程的思维和能力,而这正是得益于 Interface 接口类型。
使用接口定义变量和函数参数的类型
interface PersonInfo {
name: string;
age: number;
}
// 定义变量的类型
let zhangsan: PersonInfo = {
name: "张三",
age: 20,
};
// 定义数组的类型
interface ArrayNumber {
[idx: number]: number;
}
let arr1: ArrayNumber = [1, 2, 3];
// 定义函数的类型
interface PersonFn {
(p: PersonInfo): void;
}
let Person1: PersonFn = (obj: PersonInfo): void => {
console.log(obj.name, obj.age);
};
注意:
很少使用接口类型来定义函数的类型,更多使用内联类型或类型别名配合箭头函数语法来定义函数类型;
多个不同接口之间是可以实现继承的,但是如果继承的接口PersonInfo和被继承的接口NameInfo有相同的属性,并且类型不兼容,那么就会报错。
interface NameInfo {
name: string;
}
interface AgeInfo {
age: number;
}
interface PersonInfo extends NameInfo, AgeInfo {
height: number;
}
let zs: PersonInfo = {
name: "张三",
age: 20,
height: 177,
};
多个不同的接口可以实现继承,组合成一个新的接口,那么如果出现多个相同名字的接口会怎么样?
多个相同名字的接口,会进行合并,得到一个新的接口;这个接口的特性一般用在扩展第三方库的接口类型。
interface PersonInfo {
name: string,
age: number
}
interface PersonInfo {
name: string,
height: number
}
let zs: PersonInfo = {
name: "张三",
age: 20,
height: 177,
};
interface PersonInfo {
name?: string; // 缺省
readonly height: number; // 只读
}
接口类型的一个作用是将内联类型抽离出来,从而实现类型可复用。其实,我们也可以使用类型别名接收抽离出来的内联类型实现复用。格式:type 别名名称 = 类型定义。
类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
type PersonInfo = { name: string; age: number };
let zs: PersonInfo = {
name: "张三",
age: 20,
};
大家可能觉得这个和接口没多大区别,这不是重复了吗?其实不是,类型别名可以针对接口没法覆盖的场景,例如组合类型、交叉类型等;
// 1. 组合类型
type NumAndString = number | string;
// 2. 交叉类型
type SectionType = { name: string; age: number } & {
height: number;
name: string;
};
interface PersonInfo {
name: string;
height: number;
}
// 3. 提取接口属性类型
type PersonHeight = PersonInfo["height"];
let zs: SectionType = {
name: "张三",
age: 20,
height: 180,
};
// 黑魔法
type BorderColor = 'black' | 'red' | 'green' | 'yellow' | 'blue' | string & {}; // vscode提示字面类型都被保留
实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。
重复定义的接口类型,它的属性会叠加,这个特性使得我们可以极其方便地对全局变量、第三方库的类型做扩展
如果我们重复定义类型别名,那么就会报错
和JavaScript一样,TypeScript函数可以创建有名字的函数和匿名函数
显式指定函数参数和返回值的类型
const add = (a: number, b: number): number => {
return a + b;
}
或者用type来声明函数类型
type addFnType = (a: number, b:number) => number;
let addFn: addFnType = (num1, num2) => {
return num1 + num2;
}
参数一般有:可选参数、默认参数、剩余参数;
在类型标注的:前添加?表示 log 函数的参数 x 就是可缺省的;
function log(msg?: string):void {
console.log(msg);
}
可缺省是不是相当于msg参数的类型就是和string | undefined等价呢?这个当然不是,string | undefined的意思是这两个类型中的一种,而可缺省是不传的意思。
function addFn1(num1: number = 1, num2: number = 2):number {
return num1 + num2;
}
函数的默认参数类型必须是参数类型的子类型
function log3(x: number | string = 'hello') {
console.log(x);
}
这里x参数的类型就是联合类型number | string,函数默认参数的类型就是联合类型的子类型
function sum(...nums: number[]) {
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2); // => 3
sum(1, 2, 3); // => 6
函数中的this问题,一直都是javascript最令人头疼的问题,因为this的指向只有函数调用的时候才能确定。还有一些可以改变this指向的方法(apply,call,bind)。
但是在Typescript中,必须要明确的指定this的类型(严格模式下)。
type objType = {person: (n: string) => void, myname: string};
function person(this: Window | objType , name: string):void {
this.myname = name;
console.log(this.myname);
}
window.person = person;
window.person('window name');
let obj:objType = {
person,
myname: ''
};
obj.person('obj name');
单单是上面的代码是有问题的,我们还需要创建一个类型声明文件global.d.ts,为window对象上扩展两个属性person、myname;
interface Window {
person: (n: string) => void;
myname: string;
}
定义对象的函数属性时,只要实际调用中 this 的指向与指定的 this 指向不同,TypeScript 就能发现 this 指向的错误
interface ObjType2 {
name: string;
say: (this: ObjType2) => void;
}
let obj2:ObjType2 = {
name: 'obj2',
say() {
console.log(this.name);
}
}
obj2.say(); // ok
let t11 = obj2.say;
t11();
注意:
显式声明函数的返回值类型为 undfined,则会出现错误提示,如果没有返回值,我们用void表示;
注意:显式注解函数中的 this 类型,它表面上占据了第一个形参的位置,但并不意味着函数真的多了一个参数,因为 TypeScript 转译为 JavaScript 后,“伪形参” this 会被抹掉,这算是 TypeScript 为数不多的特有语法。
数字枚举和字符串枚举
使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。
枚举的作用在于定义被命名的常量集合,一个默认从 0 开始递增的数字集合,称之为数字枚举。
enum Days {
Sunday = 1,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
}
let day = Days.Sunday;
enum Gender {
FEMALE=0,
MALE=1
}
console.log(Gender.FEMALE);
面向对象实践 OOP 编程思想,在实际工作中,都是极其有用的抽象、封装利器。
class Person {
name: string;
say(this: Person, song: string): Person {
console.log(song);
return this;
}
constructor(name: string) {
this.name = name;
}
}
let p1 = new Person('张三');
p1.say('Song').name;
使用extends关键字实现继承
class Male extends Person {
age: number;
constructor(name: string, age: number) {
super(name);
this.age = age;
}
}
在 TypeScript 中就支持 3 种访问修饰符,分别是 public、private、protected。通过这三个修饰符做到控制属性和方法的访问。
public:基类、子类、类外部都可以访问;
protected:基类、子类可以访问,类外部不可以访问;
private:基类可以访问,子类、类外部不可以访问;
readonly:只读修饰符
class Person {
public readonly name: string = '张三';
protected age: number = 20;
private height: string = '180';
protected getPersonInfo():void {
console.log(this.name, this.age, this.height); // 基类里面三个修饰符都可以访问
}
}
class Male extends Person {
public getInfo():void {
console.log(this.name, this.age); // 子类只能访问public、protected修饰符的
}
}
let m = new Male();
console.log(m.name); // 类外部只能访问public修饰的
m.name = '李四'; // name属性使用只读修饰符,所以不能对name进行赋值修改操作
基于静态属性的特性,往往会把与类相关的常量、不依赖实例 this 上下文的属性和方法定义为静态属性,从而避免数据冗余,进而提升运行性能。
class Person {
static title: string = "个人信息";
}
Person.title;
抽象类,它是一种不能被实例化仅能被子类继承的特殊类。
关键字abstract
抽象类不允许被实例化,抽象类的存在只为了向子类服务
抽象类中包含抽象属性/方法,和普通属性/方法
被抽象的属性/方法不允许拥有具体的内容
子类如果不是抽象类,就必须将所有抽象父类的方法/属性具体化
abstract class Person {
abstract name: string;
abstract getName(): void;
extendsFn():void {
console.log('扩展方法');
}
}
class Male extends Person {
constructor(name: string) {
super();
this.name = name;
}
name: string;
getName(): void {
console.log(this.name);
}
}
接口interface也可以约束类的实现,使用接口与使用抽象类相比,区别在于接口只能定义类成员的类型。
interface Person {
name: string;
age: number;
getName: () => void;
}
class Male implements Person {
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
name: string;
age: number;
getName(): void {
console.log(this.name);
}
}
在声明类的时候,其实也同时声明了一个特殊的类型(确切地讲是一个接口类型),这个类型的名字就是类名,表示类实例的类型。
class Male {
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
name: string;
age: number;
getName(this: Male): void {
console.log(this.name);
}
}
let m1: Male = new Male("张三", 20);
let m2: Male = {
name: "张三",
age: 20,
getName(this: Male) {
console.log(this.name);
},
};
m2.getName();
let fn = m2.getName;
fn(); // 报错,this指向并不是Male对象
泛型指的是类型参数化,即将原来某种具体的类型进行参数化。设计泛型的目的在于有效约束类型成员之间的关系,比如函数参数和返回值、类或者接口成员和方法之间的关系。
function getValue(val: string): string {
return val;
}
function getValue1(val: number): number {
return val;
}
function getValue2(val: unknown): unknown {
return val;
}
let g1: string = getValue("1");
let g2: number = getValue1(1);
let g3: unknown = getValue2(1);
function getValue3(val: T): T {
return val;
}
let g4: number = getValue3(3);
let g5: string = getValue3('4');
其中 T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思:
T(Type):表示一个 TypeScript 类型
K(Key):表示对象中的键类型
V(Value):表示对象中的值类型
E(Element):表示元素类型
注意:泛型类型可以有多个或一个以逗号(,)进行分割,比如
前面我们使用过Array<类型>来定义数组的类型,这里的Array也是一种类型。
在 TypeScript 中,类型本身就可以被定义为拥有不明确的类型参数的泛型,并且可以接收明确类型作为入参,从而衍生出更具体的类型。
// 定义数组类型
let arr: Array = [1];
let arr1: Array = [""];
// 类型别名
type typeFn = (params: P) => P;
let fntype: typeFn = (n: number) => {
return n;
};
let fn1:typeFn = (p: string):string => {
return p;
}
// 定义接口类型
interface TypeItf {
name: P;
getName: (p: P) => P;
}
let t1: TypeItf = {
name: 123,
getName: (n: number) => {
return n;
},
};
let t2: TypeItf = {
name: "123",
getName: (n: string) => {
return n;
},
};
把泛型入参限定在一个相对更明确的集合内,以便对入参进行约束。
interface TypeItf {
name: P;
getName: (p: P) => P;
}
let t1: TypeItf = {
name: 123,
getName: (n: number) => {
return n;
},
};
let t2: TypeItf = {
name: "123",
getName: (n: string) => {
return n;
},
};
打造自己的工具类型,这个时候需要用到一些物料
extends关键字判断泛型参数P是否是string或者是number其中的一种,最终类型的确定由三元运算的结果决定。
type TypeFn = P extends string | number ? P[] : P;
let m: TypeFn = [1, 2, 3];
let m1: TypeFn = ['1', '2', '3'];
let m2: TypeFn = true;
类型推断infer相当于声明一个变量接收传入的类型
type ObjType = T extends { name: infer N; age: infer A } ? [N, A] : [T];
let p: ObjType<{ name: string; age: number }> = ["张三", 1];
let p1: ObjType<{name: string}> = [{name: '张三'}];
Keyof提取对象属性名、索引名、索引签名的类型;
interface NumAndStr {
name: string;
age: number;
[key: number]: string | number;
}
type TypeKey = keyof NumAndStr; // number | 'name' | 'age'
let t:TypeKey = 'name';
in是映射类型
type NumAndStr = number | string;
type TargetType = {
[key in NumAndStr]: string | number;
};
let obj: TargetType = {
1: '123',
"name": 123
}
注意:
我们只能在类型别名定义中使用 in,如果在接口(interface)中使用,则会提示一个错误
in 和 keyof 也只能在类型别名定义中组合使用
typeof 的主要用途是在类型上下文中获取变量或者属性的类型
// 推断变量的类型
let strA = "2";
type KeyOfType = typeof strA; // string
// 反推出对象的类型作为新的类型
let person = {
name: '张三',
getName(name: string):void {
console.log(name);
}
}
type Person = typeof person;
用于标识 TypeScript 项目的根路径;
用于配置 TypeScript 编译器;
用于指定编译的文件。
files - 设置要编译的文件的名称;
include - 设置需要进行编译的文件,支持路径模式匹配;
exclude - 设置无需进行编译的文件,支持路径模式匹配;
compilerOptions - 设置与编译流程相关的选项。
{
"compilerOptions": {
/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。
/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}