从实践角度梳理一下常用的 TS 知识点和使用场景
想要学号 TS,官方文档多读,代码多写,无他。
- https://www.tslang.cn/
- https://ts.xcatliu.com/ xcatliu 的 TS 文章也不错,适合前端工程师阅读
类型和值
要想搞懂 TS,先理解这2个概念
类型就是变量的类型,类的类型(使用 interface 定义),函数的类型(参数和返回值)
声明类型的方法有6种
- 使用类型别名
- x: number
- 使用枚举 enum
- 使用接口 interface
- 类声明 class c {}
什么是值?
声明一个变量,声明一个函数,然后这些变量、函数可以被调用,值是运行时的名字。
可索引类型
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
interface 和 type 关键字
功能非常相近,但是又有点区别
interface 和 type 两个关键字的含义和功能都非常的接近。这里我们罗列下这两个主要的区别:
interface:
- 同名的 interface 自动聚合,也可以跟同名的 class 自动聚合
- 可以给函数挂属性
- 只能表示 object、class、function 类型
- 想要扩展接口,只能用继承的方式
- 不能实现或者继承联合类型的 type
type:
- 不仅仅能够表示 object、class、function
- 不能重名(自然不存在同名聚合了),扩展已有的 type 需要创建新 type
- 支持复杂的类型操作,比如说 & |
- 想要扩展 type 定义的类型,使用 &
枚举
常用的有常量枚举,不生成类型,只使用枚举值。
export const enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
断言
有时候,我们比 TS 更清楚变量是什么类型,可以使用断言,通常我们在 React 中使用 as 语法。
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
尖括号语法
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
类型保护
在特定的区块中保证变量属于某种确定的类型,可以在此区块中放心的引用此类型的属性,或者调用此类型的方法。
- instanceof
- typeof 判断类型
- in 某个 key 存在于实例中
- 保护函数
特殊的返回值 - 类型谓词
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
交叉类型 联合类型
interface IPerson {
id: string;
age: number;
}
interface IWorker {
companyId: string;
}
type IStaff = IPerson & IWorker;
const sayHello = (name: string | undefined) => {
/* ... */
};
为函数指定类型
注意:使用变量定义、类型别名、接口约束函数,都是函数类型的声明定义,没有实现该函数
// 返回值可以推断出来,不用写
const add = (x: number, y: number) => x + y
const add9 = (x: number, y: number): number => x + y
使用接口约束函数
interface F {
(a: number, b: number): number;
}
let add: F = (a, b) => a + b
类型别名约束函数
type Add = (x: number, y: number) => number;
let add: Add = (a, b) => a + b
变量定义函数
let mySum: (x: number, y: number) => number
mySum = function (x: number, y: number): number {
return x + y;
};
模块
第一,ES6 模块与 CommonJS 规范不要混用
有一种兼容性写法
导出 export =
导入 import from 或者 impot x = require
操作符
extends
type num = {
num:number;
}
interface IStrNum extends num {
str:string;
}
// 与上面等价
type TStrNum = A & {
str:string;
}
keyof
获取某种类型的所有 key 的联合类型
interface Person {
name: string;
age: number;
location: string;
}
type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[]; // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person }; // string | number
泛型
泛型就是定义一种模板,例如ArrayList
,然后在代码中为用到的类创建对应的ArrayList<类型>。
- 实现了编写一次,万能匹配,又通过编译器保证了类型安全:这就是泛型
- 泛型的好处是使用时不必对类型进行强制转换,它通过编译器对类型进行检查;
- 泛型可以通过参数
决定引入的参数类型 参考 什么是泛型 《Java教程》廖雪峰
通常我们说,泛型就是指定一个表示类型的变量
「泛型就是不预先确定的数据类型,具体的类型在使用的时候再确定的一种类型约束规范」
「我们也可以把泛型变量理解为函数的参数,只不过是另一个维度的参数,是代表类型而不是代表值的参数。」**
泛型可以用于类的实例成员、类的方法、函数参数和函数返回值
泛型可以应用于 function
、interface
、type
或者 class
中。但是注意,「泛型不能应用于类的静态成员」
interface GenericIdentityFn<T> {
(arg: T): T;
}
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
泛型类型、泛型接口
type Log = <T>(value: T) => T
let myLog: Log = log
interface Log<T> {
(value: T): T
}
let myLog: Log<number> = log // 泛型约束了整个接口,实现的时候必须指定类型。如果不指定类型,就在定义的之后指定一个默认的类型
myLog(1)
函数中省略尖括号,推断出要传入的类型
TS 会自动推断出要传的参数类型
function identity <T, U>(value: T, message: U) : T {
console.log(message);
return value;
}
console.log(identity(68, "Semlinker"));
函数返回一个object,并使用泛型
interface Iidentity<T, U> {
value: T,
message: U
}
function identity<T, U>(value: T, message: U): Iidentity<T, U> {
return {
value,
message
} ;
}
console.log(identity([12,3], 'hello'));
常见泛型变量代表的意思
- T(Type):表示一个 TypeScript 类型
- K(Key):表示对象中的键类型
- V(Value):表示对象中的值类型
- E(Element):表示元素类型
泛型类
泛型类可确保在整个类中一致地使用指定的数据类型。
我们来看一个泛型类实现泛型接口的例子
interface GenericInterface<U> {
value: U
getIdentity: () => U
}
class IdentityClass<T> implements GenericInterface<T> {
value: T
constructor(value: T) {
this.value = value
}
getIdentity(): T {
return this.value
}
}
// 可以省略 <number>
const myNumberClass = new IdentityClass(68);
console.log(myNumberClass.getIdentity()); // 68
const myStringClass = new IdentityClass<string>("Semlinker!");
console.log(myStringClass.getIdentity()); // Semlinker!
什么时候需要使用泛型?
当你的函数、接口或类将处理多种数据类型时
可能刚开始不使用泛型,随着项目越来越复杂,需要支持多种数据类型,你可能要考虑使用泛型了
类型约束,需预定义一个接口
希望限制每个类型变量(T)接受的类型数量
1 只允许函数传入那些包含 length 属性的变量
interface Length {
length: number
}
function logAdvance<T extends Length>(value: T): T {
console.log(value, value.length);
return value;
}
// 必须传入具有 length 属性的
logAdvance([1])
logAdvance('123')
logAdvance({ length: 3 })
2 举一个例子,返回一个对象上的 k-v 值
// 限制输入的 key 是对象上的
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
let tsInfo = {
name: "Typescript",
supersetOf: "Javascript",
}
let difficulty = getProperty(tsInfo, 'supersetOf'); // OK
泛型参数指定默认类型
interface A<T=string> {
name: T;
}
const strA: A = { name: "Semlinker" };
// 如果想传入 number,需写上<number>
const numB: A<number> = { name: 101 };
泛型工具类型 - 映射类型
返回一个新类型
常用的如 Partial、Required、Readonly、Record 和 ReturnType
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Construct a type with a set of properties K of type T
* 引入新的类型
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// node_modules/typescript/lib/lib.es5.d.ts
/**
* From T, pick a set of properties whose keys are in the union K
* 把 T 的子属性挑出来,变成包含这个类型部分属性的子类型。
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// node_modules/typescript/lib/lib.es5.d.ts
/**
* Exclude from T those types that are assignable to U
* 将 T 中某些属于 U 的属性剔除掉
*
*/
type Exclude<T, U> = T extends U ? never : T;
类型声明和声明文件
为啥要写声明文件,不写编辑器就会报错,类型检查不通过,代码编译不通过。
声明文件的作用?
比如你引入一个 JS 文件,TS 编译器并不能知道这个模块导出了什么接口,所以你要写一个 .d.ts 来告诉 TS 你所暴露的成员都是什么,接受什么参数等等。
TypeScript 的声明文件是一个以
.d.ts
为后缀的 TypeScript 代码文件, 但它的作用是描述一个 JavaScript 模块(广义上的)内所有导出接口的类型信息
前置学习条件
- 学习 declare 语句
- 不同场景下,如何导出
- 普通模块
- 全局JS
- UMD
- 学习声明合并
- 学习 namespace
引入的@types包
默认所有可见的”@types”包会在编译过程中被包含进来。如果指定了 typeRoots和types,结果会有些不同。看这里 https://www.tslang.cn/docs/handbook/tsconfig-json.html
我们结合常见的三种场景梳理一下类型声明文件该怎么写
全局 JS
修改全局作用域 的模块( 模块化引入 )
global-modifying-module.d.ts
当一个全局修改的模块被导入的时候,它们会改变全局作用域里的值
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the global-modifying module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ Note: If your global-modifying module is callable or constructable, you'll
*~ need to combine the patterns here with those in the module-class or module-function
*~ template files
*/
declare global {
/*~ Here, declare things that go in the global namespace, or augment
*~ existing declarations in the global namespace
*/
interface String {
fancyFormat(opts: StringFormatOptions): string;
}
}
/*~ If your module exports types or values, write them as usual */
export interface StringFormatOptions {
fancinessLevel: number;
}
/*~ For example, declaring a method on the module (in addition to its global side effects) */
export function doSomething(): void;
/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */
export { };
修改全局作用域 的 JS 插件(通过 script 引入)
global-plugin.d.ts
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This template shows how to write a global plugin. */
/*~ Write a declaration for the original type and add new members.
*~ For example, this adds a 'toBinaryString' method with to overloads to
*~ the built-in number type.
*/
interface Number {
toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;
toBinaryString(callback: MyLibrary.BinaryFormatCallback, opts?: MyLibrary.BinaryFormatOptions): string;
}
/*~ If you need to declare several types, place them inside a namespace
*~ to avoid adding too many things to the global namespace.
放到命名空间下,避免太多全局变量
*/
declare namespace MyLibrary {
type BinaryFormatCallback = (n: number) => string;
interface BinaryFormatOptions {
prefix?: string;
padding: number;
}
}
普通的全局库( script 引入)
使用 global.d.ts 模板
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ If this library is callable (e.g. can be invoked as myLib(3)),
如果该库可调用 muLib(3)
*~ include those call signatures here.
*~ Otherwise, delete this section.
*/
declare function myLib(a: string): string;
declare function myLib(a: number): number;
/*~ If you want the name of this library to be a valid type name,
*~ you can do so here.
想让你的类库是一个有效的类型名
*~
*~ For example, this allows us to write 'var x: myLib';
*~ Be sure this actually makes sense! If it doesn't, just
*~ delete this declaration and add types inside the namespace below.
*/
interface myLib {
name: string;
length: number;
extras?: string[];
}
/*~ If your library has properties exposed on a global variable,
你的类库在全局变量下暴露的很多属性
*~ place them here.
*~ You should also place types (interfaces and type alias) here.
*/
declare namespace myLib {
//~ We can write 'myLib.timeout = 50;'
let timeout: number;
//~ We can access 'myLib.version', but not change it
const version: string;
//~ There's some class we can create via 'let c = new myLib.Cat(42)'
//~ Or reference e.g. 'function f(c: myLib.Cat) { ... }
class Cat {
constructor(n: number);
//~ We can read 'c.age' from a 'Cat' instance
readonly age: number;
//~ We can invoke 'c.purr()' from a 'Cat' instance
purr(): void;
}
//~ We can declare a variable as
//~ 'var s: myLib.CatSettings = { weight: 5, name: "Maru" };'
interface CatSettings {
weight: number;
name: string;
tailLength?: number;
}
//~ We can write 'const v: myLib.VetID = 42;'
//~ or 'const v: myLib.VetID = "bob";'
type VetID = string | number;
//~ We can invoke 'myLib.checkCat(c)' or 'myLib.checkCat(c, v);'
function checkCat(c: Cat, s?: VetID);
}
UMD
UMD 指的是既可以通过 script 引入,通过全局变量执行,又可以通过模块化引入的方式。
为什么模块导出要这样写?export = MyClass;
TODO;
如果模块能够作为 function调用
module-function.d.ts
export as namespace myFuncLib;
/*~ This declaration specifies that the function
*~ is the exported object from the file
*/
export = MyFunction;
/*~ This example shows how to have multiple overloads for your function */
declare function MyFunction(name: string): MyFunction.NamedReturnType;
declare function MyFunction(length: number): MyFunction.LengthReturnType;
/*~ If you want to expose types from your module as well, you can
*~ place them in this block. Often you will want to describe the
*~ shape of the return type of the function; that type should
*~ be declared in here, as this example shows.
*/
declare namespace MyFunction {
export interface LengthReturnType {
width: number;
height: number;
}
export interface NamedReturnType {
firstName: string;
lastName: string;
}
/*~ If the module also has properties, declare them here. For example,
*~ this declaration says that this code is legal:
*~ import f = require('myFuncLibrary');
*~ console.log(f.defaultName);
*/
export const defaultName: string;
export let defaultLength: number;
}
如果模块能够被 new
module-class.d.ts
export as namespace myClassLib;
/*~ This declaration specifies that the class constructor function
*~ is the exported object from the file
*/
export = MyClass;
/*~ Write your module's methods and properties in this class */
declare class MyClass {
constructor(someParam?: string);
someProperty: string[];
myMethod(opts: MyClass.MyClassMethodOptions): number;
}
/*~ If you want to expose types from your module as well, you can
*~ place them in this block.
*/
declare namespace MyClass {
export interface MyClassMethodOptions {
width?: number;
height?: number;
}
}
如果模块不能被调用或构造
module.d.ts
下面是 module.d.ts 的例子
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ If this module is a UMD module that exposes a global variable 'myLib' when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
export as namespace myLib;
/*~ If this module has methods, declare them as functions like so.
*/
export function myMethod(a: string): string;
export function myOtherMethod(a: number): number;
/*~ You can declare types that are available via importing the module */
// 您可以通过导入模块声明可用的类型
// 这些类型声明可以在你的代码库中使用
export interface someType {
name: string;
length: number;
extras?: string[];
}
/*~ You can declare properties of the module using const, let, or var */
export const myField: number;
/*~ If there are types, properties, or methods inside dotted names
*~ of the module, declare them inside a 'namespace'.
*/
export namespace subProp {
/*~ For example, given this definition, someone could write:
*~ import { subProp } from 'yourModule';
*~ subProp.foo();
*~ or
*~ import * as yourMod from 'yourModule';
*~ yourMod.subProp.foo();
*/
export function foo(): void;
}
插件
插件的引入,会改变模块或者全局变量
模块插件或者 UMD 插件
module-plugin.d.ts
比如一个 moment 的插件,会为 moment 对象添加新的方法
了解一下 declare module 扩展模块 的用法
// Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
// Project: [~THE PROJECT NAME~]
// Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
/*~ This is the module plugin template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ On this line, import the module which this module adds to */
import * as m from 'someModule';
/*~ You can also import other modules if needed */
import * as other from 'anotherModule';
/*~ Here, declare the same module as the one you imported above */
declare module 'someModule' {
/*~ Inside, add new function, classes, or variables. You can use
*~ unexported types from the original module if needed. */
export function theNewMethod(x: m.foo): other.bar;
/*~ You can also add new properties to existing interfaces from
*~ the original module by writing interface augmentations */
export interface SomeModuleOptions {
someModuleSetting?: string;
}
/*~ New types can also be declared and will appear as if they
*~ are in the original module */
export interface MyModulePluginOptions {
size: number;
}
}
模块
如果我们一个 JS 库,想要发布到 npm 上,并且像为它添加上类型文件。可以直接放一个 index.d.ts 文件(位置在包的根目录下,与 index.js 并列 ),就行了。或者不叫 index.d.ts ,那样就需要通过 package.json 文件的 typings 或者 types 字段,指定这个文件。我们来看一个例子
{
"name": "awesome",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts"
}
如果我们引入别人写的模块,而且正好 @types仓库里没有现成的,那自己整一个吧。
创建一个 types 目录,把声明文件都放到这个目录下,然后修改 tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
}
}
}
把上面 UMD 那个示例文件,删掉下面这一行,不用导出全局变量
export as namespace myLib;
关于依赖
如果我们写的类库中依赖的第三方库,没有将自己的声明文件放到包中,那我们需要下载该依赖文件,并且依赖项放到 dependencies,而不是 devDependencies。如果放到 dev 依赖,用户就需要自己 install @types/xxx 了。
{
"name": "browserify-typescript-extension",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts",
"dependencies": {
"browserify": "latest",
"@types/browserify": "latest",
"typescript": "next"
}
}
全局变量
下面的写法用到了声明合并,命名空间和函数合并,相当于为函数添加属性和方法
// global.d.ts
declare function globalLib(options: globalLib.Options): void;
declare namespace globalLib {
const version: string;
function doSomething(): void;
interface Options {
[key: string]: any
}
}
// global.ts
function globalLib(options) {
console.log(options);
}
globalLib.version = '1.0.0';
globalLib.doSomething = function() {
console.log('globalLib do something');
};
自动生成声明文件
如果你的类库是用 TS 写的,在 tsconfig.json 里开启 declaration 选型,或者使用 tsc —declaration
{
"compilerOptions": {
"module": "commonjs",
"outDir": "lib",
"declaration": true,
}
}
类型别名
type Message = string | string[];
interface
工程知识
常用的 npm 依赖
"dependencies": {
"typescript": "3.5.3"
},
"devDependencies": {
"@types/jest": "24.0.15",
"@types/node": "12.6.8",
"@types/react": "16.8.23",
"@types/react-dom": "16.8.5",
"@types/lodash": "^4.14.136",
"@types/react-redux": "^7.1.2",
"@types/react-router-dom": "^4.3.4",
}
@types放哪里的问题
假设您正在开发一个包含“A”的包,它在devDependencies中包含@types/some-module包。出于某种原因,您要从@types/some-module导出类型
import {SomeType} from 'some-module';
export default class APackageClass {
constructor(private config: SomeType) {
}
}
现在,包“A”的TypeScript使用者无法猜出SomeType是什么,因为未安装包“A”的devDependencies。
在这种特殊情况下,您需要将@ types/*包与常规“依赖”放在一起。对于其他情况,“devDependencies”足够好。
node 执行 ts 代码
node 环境需要使用 ts-node 来编译 ts 代码
编译工具、代码检查工具
配置文件
{
"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 // 为装饰器提供元数据的支持
}
}