typescript
学习:B站小满ZS
CSDN:CSDN
基础类型 基础类型:Boolean、Number、String、null
、undefined
以及 ES6 的 Symbol 和 ES10 的 BigInt
1、字符串类型 字符串是使用string定义的
1 2 3 4 5 6 let a : string = '123' let str : string = `dddd${a} `
其中 `用来定义 ES6 中的模板字符串 ,${expr}用来在模板字符串中嵌入表达式。
2、数字类型 支持十六进制、十进制、八进制和二进制;
1 2 3 4 5 6 7 let notANumber : number = NaN ;let num : number = 123 ;let infinityNumber : number = Infinity ;let decimal : number = 6 ;let hex : number = 0xf00d ;let binary : number = 0b1010 ;let octal : number = 0o744 ;
3、布尔类型 注意,使用构造函数 Boolean 创造的对象不是布尔值:
1 2 let createdBoolean : boolean = new Boolean (1 )
事实上 new Boolean() 返回的是一个 Boolean 对象 需要改成
1 let createdBoolean : Boolean = new Boolean (1 )
1 2 3 let booleand : boolean = true let booleand2 : boolean = Boolean (1 )
4、空值类型 JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数
1 2 3 function voidFn ( ): void { console .log ('test void' ) }
void 类型的用法,主要是用在我们不希望调用者关心函数返回值的情况下,比如通常的异步回调函数
void也可以定义undefined 和 null类型
1 2 let u : void = undefined let n : void = null ;
5、Null和undefined类型 1 2 let u : undefined = undefined ;let n : null = null ;
void 和 undefined 和 null 最大的区别 与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 string 类型的变量:
1 2 3 let test : void = undefined let num2 : string = "1"
1 2 3 4 5 6 7 8 9 10 11 12 num2 = testlet test : null = null let num2 : string = "1" num2 = testlet test : undefined = undefined let num2 : string = "1" num2 = test
TIPS 注意: 如果你配置了tsconfig.json 开启了严格模式
1 2 3 4 5 { "compilerOptions" :{ "strict" : true } }
null 不能 赋予 void 类型
任意类型 Any 类型 和 unknown 顶级类型
1.没有强制限定哪种类型,随时切换类型都可以 我们可以对 any 进行任何操作,不需要检查类型 1 2 3 let anys :any = 123 anys = '123' anys = true
2.声明变量的时候没有指定任意类型默认为any 1 2 3 let anys; anys = '123' anys = true
3.弊端如果使用any 就失去了TS类型检测的作用 4.TypeScript 3.0中引入的 unknown 类型也被认为是 top type ,但它更安全。与 any 一样,所有类型都可以分配给unknown unknow unknow类型比any更加严格当你要使用any 的时候可以尝试使用unknow
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 let value : unknown ; value = true ; value = 42 ; value = "Hello World" ; value = []; value = {}; value = null ; value = undefined ; value = Symbol ("type" ); let names :unknown = '123' let names2 :string = nameslet names :any = '123' let names2 :string = names let bbb :unknown = '123' let aaa :any = '456' aaa = bbb
区别2
1 2 3 4 5 6 7 8 如果是any 类型在对象没有这个属性的时候还在获取是不会报错的let obj :any = {b :1 } obj.a 如果是unknow 是不能调用属性和方法let obj :unknown = {b :1 ,ccc :():number =>213 } obj.b obj.ccc ()
接口和对象类型 对象的类型 在typescript中,我们定义对象的方式要用关键字interface (接口),我的理解是使用interface 来定义一种约束,让数据的结构满足约束的格式。定义方式如下:
1 2 3 4 5 6 7 8 9 10 11 interface Person { b :string , a :string } const person :Person = { a :"213" }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface A{name :string }interface A{age :number }var x :A={name :'xx' ,age :20 }interface A{ name :string } interface B extends A{ age :number } let obj :B = { age :18 , name :"string" }
可选属性 使用?操作符 1 2 3 4 5 6 7 8 9 10 interface Person { b?:string , a :string } const person :Person = { a :"213" }
任意属性 [propName: string] 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集 :
1 2 3 4 5 6 7 8 9 10 11 12 13 interface Person { b?:string , a :string , [propName : string ]: any ; } const person :Person = { a :"213" , c :"123" }
只读属性 readonly readonly 只读属性是不允许被赋值的只能读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Person { b?: string , readonly a : string , [propName : string ]: any ; } const person : Person = { a : "213" , c : "123" } person.a = 123
添加函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Person { b?: string , readonly a : string , [propName : string ]: any ; cb :()=> void } const person : Person = { a : "213" , c : "123" , cb :()=> { console .log (123 ) } }
数组类型 数组的类型 类型[ ]
1 2 3 4 5 6 7 8 9 10 11 12 let arr :number [] = [123 ]let arr :number [] = [1 ,2 ,3 ,'1' ]let arr :number [] = [1 ,2 ,3 ,] arr.unshift ('1' ) var arr : number [] = [1 , 2 , 3 ]; var arr2 : string [] = ["1" , "2" ]; var arr3 : any [] = [1 , "2" , true ];
数组泛型 规则 Array<类型>
1 let arr :Array <number > = [1 ,2 ,3 ,4 ,5 ]
用接口表示数组 一般用来描述类数组
1 2 3 4 5 interface NumberArray { [index : number ]: number ; }let fibonacci : NumberArray = [1 , 1 , 2 , 3 , 5 ];
多维数组 1 let data :number [][] = [[1 ,2 ], [3 ,4 ]];
arguments类数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function Arr (...args:any ): void { console .log (arguments ) let arr :number [] = arguments }Arr (111 , 222 , 333 ) function Arr (...args:any ): void { console .log (arguments ) let arr :IArguments = arguments }Arr (111 , 222 , 333 ) interface IArguments { [index : number ]: any ;length : number ;callee : Function ; }
any 在数组中的应用 一个常见的例子数组中可以存在任意类型
1 let list : any [] = ['test' , 1 , [],{a :1 }]
函数扩展 函数的类型 1 2 3 4 5 const fn = (name : string , age :number ): string => { return name + age }fn ('张三' ,18 )
函数的可选参数? 1 2 3 4 5 const fn = (name : string , age?:number ): string => { return name + age }fn ('张三' )
函数参数的默认值 1 2 3 4 const fn = (name : string = "我是默认值" ): string => { return name }fn ()
接口定义函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 interface Add { (num : number , num2 : number ): number } const fn : Add = (num : number , num2 : number ): number => { return num + num2 }fn (5 , 5 ) interface User { name : string ; age : number ; }function getUserInfo (user: User ): User { return user }
定义剩余参数 1 2 3 4 5 6 7 8 const fn = (array :number [],...items :any []):any [] => { console .log (array,items) return items } let a :number [] = [1 ,2 ,3 ] fn (a,'4' ,'5' ,'6' )
函数重载 重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
如果参数类型不同,则参数类型应设置为 any 。
参数数量不同你可以将不同的参数设置为可选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function fn (params: number ): void function fn (params: string , params2: number ): void function fn (params: any , params2?: any ): void { console .log (params) console .log (params2) } fn (123 ) fn ('123' ,456 )
类型断言 | 联合类型 | 交叉类型 联合类型 1 2 3 4 5 6 7 let myPhone : number | string = '010-820' let myPhone : number | string = true
函数使用联合类型
1 2 3 const fn = (something :number | boolean ):boolean => { return !!something }
交叉类型 多种类型的集合,联合对象将具有所联合类型的所有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 interface People { age : number , height: number }interface Man { sex : string }const xiaoman = (man: People & Man ) => { console .log (man.age ) console .log (man.height ) console .log (man.sex ) }xiaoman ({age : 18 ,height : 180 ,sex : 'male' });
类型断言 语法:值 as 类型 或 <类型>值 value as string <string>value
1 2 3 4 5 6 7 8 9 10 11 12 interface A { run : string } interface B { build : string } const fn = (type : A | B): string => { return type .run }
1 2 3 4 5 6 7 8 9 10 11 12 interface A { run : string } interface B { build : string } const fn = (type : A | B): string => { return (type as A).run }
需要注意的是,类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误:
使用any临时断言
1 2 (window as any ).abc = 123
as const
是对字面值的断言 ,与const直接定义常量是有区别的
如果是普通类型跟直接const 声明是一样的
1 2 3 4 5 6 7 8 const names = '小满' names = 'aa' let names2 = '小满' as const names2 = 'aa'
1 2 3 4 5 6 let a1 = [10 , 20 ] as const ;const a2 = [10 , 20 ]; a1.unshift (30 ); a2.unshift (30 );
类型断言是不具影响力的 在下面的例子中,将 something 断言为 boolean 虽然可以通过编译,但是并没有什么用 并不会影响结果, 因为编译过程中会删除类型断言
1 2 3 4 5 6 7 function toBoolean (something: any ): boolean { return something as boolean ; } toBoolean (1 );
内置对象&代码雨 JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
ECMAScript 的内置对象 Boolean
、Number、string
、RegExp
、Date
、Error
1 2 3 4 5 6 7 8 9 10 11 12 let b : Boolean = new Boolean (1 )console .log (b)let n : Number = new Number (true )console .log (n)let s : String = new String ('哔哩哔哩关注小满zs' )console .log (s)let d : Date = new Date ()console .log (d)let r : RegExp = /^1/ console .log (r)let e : Error = new Error ("error!" )console .log (e)
DOM 和 BOM 的内置对象 Document
、HTMLElement
、Event
、NodeList
等
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 let body : HTMLElement = document .body ;let allDiv : NodeList = document .querySelectorAll ('div' );let div :HTMLElement = document .querySelector ('div' ) as HTMLDivElement document .addEventListener ('click' , function (e: MouseEvent ) { });interface HTMLElementTagNameMap { "a" : HTMLAnchorElement ; "abbr" : HTMLElement ; "address" : HTMLElement ; "applet" : HTMLAppletElement ; "area" : HTMLAreaElement ; "article" : HTMLElement ; "aside" : HTMLElement ; "audio" : HTMLAudioElement ; "b" : HTMLElement ; "base" : HTMLBaseElement ; "bdi" : HTMLElement ; "bdo" : HTMLElement ; "blockquote" : HTMLQuoteElement ; "body" : HTMLBodyElement ; "br" : HTMLBRElement ; "button" : HTMLButtonElement ; "canvas" : HTMLCanvasElement ; "caption" : HTMLTableCaptionElement ; "cite" : HTMLElement ; "code" : HTMLElement ; "col" : HTMLTableColElement ; "colgroup" : HTMLTableColElement ; "data" : HTMLDataElement ; "datalist" : HTMLDataListElement ; "dd" : HTMLElement ; "del" : HTMLModElement ; "details" : HTMLDetailsElement ; "dfn" : HTMLElement ; "dialog" : HTMLDialogElement ; "dir" : HTMLDirectoryElement ; "div" : HTMLDivElement ; "dl" : HTMLDListElement ; "dt" : HTMLElement ; "em" : HTMLElement ; "embed" : HTMLEmbedElement ; "fieldset" : HTMLFieldSetElement ; "figcaption" : HTMLElement ; "figure" : HTMLElement ; "font" : HTMLFontElement ; "footer" : HTMLElement ; "form" : HTMLFormElement ; "frame" : HTMLFrameElement ; "frameset" : HTMLFrameSetElement ; "h1" : HTMLHeadingElement ; "h2" : HTMLHeadingElement ; "h3" : HTMLHeadingElement ; "h4" : HTMLHeadingElement ; "h5" : HTMLHeadingElement ; "h6" : HTMLHeadingElement ; "head" : HTMLHeadElement ; "header" : HTMLElement ; "hgroup" : HTMLElement ; "hr" : HTMLHRElement ; "html" : HTMLHtmlElement ; "i" : HTMLElement ; "iframe" : HTMLIFrameElement ; "img" : HTMLImageElement ; "input" : HTMLInputElement ; "ins" : HTMLModElement ; "kbd" : HTMLElement ; "label" : HTMLLabelElement ; "legend" : HTMLLegendElement ; "li" : HTMLLIElement ; "link" : HTMLLinkElement ; "main" : HTMLElement ; "map" : HTMLMapElement ; "mark" : HTMLElement ; "marquee" : HTMLMarqueeElement ; "menu" : HTMLMenuElement ; "meta" : HTMLMetaElement ; "meter" : HTMLMeterElement ; "nav" : HTMLElement ; "noscript" : HTMLElement ; "object" : HTMLObjectElement ; "ol" : HTMLOListElement ; "optgroup" : HTMLOptGroupElement ; "option" : HTMLOptionElement ; "output" : HTMLOutputElement ; "p" : HTMLParagraphElement ; "param" : HTMLParamElement ; "picture" : HTMLPictureElement ; "pre" : HTMLPreElement ; "progress" : HTMLProgressElement ; "q" : HTMLQuoteElement ; "rp" : HTMLElement ; "rt" : HTMLElement ; "ruby" : HTMLElement ; "s" : HTMLElement ; "samp" : HTMLElement ; "script" : HTMLScriptElement ; "section" : HTMLElement ; "select" : HTMLSelectElement ; "slot" : HTMLSlotElement ; "small" : HTMLElement ; "source" : HTMLSourceElement ; "span" : HTMLSpanElement ; "strong" : HTMLElement ; "style" : HTMLStyleElement ; "sub" : HTMLElement ; "summary" : HTMLElement ; "sup" : HTMLElement ; "table" : HTMLTableElement ; "tbody" : HTMLTableSectionElement ; "td" : HTMLTableDataCellElement ; "template" : HTMLTemplateElement ; "textarea" : HTMLTextAreaElement ; "tfoot" : HTMLTableSectionElement ; "th" : HTMLTableHeaderCellElement ; "thead" : HTMLTableSectionElement ; "time" : HTMLTimeElement ; "title" : HTMLTitleElement ; "tr" : HTMLTableRowElement ; "track" : HTMLTrackElement ; "u" : HTMLElement ; "ul" : HTMLUListElement ; "var" : HTMLElement ; "video" : HTMLVideoElement ; "wbr" : HTMLElement ; }
定义Promise 如果我们不指定返回的类型TS是推断不出来返回的是什么类型
指定返回的类型
函数定义返回promise 语法规则:Promise 类型
1 2 3 4 5 6 7 8 9 function promise ( ):Promise <number >{ return new Promise <number >((resolve,reject )=> { resolve (1 ) }) } promise ().then (res => { console .log (res) })
当你在使用一些常用的方法的时候,TypeScript 实际上已经帮你做了很多类型判断的工作了
而他们的定义文件,则在 TypeScript 核心库的定义文件 中
代码雨案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let canvas = document .querySelector ('#canvas' ) as HTMLCanvasElement let ctx = canvas.getContext ('2d' ) as CanvasRenderingContext2D canvas.height = screen.availHeight ; canvas.width = screen.availWidth ; let str : string [] = 'XMZSWSSBXMZSWSSBXMZSWSSBXMZSWSSBXMZSWSSB' .split ('' )let Arr = Array (Math .ceil (canvas.width / 10 )).fill (0 ) console .log (Arr ); const rain = ( ) => { ctx.fillStyle = 'rgba(0,0,0,0.05)' ctx.fillRect (0 , 0 , canvas.width , canvas.height ) ctx.fillStyle = "#0f0" ; Arr .forEach ((item, index ) => { ctx.fillText (str[ Math .floor (Math .random () * str.length ) ], index * 10 , item + 10 ) Arr [index] = item >= canvas.height || item > 10000 * Math .random () ? 0 : item + 10 ; }) console .log (Arr ); }setInterval (rain, 40 )
Class类 ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样。
1 2 3 4 5 6 7 8 9 class Person { constructor () { } run () { } }
定义类
在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明
这样引发了第二个问题你如果了定义了变量不用 也会报错 通常是给个默认值 或者 进行赋值
类的修饰符 public private protected
使用public 修饰符 可以让你定义的变量 内部访问 也可以外部访问 如果不写默认就是public
使用 private 修饰符 代表定义的变量私有的只能在内部访问 不能在外部访问
使用 protected 修饰符 代表定义的变量私有的只能在内部和继承的子类中访问 不能在外部访问
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 class Person { public name :string private age :number protected some :any constructor (name :string ,ages :number ,some :any ) { this .name = name this .age = ages this .some = some } run () { } } class Man extends Person { constructor () { super ("张三" ,18 ,1 ) console .log (this .some ) } create () { console .log (this .some ) } }let xiaoman = new Person ('小满' ,18 ,1 )let man = new Man () man.some
static 静态属性 和 静态方法
我们用static 定义的属性 不可以通过this 去访问 只能通过类名去调用
static 静态函数 同样也是不能通过this 去调用 也是通过类名去调用
需注意: 如果两个函数都是static 静态的是可以通过this互相调用
interface 定义 类
ts interface 定义类 使用关键字 implements 后面跟interface的名字多个用逗号隔开 继承还是用extends
super() 其实是符类的prototype.constructor.call
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 interface PersonClass { get (type : boolean ): boolean } interface PersonClass2 { set ():void , asd :string } class A { name : string constructor ( ) { this .name = "123" } } class Person extends A implements PersonClass ,PersonClass2 { asd : string constructor ( ) { super () this .asd = '123' } get (type :boolean ) { return type } set () { } }
抽象类 应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
或者你也可以把他作为一个基类-> 通过继承一个派生类去实现基类的一些方法
我们看例子
下面这段代码会报错抽象类无法被实例化
1 2 3 4 5 6 abstract class A { public name :string } new A ()
例子2
我们在A类定义了 getName 抽象方法但为实现
我们B类实现了A定义的抽象方法 如不实现就不报错 我们定义的抽象方法必须在派生类实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 abstract class A { name : string constructor (name: string ) { this .name = name; } print (): string { return this .name } abstract getName (): string } class B extends A { constructor ( ) { super ('小满' ) } getName (): string { return this .name } } let b = new B (); console .log (b.getName ());
视频案例
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 interface Options { el : string | HTMLElement } interface VueCls { init (): void options : Options } interface Vnode { tag : string text?: string props?: { id?: number | string key?: number | string | object } children?: Vnode [] } class Dom { constructor ( ) { } private createElement (el : string ): HTMLElement { return document .createElement (el) } protected setText (el: Element, text: string | null ) { el.textContent = text; } protected render (createElement : Vnode ): HTMLElement { const el = this .createElement (createElement.tag ) if (createElement.children && Array .isArray (createElement.children )) { createElement.children .forEach (item => { const child = this .render (item) this .setText (child, item.text ?? null ) el.appendChild (child) }) } else { this .setText (el, createElement.text ?? null ) } return el; } } class Vue extends Dom implements VueCls { options : Options constructor (options: Options ) { super () this .options = options; this .init () } static version () { return '1.0.0' } public init ( ) { let app = typeof this .options .el == 'string' ? document .querySelector (this .options .el ) : this .options .el ; let data : Vnode = { tag : "div" , props : { id : 1 , key : 1 }, children : [ { tag : "div" , text : "子集1" , }, { tag : "div" , text : "子集2" } ] } app?.appendChild (this .render (data)) console .log (app); this .mount (app as Element ) } public mount (app: Element ) { document .body .append (app) } } const v = new Vue ({ el : "#app" })
元组类型 如果需要一个固定大小的不同类型值的集合,我们需要使用元组。
元组就是数组的变种 元组(Tuple)是固定数量的不同类型的元素的组合 。
元组与集合的不同之处在于,元组中的元素类型可以是不同的,而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值,可以把这多个值作为元组返回,而不需要创建额外的类来表示。
1 2 3 4 let arr :[number ,string ] = [1 ,'string' ] let arr2 : readonly [number ,boolean ,string ,undefined ] = [1 ,true ,'sring' ,undefined ]
当赋值或访问一个已知索引的元素时,会得到正确的类型:
1 2 3 4 5 let arr :[number ,string ] = [1 ,'string' ] arr[0 ].length arr[1 ].length
元组类型还可以支持自定义名称和变为可选的
1 let a :[x :number ,y?:boolean ] = [1 ]
越界元素 1 2 3 let arr :[number ,string ] = [1 ,'string' ] arr.push (true )
对于越界的元素他的类型被限制为 联合类型(就是你在元组中定义的类型)如下图
应用场景 例如定义excel返回的数据 1 2 3 4 5 6 7 let excel : [string , string , number , string ][] = [ ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ['title' , 'name' , 1 , '123' ], ]
枚举类型 在javaScript中是没有枚举的概念的TS帮我们定义了枚举这个类型
使用枚举 通过enum关键字定义我们的枚举
数字枚举 例如 红绿蓝 Red = 0 Green = 1 Blue= 2 分别代表红色0 绿色为1 蓝色为2
1 2 3 4 5 enum Types { Red , Green , BLue }
这样写就可以实现应为ts定义的枚举中的每一个组员默认都是从0开始的所以也就是
1 2 3 4 5 6 enum Types { Red = 0 , Green = 1 , BLue = 2 }
增长枚举
1 2 3 4 5 enum Types { Red = 1 , Green , BLue }
如上,我们定义了一个数字枚举, Red使用初始化为 1
。 其余的成员会从 1
开始自动增长。 换句话说, Type.Red
的值为 1
, Green
为 2
, Blue
为 3
。
字符串枚举 字符串枚举的概念很简单。 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
1 2 3 4 5 enum Types { Red = 'red' , Green = 'green' , BLue = 'blue' }
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
异构枚举 枚举可以混合字符串和数字成员
1 2 3 4 enum Types { No = "No" , Yes = 1 , }
接口枚举 定义一个枚举Types 定义一个接口A 他有一个属性red 值为Types.yyds
声明对象的时候要遵循这个规则
1 2 3 4 5 6 7 8 9 10 11 enum Types { yyds, dddd }interface A { red :Types .yyds } let obj :A = { red :Types .yyds }
const
枚举let 和 var 都是不允许的声明只能使用const
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。 为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义
const 声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象
1 2 3 4 const enum Types { No = "No" , Yes = 1 , }
Const 声明编译之后
普通声明编译之后
反向映射 它包含了正向映射( name
-> value
)和反向映射( value
-> name
)
要注意的是 不会 为字符串枚举成员生成反向映射。
1 2 3 4 5 6 7 enum Enum { fall }let a = Enum .fall ;console .log (a); let nameOfA = Enum [a]; console .log (nameOfA);
类型推论|类型别名 什么是类型推论
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论
所以TS帮我推断出来这是一个string类型
不能够在赋值给别的类型
如果你声明变量没有定义类型也没有赋值这时候TS会推断成any类型可以进行任何操作
类型别名 type 关键字(可以给一个类型定义一个名字)多用于复合类型
定义类型别名
1 2 3 4 5 6 type str = string let s :str = "我是小满" console .log (s);
定义函数别名
1 2 3 4 type str = () => string let s : str = () => "我是小满" console .log (s);
定义联合类型别名
1 2 3 4 5 6 7 8 type str = string | number let s : str = 123 let s2 : str = '123' console .log (s,s2);
定义值的别名
1 2 3 4 5 type value = boolean | 0 | '213' let s :value = true
type 和 interface 还是一些区别的 虽然都可以定义类型 1.interface可以继承 type 只能通过 & 交叉类型合并
2.type 可以定义 联合类型 和 可以使用一些操作符 interface不行
3.interface 遇到重名的会合并 type 不行
type高级用法 左边的值会作为右边值的子类型遵循图中上下的包含关系
1 2 3 4 5 6 7 8 9 10 11 type a = 1 extends number ? 1 : 0 type a = 1 extends Number ? 1 : 0 type a = 1 extends Object ? 1 : 0 type a = 1 extends any ? 1 : 0 type a = 1 extends unknow ? 1 : 0 type a = 1 extends never ? 1 : 0
never类型 TypeScript 将使用 never 类型来表示不应该存在的状态
1 2 3 4 5 6 7 8 9 10 11 12 function error (message: string ): never { throw new Error (message); } function loop ( ): never { while (true ) { } }
never 与 void
的差异 1 2 3 4 5 6 7 8 9 function Void ( ):void { console .log (); } function Never ( ):never {throw new Error ('aaa' ) }
差异2 当我们鼠标移上去的时候会发现 只有void和number never在联合类型中会被直接移除
1 type A = void | number | never
never 类型的一个应用场景 举一个我们可能会见到的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type A = '小满' | '大满' | '超大满' function isXiaoMan (value:A ) { switch (value) { case "小满" : break case "大满" : break case "超大满" : break default : const error :never = value; return error } }
比如新来了一个同事他新增了一个篮球,我们必须手动找到所有 switch 代码并处理,否则将有可能引入 BUG 。
而且这将是一个“隐蔽型”的BUG,如果回归面不够广,很难发现此类BUG。
那 TS 有没有办法帮助我们在类型检查阶段发现这个问题呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type A = '小满' | '大满' | '超大满' | "小小满" function isXiaoMan (value:A ) { switch (value) { case "小满" : break case "大满" : break case "超大满" : break default : const error :never = value; return error } }
由于任何类型都不能赋值给 never
类型的变量,所以当存在进入 default
分支的可能性时,TS的类型检查会及时帮我们发现这个问题
symbol类型 自ECMAScript 2015起,symbol
成为了一种新的原生类型,就像number
和string
一样。
symbol
类型的值是通过Symbol
构造函数创建的。
可以传递参做为唯一标识 只支持 string 和 number类型的参数
1 2 let sym1 = Symbol ();let sym2 = Symbol ("key" );
Symbol的值是唯一的 1 2 3 4 5 const s1 = Symbol ()const s2 = Symbol ()console .log (Symbol .for ('xie' ) === Symbol .for ('xie' ))
用作对象属性的键 解决属性重复的key,仿重
1 2 3 4 5 6 7 let sym = Symbol (); let obj = { [sym]: "value" }; console .log (obj[sym]);
使用symbol定义的属性,是不能通过如下方式遍历拿到的 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const symbol1 = Symbol ('666' )const symbol2 = Symbol ('777' )const obj1= { [symbol1]: '小满' , [symbol2]: '二蛋' , age : 19 , sex : '女' }for (const key in obj1) { console .log (key) }Object .keys (obj1)console .log (Object .keys (obj1))console .log (Object .getOwnPropertyNames (obj1))console .log (JSON .stringify (obj1))
如何拿到
1 2 3 4 5 6 Object . (obj1)console .log (Object .getOwnPropertySymbols (obj1))Reflect .ownKeys (obj1)console .log (Reflect .ownKeys (obj1))
Symbol.iterator 迭代器 和 生成器 for of 支持遍历大部分类型迭代器 arr nodeList argumetns set map 等
1 2 3 4 5 6 7 8 9 var arr = [1 ,2 ,3 ,4 ];let iterator = arr[Symbol .iterator ](); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ()); console .log (iterator.next ());
测试用例
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 interface Item { age : number , name : string }const array : Array <Item > = [{ age : 123 , name : "1" }, { age : 123 , name : "2" }, { age : 123 , name : "3" }]type mapTypes = string | number const map :Map <mapTypes,mapTypes> = new Map () map.set ('1' ,'王爷' ) map.set ('2' ,'陆北' )const obj = { aaa :123 , bbb :456 }let set :Set <number > = new Set ([1 ,2 ,3 ,4 ,5 ,6 ])const gen = (erg :any ): void => { let it : Iterator <any > = erg[Symbol .iterator ]() let next :any = { done : false } while (!next.done ) { next = it.next () if (!next.done ) { console .log (next.value ) } } }gen (array)
我们平时开发中不会手动调用iterator 应为 他是有语法糖的就是for of 记住 for of 是不能循环对象的,因为对象没有 iterator
1 2 3 for (let value of map) { console .log (value) }
数组解构的原理其实也是调用迭代器的
1 2 3 var [a,b,c] = [1 ,2 ,3 ]var x = [...xxxx]
那我们可以自己实现一个迭代器让对象支持for of
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 const obj = { max : 5 , current : 0 , [Symbol .iterator ]() { return { max : this .max , current : this .current , next ( ) { if (this .current == this .max ) { return { value : undefined , done : true } } else { return { value : this .current ++, done : false } } } } } }console .log ([...obj])for (let val of obj) { console .log (val); }
以下为这些symbols的列表
方法,会被instanceof运算符调用。构造器对象用来识别一个对象是否是其实例。
Symbol.isConcatSpreadable
布尔值,表示当在一个对象上调用Array.prototype.concat时,这个对象的数组元素是否可展开。
方法,被for-of语句调用。返回对象的默认迭代器。
方法,被String.prototype.match调用。正则表达式用来匹配字符串。
方法,被String.prototype.replace调用。正则表达式用来替换字符串中匹配的子串。
方法,被String.prototype.search调用。正则表达式返回被匹配部分在字符串中的索引。
函数值,为一个构造函数。用来创建派生对象。
方法,被String.prototype.split调用。正则表达式来用分割字符串。
方法,被ToPrimitive抽象操作调用。把对象转换为相应的原始值。
方法,被内置方法Object.prototype.toString调用。返回创建对象时默认的字符串描述。
对象,它自己拥有的属性会被with作用域排除在外。
泛型 泛型在TypeScript 是很重要的东西 例如vue3 是用ts编写的 里面用到了非常多的泛型
函数泛型 我写了两个函数一个是数字类型的函数,另一个是字符串类型的函数,其实就是类型不同,
实现的功能是一样的,这时候我们就可以使用泛型来优化
1 2 3 4 5 6 7 8 function num (a :number ,b :number ) : Array <number > { return [a ,b]; }num (1 ,2 )function str (a :string ,b :string ) : Array <string > { return [a ,b]; }str ('独孤' ,'求败' )
泛型优化
语法为函数名字后面跟一个<参数名> 参数名可以随便写 例如我这儿写了T
当我们使用这个函数的时候把参数的类型传进去就可以了 (也就是动态类型)
1 2 3 4 5 6 function Add <T>(a : T, b : T): Array <T> { return [a,b] } Add <number >(1 ,2 )Add <string >('1' ,'2' )
我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
1 2 3 4 5 6 7 function Sub <T,U>(a :T,b :U):Array <T|U> { const params :Array <T|U> = [a,b] return params } Sub <Boolean ,number >(false ,1 )
定义泛型接口 声明接口的时候 在名字后面加一个<参数>
使用的时候传递类型
1 2 3 4 5 6 7 8 9 10 11 interface MyInter <T> { (arg : T): T } function fn<T>(arg : T): T { return arg } let result : MyInter <number > = fn result (123 )
对象字面量泛型 1 2 3 4 5 6 7 let foo : { <T>(arg : T): T } foo = function <T>(arg :T):T { return arg } foo (123 )
这时候我们就可以使用泛型约束
于是,我们就得对使用的泛型进行约束,我们约束其为具有length
属性的类型,这里我们会用到interface
,代码如下
1 2 3 4 5 6 7 8 9 interface Len { length :number } function getLegnth<T extends Len >(arg :T) { return arg.length } getLegnth<string >('123' )
使用keyof 约束对象 其中使用了TS泛型和泛型约束。首先定义了T类型并使用extends关键字继承object类型的子类型,然后使用keyof操作符获取T类型的所有键,它的返回 类型是联合 类型,最后利用extends关键字约束 K类型必须为keyof T联合类型的子类型
1 2 3 4 5 6 7 8 9 function prop<T, K extends keyof T>(obj : T, key : K) { return obj[key] } let o = { a : 1 , b : 2 , c : 3 } prop (o, 'a' ) prop (o, 'd' )
泛型类 声明方法跟函数类似名称后面定义<类型>
使用的时候确定类型new Sub()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Sub <T>{ attr : T[] = []; add (a :T):T[] { return [a] } } let s = new Sub <number >() s.attr = [1 ,2 ,3 ] s.add (123 ) let str = new Sub <string >() str.attr = ['1' ,'2' ,'3' ] str.add ('123' )
tsconfig.json配置文件 生成tsconfig.json 文件 这个文件是通过tsc --init
命令生成的
配置详解
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 "compilerOptions" : { "incremental" : true , "tsBuildInfoFile" : "./buildFile" , "diagnostics" : true , "target" : "ES5" , "module" : "CommonJS" , "outFile" : "./app.js" , "lib" : ["DOM" , "ES2015" , "ScriptHost" , "ES2019.Array" ], "allowJS" : true , "checkJs" : true , "outDir" : "./dist" , "rootDir" : "./" , "declaration" : true , "declarationDir" : "./file" , "emitDeclarationOnly" : true , "sourceMap" : true , "inlineSourceMap" : true , "declarationMap" : true , "typeRoots" : [], "types" : [], "removeComments" :true , "noEmit" : true , "noEmitOnError" : true , "noEmitHelpers" : true , "importHelpers" : true , "downlevelIteration" : true , "strict" : true , "alwaysStrict" : true , "noImplicitAny" : true , "strictNullChecks" : true , "strictFunctionTypes" : true , "strictPropertyInitialization" : true , "strictBindCallApply" : true , "noImplicitThis" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "noFallthroughCasesInSwitch" : true , "noImplicitReturns" : true , "esModuleInterop" : true , "allowUmdGlobalAccess" : true , "moduleResolution" : "node" , "baseUrl" : "./" , "paths" : { "jquery" : ["node_modules/jquery/dist/jquery.min.js" ] }, "rootDirs" : ["src" ,"out" ], "listEmittedFiles" : true , "listFiles" : true } "include" : [ "src/**/*" ], "exclude" : [ "demo.ts" ], "files" : [ "demo.ts" ]
介绍几个常用的
1.include 指定编译文件默认是编译当前目录下所有的ts文件
2.exclude 指定排除的文件
3.target 指定编译js 的版本例如es5 es6
4.allowJS 是否允许编译js文件
是否在编译过程中删除文件中的注释
6.rootDir 编译文件的目录
7.outDir 输出的目录
8.sourceMap 代码源文件
9.strict 严格模式
10.module 默认common.js 可选es6模式 amd umd 等
namespace命名空间 我们在工作中无法避免全局变量造成的污染,TypeScript提供了namespace 避免这个问题出现
内部模块,主要用于组织代码,避免命名冲突。
命名空间内的类默认私有
通过 export
暴露
通过 namespace
关键字定义
TypeScript与ECMAScript 2015一样,任何包含顶级import
或者export
的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import
或者export
声明,那么它的内容被视为全局可见的(因此对模块也是可见的)
命名空间中通过export
将想要暴露的部分导出
如果不用export 导出是无法读取其值的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 namespace a { export const Time : number = 1000 export const fn = <T>(arg : T): T => { return arg } fn (Time ) } namespace b { export const Time : number = 1000 export const fn = <T>(arg : T): T => { return arg } fn (Time ) } a.Time b.Time
嵌套命名空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 namespace a { export namespace b { export class Vue { parameters : string constructor (parameters: string ) { this .parameters = parameters } } } } let v = a.b .Vue new v ('1' )
抽离命名空间
a.ts
1 2 3 export namespace V { export const a = 1 }
b.ts
1 2 3 import {V} from '../observer/index' console .log (V);
//{a:1}
简化命名空间
1 2 3 4 5 6 7 8 9 namespace A { export namespace B { export const C = 1 } }import X = A.B .C console .log (X);
合并命名空间
重名的命名空间会合并
三斜线指令 三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。
三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。
/// <reference path="..." />
指令是三斜线指令中最常见的一种。 它用于声明文件间的 依赖。
三斜线引用告诉编译器在编译过程中要引入的额外的文件。
你也可以把它理解能import,它可以告诉编译器在编译过程中要引入的额外的文件 例如a.ts
1 2 3 namespace A { export const fn = ( ) => 'a' }
b.ts
1 2 3 namespace A { export const fn2 = ( ) => 'b' }
index.ts
引入之后直接可以使用变量A
声明文件引入
例如,把 /// <reference types="node" />
引入到声明文件,表明这个文件使用了 @types/node/index.d.ts
里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
仅当在你需要写一个d.ts文件时才使用这个指令。
注意事项:
如果你在配置文件 配置了noResolve 或者自身调用自身文件会报错
声明文件d.ts 声明文件 declare 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
1 2 3 4 5 6 7 declare var 声明全局变量declare function 声明全局方法declare class 声明全局类declare enum 声明全局枚举类型declare namespace 声明(含有子属性的)全局对象 interface 和 type 声明全局类型 /// <reference /> 三斜线指令
例如我们有一个express 和 axios
发现express 报错了
让我们去下载他的声明文件
npm install @types/node -D
那为什么axios 没有报错
我们可以去node_modules 下面去找axios 的package json
发现axios已经指定了声明文件 所以没有报错可以直接用
通过语法declare 暴露我们声明的axios 对象
declare const axios: AxiosStatic;
如果有一些第三方包确实没有声明文件我们可以自己去定义
名称.d.ts 创建一个文件去声明
案例手写声明文件 index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import express from 'express' const app = express () const router = express.Router () app.use ('/api' , router) router.get ('/list' , (req, res ) => { res.json ({ code : 200 }) }) app.listen (9001 ,()=> { console .log (9001 ) })
express.d.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 declare module 'express' { interface Router { get (path : string , cb : (req: any , res: any ) => void ): void } interface App { use (path : string , router : any ): void listen (port : number , cb?: () => void ): void } interface Express { (): App Router (): Router } const express : Express export default express }
Mixins混入 TypeScript 混入 Mixins 其实vue也有mixins这个东西 你可以把他看作为合并
1.对象混入 可以使用es6的Object.assign 合并多个对象
此时 people 会被推断成一个交差类型 Name & Age & sex;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Name { name : string }interface Age { age : number }interface Sex { sex : number } let people1 : Name = { name : "小满" }let people2 : Age = { age : 20 }let people3 : Sex = { sex : 1 } const people = Object .assign (people1,people2,people3)
2.类的混入 首先声明两个mixins类 (严格模式要关闭不然编译不过)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class A { type : boolean = false ; changeType ( ) { this .type = !this .type } } class B { name : string = '张三' ; getName (): string { return this .name ; } }
下面创建一个类,结合了这两个mixins
首先应该注意到的是,没使用extends而是使用implements。 把类当成了接口
我们可以这么做来达到目的,为将要mixin进来的属性方法创建出占位属性。 这告诉编译器这些成员在运行时是可用的。 这样就能使用mixin带来的便利,虽说需要提前定义一些占位属性
1 2 3 4 5 6 class C implements A,B{ type :boolean changeType :()=> void ; name : string ; getName :()=> string }
最后,创建这个帮助函数,帮我们做混入操作。 它会遍历mixins上的所有属性,并复制到目标上去,把之前的占位属性替换成真正的实现代码
Object.getOwnPropertyNames()可以获取对象自身的属性,除去他继承来的属性, 对它所有的属性遍历,它是一个数组,遍历一下它所有的属性名
1 2 3 4 5 6 7 8 Mixins (C, [A, B])function Mixins (curCls: any , itemCls: any [] ) { itemCls.forEach (item => { Object .getOwnPropertyNames (item.prototype ).forEach (name => { curCls.prototype [name] = item.prototype [name] }) }) }
装饰器Decorator 它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能
若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json
里启用编译器选项
装饰器 装饰器 是一种特殊类型的声明,它能够被附加到类声明,方法, 访问符,属性或参数上。
首先定义一个类
1 2 3 4 5 class A { constructor ( ) { } }
定义一个类装饰器函数 他会把ClassA的构造函数传入你的watcher函数当做第一个参数
1 2 3 4 5 const watcher : ClassDecorator = (target: Function ) => { target.prototype .getParams = <T>(params : T):T => { return params } }
使用的时候 直接通过@函数名使用
1 2 3 4 5 6 @watcher class A { constructor ( ) { } }
验证
1 2 const a = new A ();console .log ((a as any ).getParams ('123' ));
装饰器工厂 其实也就是一个高阶函数 外层的函数接受值 里层的函数最终接受类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const watcher = (name : string ): ClassDecorator => { return (target: Function ) => { target.prototype .getParams = <T>(params : T): T => { return params } target.prototype .getOptions = (): string => { return name } } } @watcher ('name' )class A { constructor ( ) { } } const a = new A ();console .log ((a as any ).getParams ('123' ));
装饰器组合 就是可以使用多个装饰器
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 const watcher = (name : string ): ClassDecorator => { return (target: Function ) => { target.prototype .getParams = <T>(params : T): T => { return params } target.prototype .getOptions = (): string => { return name } } }const watcher2 = (name : string ): ClassDecorator => { return (target: Function ) => { target.prototype .getNames = ():string => { return name } } } @watcher2 ('name2' )@watcher ('name' )class A { constructor ( ) { } } const a = new A ();console .log ((a as any ).getOptions ());console .log ((a as any ).getNames ());
方法装饰器 返回三个参数
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
成员的属性描述符 。
1 2 3 4 5 6 7 8 9 10 [ {}, 'setParasm' , { value : [Function : setParasm], writable : true , enumerable : false , configurable : true } ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const met :MethodDecorator = (...args ) => { console .log (args); } class A { constructor ( ) { } @met getName ():string { return '小满' } } const a = new A ();
属性装饰器 返回两个参数
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
属性的名字。
[ {}, ‘name’, undefined ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const met :PropertyDecorator = (...args ) => { console .log (args); } class A { @met name :string constructor ( ) { } } const a = new A ();
参数装饰器 返回三个参数
对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。
参数在函数参数列表中的索引。
[ {}, ‘setParasm’, 0 ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const met :ParameterDecorator = (...args ) => { console .log (args); } class A { constructor ( ) { } setParasm (@met name :string = '213' ) { } } const a = new A ();
元数据存储
1 import 'reflect-metadata'
可以快速存储元数据然后在用到的地方取出来 defineMetadata getMetadata
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import axios from 'axios' import 'reflect-metadata' const Base = (base:string ) => { const fn :ClassDecorator = (target ) => { target.prototype .base = base; } return fn } const Get = (url:string ) => { const fn :MethodDecorator = (target:any ,key,descriptor:PropertyDescriptor ) => { axios.get (url).then (res => { const key = Reflect .getMetadata ('key' ,target) descriptor.value (key ? res.data [key] : res.data ) }) } return fn } const result = ( ) => { const fn :ParameterDecorator = (target:any ,key,index ) => { Reflect .defineMetadata ('key' ,'result' ,target) } return fn } const Bt :PropertyDecorator = (target,key ) => { console .log (target,key) } @Base ('/api' )class Http { @Bt xiaoman :string constructor () { this .xiaoman = 'xiaoman' } @Get ('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10' ) getList (@result () data :any ) { } create () { } } const http = new Http () as any