这篇文章涵盖了 TypeScript 的基础和高级特性。首先介绍了 TypeScript
的八种内置基础类型,以及 any
、unknown
类型的使用。接着,深入讲解了接口、对象类型、泛型、函数扩展、联合类型等内容,并介绍了如何通过
type
和 interface
区分类型别名。文章还详细说明了 TypeScript
的类型推论、枚举类型、元组、符号类型等。之后,内容转向高级用法,介绍了装饰器、命名空间、Mixins
等概念,并提供了 TypeScript 项目构建的实用工具,如 Rollup 和 Webpack
配置,最后讨论了 TypeScript 的进阶模式和工具类型。
基础类型(TS – 1)
全局安装 TSnpm install -g typescript
检查是否安装成功tsc -v
初始化生成配件文件tsc --init
设置实时 TS 代码转换成 JS
代码:shift+ctrl+B
选择监视
然后写的 TS 编译成 js,使用 node 在终端运行转换成功的 js 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let str : string = "这是字符串类型" ;let str : string = 666 ;let muban : string = `web${str} ` ;let u : void = undefined ;let u : void = null ;function fnvoid ( ): void { return ; }
八种内置类型
1 2 3 4 5 6 7 8 let str : string = "jimmy" ;let num : number = 24 ;let bool : boolean = false ; let u : undefined = undefined ;let n : null = null ;let obj : object = { x : 1 };let big : bigint = 100n ;let sym : symbol = Symbol ("me" );
注意点
null 和 undefined
默认情况下 null
和 undefined
是所有类型的子类型。 就是说你可以把 null
和
undefined
赋值给其他类型。
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 let str : string = "666" ;str = null ; str = undefined ; let num : number = 666 ;num = null ; num = undefined ; let obj : object = {};obj = null ; obj = undefined ; let sym : symbol = Symbol ("me" );sym = null ; sym = undefined ; let isDone : boolean = false ;isDone = null ; isDone = undefined ; let big : bigint = 100n ;big = null ; big = undefined ;
如果你在 tsconfig.json 指定了 "strictNullChecks":true
,null
和 undefined
只能赋值给
void
和它们各自的类型。
number 和 bigint
虽然 number
和 bigint
都表示数字,但是这两个类型不兼容。
1 2 3 4 let big : bigint = 100n ;let num : number = 6 ;big = num; num = big;
会抛出一个类型不兼容的 ts (2322) 错误。
任意类型(TS – 2)
1 2 npm install @types/node -D npm install ts-node -g (装全局的)
类型(任意值) – any
1 2 3 4 5 6 7 let anys : any = "小满穿黑丝" ;anys = []; anys = 18 ; anys = {}; anys = Symbol ("666" );
作用的地方:
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。
这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。
这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。
那么我们可以使用 any
类型来标记这些变量
在对现有代码进行改写的时候,any
类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。
你可能认为 Object
有相似的作用,就像它在其它语言中那样。
但是 Object
类型的变量只是允许你给它赋任意值 -
但是却不能够在它上面调用任意的方法,即便它真的有这些方法
当你只知道一部分数据的类型时,any
类型也是有用的。
比如,你有一个数组,它包含了不同的类型的数据
unknown 类型
unknown 类型比 any 类型更安全
就像所有类型都可以被归为 any
,所有类型也都可以被归为
unknown
。这使得 unknown
成为 TypeScript
类型系统的另一种顶级类型(另一种的any
)
1 2 3 let unknow : unknown = { a : (): number => 123 };unknow.a ();
接口和对象类型(TS – 3)
在 typescript
中,我们定义对象的方式要用关键字
interface (接口),小满的理解是使用
interface
来定义一种约束,让数据的结构满足约束的格式。
我的理解是 interface
是一个国企部门只招一个人的话,他们会针对走后门的那个人量身定制招聘要求,到面试的时候,这些条件少一个都不行,多了也不行,毕竟已经内定了,再叼、这些条件不仅满足了而且还会更多的技能也没用,别人就是不要你。(留下心酸的眼泪)
interface 类型
1 2 3 4 5 6 7 8 9 interface A{ readonly name :string age?:number } let obj :A = { name :"小满嗷嗷叫" , age :18 }
注意:这个规定的属性不能多也不能少,参考我上面的案例
可选属性 – ?
操作符
1 2 3 4 5 6 7 8 9 interface A{ readonly name :string age?:number } let obj :A = { name :"小满嗷嗷叫" age :18 }
任意属性 – [propName:string]
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
1 2 3 4 5 6 7 8 interface Person { name :string, age?:number, [propName :string]:string|number 注意string与number中间的 `|` 符号,小飞棍来咯,这是联合类型,后面笔记会写,这里就当作将string和number类型关系到了一块,有点像逻辑或,满足联合起来的其中一个条件都行,两个也可以 }
只读属性 – readonly
只读属性必须在声明时或构造函数里被初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 interface A{ readonly name :string } let obj :A = { name :"小满嗷嗷叫" } obj.name = "小满芳龄18" console .log (obj)let name1 = obj.name console .log (name1)
继承属性 – extends
儿子在前面,父亲在后面。也就是说顺序是 儿子 继承于 父亲
父亲的部分会继承给儿子,父亲的部分如果没有使用?操作符的话,引用儿子的
对象
是必须将父亲的部分都写下去。一说到这个就想到现在有的地方买房子,出政策能够绑定
3
代人一起还款,父债子还,跑不掉的,连债务都继承了还不能摆脱,这政策太鸡儿黑心了,绝户计
1 2 3 4 5 6 7 8 9 10 11 12 interface A { name : string; } interface B extends A { age : number; } let p : B = { name : "有看到小满的裤子吗?" , age : 88 , };
Object 与 object{} – 加餐环节
前置知识点补充
原始数据类型(基础数据类型)
中文称呼
引用数据类型
中文称呼
Undefined
未定义
{}
对象
Null
空值
function
函数
Boolean
布尔值
[]
数组
Number
数字
String
字符串
存储位置不同
原始数据类型
:直接存储在栈(stack)中的简单数据段,占据空间小,大小固定,属于被频繁使用的数据,所以存储在栈中;
引用数据类型
:存储在堆(heap)中的对象,占据空间大,大小不固定,如果存储在栈中,将会影响程序运行的性能。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后,从堆中获得实体。
传值方式不同
不可变 (immutable) 性质:
基本类型是不可变的 (immutable),只有对象是可变的 (mutable).
有时我们会尝试 “改变” 字符串的内容,但在 JS 中,任何看似对 string 值的
“修改” 操作,实际都是创建新的 string
值。任何方法都无法改变一个基本类型的值(在下面的字面量类型中会再次强调)
引用类型的值是可变的
引用类型的值是同时保存在栈内存和堆内存中的对象。javascript
和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作啥呢?
实际上,是操作对象的引用,引用类型的值是按引用访问的。
object
、Object
以及
{} 这三个类型(第三个类型为空对象字面量模式)大家可能不太理解
这集加餐环节就是进行补充,一个冷门但是不邪门的知识点
Object 类型
1 2 3 4 5 6 7 8 9 10 11 let a : Object = 123 ;let b : Object = "小满今天没穿裤子" ;let c : Object = [1314 , 520 ];let d : Object = { name : "草莓" , sex : "女" , address : "小满微信一群" };let e : Object = () => "学姐贴贴" ;
Object
类型是所有 Object
类的实例的类型。
由以下两个接口来定义:
Object
接口定义了 Object.prototype
原型对象上的属性;
ObjectConstructor
接口定义了 Object 类的属性,
如上面提到的 Object.create()
。
object 类型
object 代表所有非值类型(非原始类型)的类型,例如 数组 对象
函数等,常用于泛型约束
所有原始类型都不支持,所有引用类型都支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 let f : object = "努力会获得回报的" ;let g : object = 123 ;let h : object = true ;let i : object = [123 , "学姐学习Vue3" , true ];let j : object = { name : "小满" , identity : ["B站UP主" , "二次元" , "京东员工" , "全栈开发工程师" ], sex : "女" , }; let k : object = () => "不要对自己pua,相信自己是最棒的,尊重自己,人生更精彩" ;
{}字面量类型
看起来很别扭的一个东西 你可以把他理解成 new Object 就和我们的第一个
Object 基本一样 包含所有类型
数组类型(TS – 4)
普通的声明方式
1 2 3 4 5 6 7 8 9 10 11 12 13 let arr : number[] = [123 ];let arr : number[] = [1 , 2 , 3 , "1" ];let arr : number[] = [1 , 2 , 3 ];let arr : number[] = [1 , 2 , 3 , 4 ]; let arr2 : string[] = ["1" , "2" , "3" , "4" ]; let arr3 : any[] = [1 , "2" , true , undefined , [], {}]; let arr4 : number[][][] = [[[]], [[]], [[]]];
泛型 – Array <类型>
规则 Array <类型>
1 2 3 4 5 6 let arr1 : Array <number> = [1 , 2 , 3 , 4 , 5 ];let arr2 : Array <string> = ["1,2,3,4,5" ];let arr3 : Array <boolean> = [true ];let arr4 : Array <Array <number>> = [[123 ], [456 ]];
类数组 – arguments
是所有参数的一个集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function Arr (...args: any ): void { console .log (arguments ); let arr1 : IArguments = arguments ; interface IArguments { [index : number]: any; length : number; callee : Function ; } } Arr (4 , 56 , 789 );
接口表示数组
一般用来描述类数组
1 2 3 4 5 6 7 interface ArrNumber { [index : number]: number; } let Arr : ArrNumber = [1 , 2 , 3 , 4 , 5 ];
函数扩展(TS – 5)
函数内参数类型也是可以定义的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const fn = (name :string,age :number):string => { return name + age } let a = fn ('小满' ,10000 )console .log (a)-------------------------------------------------------------------- const fn = (name :string,age :number = 666 ):string => { return name + age } let a = fn ('小满' )console .log (a)-------------------------------------------------------------------- const fn = (name :string,age?:number = 666 ):string => { return name + age } let a = fn ('小满穿女仆装' )console .log (a)
对象形式的定义
跟定义对象差不多,但是在针对多个参数的时候会更加的方便,且记得引用的时候要写成({xxxx})形式,不然会报错,输出的是数组形式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface User { name : string; age : number; } const fn = (user : User ): User => { return user; }; let a = fn ({ name : "'小满" , age : 18 , }); console .log (a);
函数重载
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
如果参数类型不同,则参数类型应设置为 any 。
参数数量不同你可以将不同的参数设置为可选。
为了让编译器能够选择正确的检查类型,它与 JavaScript
里的处理流程相似。 它查找重载列表,尝试使用第一个重载定义。
如果匹配的话就使用这个。
因此,在定义重载的时候,一定要把最精确的定义放在最前面。
1 2 3 4 5 6 7 8 9 10 11 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) } let a = fn (1 )let a = fn ("1" ,1 )
联合类型|类型断言|交叉类型(TS –
6)
联合类型
联合类型能够让我们可选我们自己需要的类型部分,如果需要的类型超过或者达到
2 个,那就可以使用。
那为什么不使用 any
呢?那是因为我们需要的并不是所有类型都能通过,我只希望这两个或者 3
个类型能够通过,如果需要的类型超过或着达到两个都使用 any 的话,那就和
JavaScript 原生没有区别了
1 2 3 4 5 6 let myPhone : number | string = "010-820" ;let myPhone : number | string = true ;
函数使用联合类型
这个!!是怎么回事呢?
我们知道一串数字想变成字符串只要加上”“就能隐式转换成字符串。
那一个类型只要!
就能进行反转,!只有正反,也就是 false 跟
true,这种就有点类似隐式转换了,我们连续转两次就相当于当前形式的布尔值类型了
1 2 3 4 5 6 7 8 9 10 let fn = function (type:number ):boolean { return !!type } -------------------------------------------------------------------- let fn = function (type:number|boolean ):boolean { return !!type } let result = fn (1 )console .log (result);
交叉类型
多种类型的集合,联合对象将具有所联合类型的所有成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Pople { name :string age :number } interface Man { sex :number } const xiaoman = (man :Pople & Man ):void => { console .log (man) } xiaoman ({ name :"小满今天坏掉了" , age :18 , sex :1 })
类型断言
语法格式,值 as 类型 或者 <类型>值
需要注意的是,类型断言只能够「欺骗」TypeScript
编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误
覆盖它的推断,并且能以你任何你想要的方式分析它,这种机制被称为「类型断言」。TypeScript
类型断言用来告诉编译器你比它更了解这个类型,并且它不应该再发出错误
当 S
类型是 T
类型的子集,或者
T
类型是 S
类型的子集时,S
能被成功断言成
T
。这是为了在进行类型断言时提供额外的安全性,完全毫无根据的断言是危险的,如果你想这么做,你可以使用
any
。
2、类型断言的用途
(1)将一个联合类型推断为其中一个类型
(2)将一个父类断言为更加具体的子类
(3)将任何一个类型断言为 any
(4)将 any 断言为一个具体的类型
1 2 3 4 5 6 7 8 9 10 11 12 原型: let fn = function (num:number | string ):void { console .log (num.length ); } fn ("12345" )-------------------------------------------------------- 断言写法 let fn = function (num:number | string ):void { console .log ((num as string).length ); } fn ("12345" )fn (12345 )
另一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface A{ run :string } interface B{ build :string } let fn = (type:A | B ) =>{ console .log ((<A>type).run ); } fn ({ build :"123" })
临时断言
1.使用 any 临时断言
1 2 3 4 window .abc = 123 (window as any).abc = 123
在下面的例子中,将 something 断言为 boolean
虽然可以通过编译,但是并没有什么用
并不会影响结果,因为编译过程中会删除类型断言
1 2 3 4 5 6 7 8 function toBoolean (something: any ): boolean { return something as boolean; } let bbb = toBoolean (1 );console .log (bbb)
as const
是对字面值的断言 ,与 const
直接定义常量是有区别的
如果是普通类型跟直接 const 声明是一样的
1 2 3 4 5 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 );
内置对象(TS – 7)
ECMAScript 的内置对象
JavaScript 中有很多内置对象,它们可以直接在
TypeScript
中当做定义好了的类型。
Boolean
、Number、string
、RegExp
、Date
、Error
1 2 3 4 5 const regexp : Regexp = /\w\d\s/ ; const date : Date = new Date (); const error : Error = new 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 ("小满今天穿白丝" );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 const list : NodeList = document .querySelectorAll ("#list li" );console .log (list);const body : HTMLElement = document .body ;console .log (body);const div : HTMLDivElement = document .querySelector ("div" );console .log (div);document .body .addEventListener ("click" , (e: MouseEvent ) => { console .log (e); }); function promise1 ( ): Promise <number> { return ( new Promise () < number > ((resolve, rejects ) => { resolve (1 ); }) ); } promise ().then (res => { console .log (res); });
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 ; }
Class 类(TS – 8)
ES6
提供了更接近传统语言的写法,引入了
Class(类)这个概念,作为对象的模板。通过 class
关键字,可以定义类。基本上,ES6 的 class
可以看作只是一个语法糖
,它的绝大部分功能,ES5
都可以做到,新的 class
写法只是让对象原型的写法更加清晰、更像面向对象
编程的语法而已。上面的代码用
ES6 的 “类” 改写
JavaScript 写法
1 2 3 4 5 6 7 8 9 10 class Person { constructor (name: string, age: number, sub: boolean ) { this .name = name; this .age = age; this .sub = sub; } } new Person ("小满" , 22 , false );
TypeScript 写法
1 2 3 4 5 6 7 8 9 10 11 12 13 class Person { name : string; age : number; sub : boolean; constructor (name: string, age: number, sub: boolean ) { this .name = name; this .age = age; this .sub = sub; } } new Person ("小满" , 22 , false );
public
public 内部外部都可以访问,如果定义了 public,像 p 就能够访问
constructor 内部的变量了。当然,默认情况下也是 public
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { public name :string public age :number public sub :boolean constructor (name:string,age:number,sub:boolean ) { this .name = name this .age = age this .sub = sub } } let p = new Person ("小满" ,22 ,false )p.age p.name p.sub
private
private 私有变量只能在内部访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Person { private name :string private age :number private sub :boolean constructor (name:string,age:number,sub:boolean ) { this .name = name this .age = age this .sub = sub } } let p = new Person ("小满" ,22 ,false )p.age p.name p.sub
protected
protected 内部和子类中访问
provate 跟 protectd
他们的区别是一个是只能在内部使用,一个是内部与子类访问,例子如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Person { protected name :string private age :number public sub :boolean constructor (name:string,age:number,sub:boolean ) { this .name = name this .age = age this .sub = sub } } class Man extends Person { constructor ( ){ super ("小满" ,22 ,false ) this .name this .sub } } let p = new Person ("小满" ,22 ,false )p.age p.name p.sub
static 静态属性 和 静态方法
静态代码块的作用 :
一般情况下,有些代码需要在项目启动的时候就执行,这时候就需要静态代码块,比如一个项目启动需要加载配置文件,或初始化内容等。
静态代码块不能出现在任何方法体内
对于普通方法:普通方法是需要加载类 new
出一个实例化对象,通过运行这个对象才能运行代码块,而静态方法随着类加载就运行了。
对于静态方法:在类加载时静态方法也加载了,但是必须需要类名或者对象名才可以访问,相比于静态代码块,静态方法是被动运行,而静态代码块是主动运行
静态代码块不能访问普通变量
普通变量只能通过对象调用的,所以普通变量不能放在静态代码块中。
普通代码块和构造代码块
静态代码块和构造代码块在声明上少一个 static
关键字
执行时机 :
构造代码块在创建对象时被调用,每次创建对象都会调用一次,且优先于构造函数执行。
注:不是优先于构造函数执行,而是依托于构造函数,如果不创建对象就不会执行构造代码块
普通代码块和构造代码块的区别在于 ,构造代码块是在类中定于的,而普通代码块是在方法体中定义的,执行顺序和书写顺序一致。
执行顺序
**静态代码块 > 构造代码块 > 构造函数 > 普通代码块**
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 class Person { protected name :string private age :number public sub :boolean static aaa :string = '123456' constructor (name:string,age:number,sub:boolean ) { this .name = name this .age = age this .sub = sub this .run () Person .run () } static run (){ this .dev () this .aaa return '789' } static dev ( ){ this .aaa return 'dev' } } console .log (Person .run ())Person .aaa console .log (Person .aaa )let p = new Person ("小满" ,22 ,false )
interface 定义 类
ts interface 定义类 使用关键字 implements 后面跟 interface
的名字多个用逗号隔开 继承还是用 extends
通过接口去约束类
1 2 3 4 5 6 7 interface Person { run (type : boolean): boolean; } class Man implements Person { }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Person { run (type : boolean): boolean; } interface H { set (): void ; } class Man implements Person , H { run (type : boolean): boolean { return type; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 interface Person { run (type : boolean): boolean; } interface H { set (): void ; } class A { params : string; constructor (params: string ) { this .params = params; } } class Man extends A implements Person , H { run (type : boolean): boolean { return type; } set ( ) { } }
抽象类(TypeScript8)
用关键词abstract
修饰的类称为 abstract
类(抽象类 )
应用场景如果你写的类实例化之后毫无用处此时我可以把他定义为抽象类
或者你也可以把他作为一个基类 ->
通过继承一个派生类 去实现基类的一些方法
对于 abstract
方法只允许声明,不允许实现(因为没有方法体)(毕竟叫抽象,当然不能实实在在的让你实现),并且不允许使用
final 和 abstract 同时修饰一个方法或者类,也不允许使用 static 修饰
abstract 方法。也就是说,abstract 方法只能是实例方法,不能是类方法。
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 abstract class A { name :string construct (name:string ){ this .name = name } setName (name:string ){ this .name = name } abstract getName ():string } class B extends A { constructor ( ){ super ('小满' ) } getName ():string{ return this .name } } let b = new Bb.setName ("小满2" ) console .log (b.getName ())
元组类型(TS – 9)
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
1 2 3 4 let arr : [string, number] = ["小满" , 22 ]; arr[0 ].length ; arr[1 ].length ;
越界的元组
当添加的元组越界的时候,越界的类型会被限制为元组类型中每个类型的联合类型
1 2 3 4 5 6 let arr : [string, number] = ["小满" , 22 ]; arr.push (true ); arr.push ("111" , 2222 );
枚举类型(TS – 10)
在 javaScript 中是没有枚举
的概念的 TS
帮我们定义了枚举这个类型
enum 关键字定义枚举
数字定义枚举
默认从 0 开始的
1 2 3 4 5 6 enum Color { red, green, blue } console .log (Color .red ,Color .blue ,Color .green )
增长枚举
能够通过自定义开头决定从哪个数字开始枚举,其他位置的都可以定义,后面的数字就按顺序枚举
1 2 3 4 5 6 enum Color { red=2 , green, blue } console .log (Color .red ,Color .blue ,Color .green )
字符串枚举
字符串枚举的概念很简单。
在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。
换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的
-
它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
1 2 3 4 5 enum Types { Red = 'red' , Green = 'green' , BLue = 'blue' }
异构枚举
枚举可以混合字符串和数字成员
1 2 3 4 5 enum Types { No = "No" , Yes = 1 , } console .log (Types .NO ,Types .Yes )
接口枚举
定义一个枚举 Types 定义一个接口 A 他有一个属性 red 值为
Types.yyds
声明对象的时候要遵循这个规则
1 2 3 4 5 6 7 8 9 10 11 12 13 enum Color { no = "NO" , yes = 1 } interface A{ red :Color .yes } let B :A = { red :Color .yes }
const 枚举
let 和 var 都是不允许的声明只能使用 const
大多数情况下,枚举是十分有效的方案。 然而在某些情况下需求很严格。
为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用
const
枚举。 常量枚举通过在枚举上使用 const
修饰符来定义
const 声明的枚举会被编译成常量
普通声明的枚举编译完后是个对象
1 2 3 4 5 6 7 8 const enum Types { sucess, fail } let code :number = 0 if (code === Types .sucess ){ console .log ("我在人民广场吃炸鸡" ) }
反向映射
它包含了正向映射( name
->
value
)和反向映射( value
->
name
)
要注意的是 不会 为字符串枚举成员生成反向映射。
1 2 3 4 5 6 enum Types { one } let success :number = Types .one console .log (one)
1 2 3 4 5 6 7 8 enum Types { success } let success :number = Types .success let key = Types [success]console .log (`value---${success} ` ,`key----${key} ` )
类型推论|类型别名(TS –
11)代号:幼儿园
类型推论
我声明了一个变量但是没有定义类型
TypeScript
会在没有明确的指定类型的时候推测出一个类型,这就是类型推论(TS
本身支持的功能)
1 2 let str = "小满" ;str = 123 ;
如果你声明变量没有定义类型也没有赋值这时候 TS 会推断成 any
类型可以进行任何操作
1 2 3 4 5 let str; str = 123 ; str = "马杀鸡" ; str = false ; str = [];
联合类型
指定多种类型,在前文有提到
1 2 3 type s = string | number; let str : s = "永恒的紫罗兰花园" ;let num : s = 520 ;
函数式的类型别名
type
关键字(可以给一个类型定义一个名字)多用于符合类型 ,但也可以要求有固定的东西
定义类型别名
1 2 3 4 5 type str = string; let s : str = "我是小满" ;console .log (s);
定义函数别名
1 2 3 4 5 type str = () => string; let s : str = () => "我是小满" ;console .log (s);
定义联合类型别名
1 2 3 4 5 6 7 type str = string | number; let s : str = 123 ;let s2 : str = "123" ;console .log (s, s2);
定义值的别名
1 2 3 4 type value = boolean | 0 | "213" ; let s : value = true ;
类型别名 type 跟 interface
的区别
区别 1:interface 是可以继承 type 的,但是 type 是没办法继承
interface 的,只能够使用&
进行合并
区别 2:interface
是没办法写联合类型的,必须要里面写一个属性才能够去写联合类型(不能直接在外面添加联合类型),而
type 能够直接写联合类型
区别 3:interface 重复写是会合并在一起的,type 不行
1 2 3 4 5 6 7 type s = number[] & B; interface A extends B { } interface B {}
1 2 3 4 5 6 7 type s = number[] | string; interface A extends B { name : string | number; } interface B {}
1 2 3 4 5 6 7 interface A { name : string | number; } interface A { age : number; }
image-20230209010245171
类型别名高阶用法
extends :包含的意思
左边的值 会作为右边类型的子类型
1 2 3 4 5 6 type num = 1 extends number ? 1 : 0
image-20230209010955388
never 类型(TS – 12)
TypeScript 将使用 never 类型来表示不应该存在的状态
返回 never 的函数必须存在无法达到的终点
1 2 3 4 5 6 7 8 9 10 function error (message: string ): never { throw new Error (message); } function loop ( ): never { while (true ) { } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 interface A{ type :"保安" } interface B{ type :"草莓" } interface C{ type :"卷心菜" } type All = A|B function type (val:All ){ while (val.type ){ case "保安" :break case "草莓" :break default : const check :never = val break } }
Symbol 类型
symbol
是一种新的原生类型,就像 number
和
string
一样
symbol类型的值是通过
Symbol 构造函数创建的
可以传递参做为唯一标识 只支持 string 和 number 类型的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let s : symbol = Symbol ("小满" );let num : symbol = Symbol ("小满" );let obj = { [num]: "value" , [s]: "草莓" , name : "小满" , sex : "男" , }; console .log (obj[num]); console .log (s, num); console .log (s === num); for (let key in obj) { console .log (key); } console .log (Object .keys (obj)); console .log (Object .getOwnPropertyNames (obj)); console .log (JSON .stringify (obj));
能够读取到 Symbol 的两种方式
静态方法 Reflect.ownKeys()
返回一个由目标对象自身的属性键组成的数组(Array)。
语法:Reflect.ownKeys(target) =>
target
获取自身属性键的目标对象。
Reflect.ownKeys
方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于
Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
1 2 3 4 console .log (Object .getOwnPropertySymbols (obj)); Reflect .ownKeys (); console .log (Reflect .ownKeys (obj));
never 类型(TS – 12)|重做
never
类型通常用于表示不可能出现的情况,它可以用于增强代码的类型安全性和可读性。
当一个变量被推断为never
类型时,表示该变量的类型不能是任何其他类型,即不存在任何值与其兼容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type A = string & number; function xy ( ): never { throw new Error ("小余" ); while (true ) { } }
never 是底层的内容,所以在联合类型上面会有问题
1 type A = void | number | never;
image-20230223163056013
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type A = "唱" | "跳" | "rap" ; function ikun (value: A ) { switch (value) { case "唱" : break ; case "跳" : break ; case "rap" : break ; default : const error : never = value; break ; } }
迭代器|生成器(TS – 13)
迭代器:Symbol.Iterator
迭代器(Iterator)是⼀种对象,它能够⽤来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址
通俗点说,迭代器表现的像指针,读取集合或者数组中的⼀个值,读完以后⼜指向下⼀条数据,⼀个个数过去。
生成器: for of
迭代器
迭代器
Interator 的用法
1.Interator
是 es6 引入的一种新的遍历机制。两个核心:
迭代器是一个统一的接口,它的作用是使各种数据结构
可被便捷的访问,它是通过一个键为
Symbol.Iterator 的方法来实现。
迭代器是用于遍历
数据结构元素的指针(如数据库中的游标)。
使用迭代
使用Symbol.interator
创建一个迭代器
调用 next 方法向下迭代,next 方法会返回当前的位置
当 done 为 true 时则遍历结束
注意点:
在迭代器迭代元素的过程中,不允许使⽤集合对象改变集合中的元素个数,如果需要添加或者删除只能使⽤迭代器的⽅法操作。
如果使⽤了集合对象改变集合中的元素个数那么就会报错:不改变个数即可,替换也可以的。
迭代器的⽣存周期为创建到使⽤结束的时段。
foreach : Iterator 的封装变形,变得⽐ Iterator
更简单。但是他也需要知道数组或集合的类型。并且,Iterator
需要注意的,foreach 同样需要注意。
存在 interator 迭代器的有 =>
数组[]里能够找到 Symbol(Symbol.interator)
argument 内找到 Symbol(Symbol.interator)
NodeList 内找到 Symbol(Symbol.interator)
new set()内的 Prototype 下一层 Symbol(Symbol.interator)
new Map 同理,一样有 Symbol(Symbol.interator)
1 2 3 4 5 6 7 8 9 let arr : Array <number> = [1 , 5 , 6 ];let it : Intertor <number> = arr[Symbol .interator ](); 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 type mapKeys = string | number; let set : Set <number> = new Set ([1 , 2 , 3 ]);let map : Map <mapKeys, mapKeys> = new Map (); map.set ("1" , "小满" ); map.set ("2" , "看看腿" ); function gen (erg: any ) { let it : interator<any> = erg[Symbol .interator ](); let next : any = { done : false }; while (!next.done ) { next = it.next (); if (!next.done ) { console .log (next); } } } gen (arr);
Symbol 列表
Symbol.hasInstance 方法,会被 instanceof
运算符调用。构造器对象用来识别一个对象是否是其实例。
Symbol.isConcatSpreadable
布尔值,表示当在一个对象上调用 Array.prototype.concat
时,这个对象的数组元素是否可展开。
Symbol.Iterator 方法,被 for-of
语句调用。返回对象的默认迭代器。
Symbol.match 方法,被 String.prototype.match
调用。正则表达式用来匹配字符串 。
Symbol.replace 方法,被 String.prototype.replace
调用。正则表达式用来替换字符串中匹配的子串。
Symbol.search 方法,被 String.prototype.search
调用。正则表达式返回被匹配部分在字符串中的索引。
Symbol.species
函数值,为一个构造函数。用来创建派生对象。
Symbol.split 方法,被 String.prototype.split
调用。正则表达式来用分割字符串。
Symbol.toPrimitive 方法,被 ToPrimitive
抽象操作调用。把对象转换为相应的原始值。
Symbol.toStringTag 方法,被内置方法
Object.prototype.toString 调用。返回创建对象时默认的字符串描述。
Symbol.unscopables 对象,它自己拥有的属性会被 with
作用域排除在外。
生成器(Builder)
又称建造者模式,该模式是一种创建型设计模式,能够分步骤创建复杂对象。该模式允许使用相同的创建代码生成不同类型和形式的对象。
具体内容请跳转链接查看:设计模式-生成器
for…of 语句
for…of 会遍历可迭代的对象,调用对象上的
Symbol.Iterator
方法。(此对象非彼对象,这个对象是指你即将下手的目标)
对象也是不支持的,因为对象没用Symbol.Iterator
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type mapKeys = string | number; let set : Set <number> = new Set ([1 , 2 , 3 ]);let map : Map <mapKeys, mapKeys> = new Map (); map.set ("1" , "小满" ); map.set ("2" , "看看腿" ); for (let item of set) { console .log (item); } for (let item of arr) { console .log (item); } for (let item of map) { console .log (item); }
跟 for in 的区别
for in 循环出来的是索引而不是内容,这个应该是最本质的区别了
因为 for of 会调用底层 interator 里面那个 list 的.value
泛型(generic) => (TS – 14 上)
泛型简单来说就是类型变量,在 ts 中存在类型,如
number、string、boolean
等。泛型就是使用一个类型变量来表示一种类型,类型值通常是在使用的时候才会设置。泛型的使用场景非常多,可以在函数、类、interface
接口中使用
TypeScript 中不建议使用 any
类型,不能保证类型安全,调试时缺乏完整的信息。
TypeScript
可以使用泛型来创建可重用的组件。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活,可以在编译时发现类型错误,从而保证了类型安全。
无泛型用法
1 2 3 4 5 6 7 8 9 10 11 12 function num (A: number, B: number ): Array <number> { return [A, B]; } num (6 , 9 );function str (A: string, B: string ): Array <string> { return [A, B]; } str ("小满" , "穿女装" );
一个笨的方法就像上面那样,也就是说 JS
提供多少种类型,就需要复制多少份代码,然后改下类型签名。这对程序员来说是致命的。这种复制粘贴增加了出错的概率,使得代码难以维护,牵一发而动全身。并且将来
JS
新增新的类型,你仍然需要修改代码,也就是说你的代码对修改开放 ,这样不好。
如果你使用 any 的话,怎么写都是 ok 的,
这就丧失了类型检查的效果。实际上我知道我传给你的是
string,返回来的也一定是 string,而 string 上没有 toFixed
方法,因此需要报错才是我想要的。也就是说我真正想要的效果是:当我用到id的时候,你根据我传给你的类型进行推导
。比如我传入的是
string,但是使用了 number 上的方法,你就应该报错。
使用泛型优化
为了解决上面的这些问题,我们使用泛型对上面的代码进行重构 。和我们的定义不同,这里用了一个
类型 T,这个 T
是一个抽象类型,只有在调用的时候才确定它的值 ,这就不用我们复制粘贴无数份代码了。
其中 T
代表
Type ,在定义泛型时通常用作第一个类型变量名称。但实际上
T
可以用任何有效名称代替。除了 T
之外,以下是常见泛型变量代表的意思:
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 function add<T>(a : T, b : T): Array <T> { return [a, b]; } add < number > (1 , 2 ); add < string > ("1" , "2" ); add (1 , 2 );add ("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
参数,但是,有的数据类型是没有 length
属性的
1 2 3 function getLegnth<T>(arg : T) { return arg.length ; }
1 2 3 4 5 6 7 8 9 10 interface Len { length :number } function getLegth<T extends Len >(arg :T) { return arg.length } getLength (1 )
泛型约束|泛型类(TS – 14 下)
使用 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 15 16 17 18 19 class Sub <T>{ attr :T[] = [] add (a :T):T[]{ return [a] } } let s = new Sub <number>()s.attr = [123 ] s.attr = ['123' ] s.add (123 ) let str = new Sub <string>()str.attr = [123 ] str.attr = ['123' ] str.add ('123' ) console .log (s,str)
泛型工具类型(大量补充额外内容)
作者使用了 Typora
作为写笔记的编辑器,这里可以对目录进行折叠方面我们查阅我们想要的部分
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如
Partial、Required、Readonly、Record 和 ReturnType
等。不过在具体介绍之前,我们得先介绍一些相关的基础知识,方便读者可以更好的学习其它的工具类型。
1.typeof
typeof
的主要用途是在类型上下文中获取变量或者属性的类型,下面我们通过一个具体示例来理解一下。
1 2 3 4 5 6 interface Person { name : string; age : number; } const sem : Person = { name : "semlinker" , age : 30 };type Sem = typeof sem;
在上面代码中,我们通过 typeof
操作符获取 sem
变量的类型并赋值给 Sem 类型变量,之后我们就可以使用 Sem 类型:
1 const lolo : Sem = { name : "lolo" , age : 5 };
你也可以对嵌套对象执行相同的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const Message = { name : "jimmy" , age : 18 , address : { province : "四川" , city : "成都" , }, }; type message = typeof Message ;
此外,typeof
操作符除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型,比如:
1 2 3 4 function toArray (x: number ): Array <number> { return [x]; } type Func = typeof toArray;
2.keyof
keyof
操作符是在 TypeScript 2.1
版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
1 2 3 4 5 6 7 8 9 interface Person { name : string; age : number; } type K1 = keyof Person ; type K2 = keyof Person []; type K3 = keyof { [x : string]: Person };
在 TypeScript 中支持两种索引签名,数字索引和字符串索引:
1 2 3 4 5 6 7 8 9 interface StringArray { [index : string]: string; } interface StringArray1 { [index : number]: string; }
为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript
在执行索引操作时,会先把数值索引先转换为字符串索引 。所以
keyof { [x: string]: Person }
的结果会返回
string | number
。
keyof 也支持基本数据类型:
1 2 3 4 let K1: keyof boolean ; // let K1: "valueOf" let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...let K3: keyof symbol; // let K1: "valueOf"
keyof 的作用
JavaScript
是一种高度动态的语言。有时在静态类型系统中捕获某些操作的语义可能会很棘手。以一个简单的
prop
函数为例:
1 2 3 function prop (obj, key ) { return obj[key]; }
该函数接收 obj 和 key
两个参数,并返回对应属性的值。对象上的不同属性,可以具有完全不同的类型,我们甚至不知道
obj 对象长什么样。
那么在 TypeScript 中如何定义上面的 prop
函数呢?我们来尝试一下:
1 2 3 function prop (obj: object, key: string ) { return obj[key]; }
在上面代码中,为了避免调用 prop 函数时传入错误的参数类型,我们为 obj
和 key 参数设置了类型,分别为 {}
和 string
类型。然而,事情并没有那么简单。针对上述的代码,TypeScript
编译器会输出以下错误信息:
1 2 Element implicitly has an 'any' type because expression of type 'string' can't be used to index type ' {}'.
元素隐式地拥有 any
类型,因为 string
类型不能被用于索引 {}
类型。要解决这个问题,你可以使用以下非常暴力的方案:
1 2 3 4 function prop (obj: object, key: string ) { return (obj as any)[key]; }
很明显该方案并不是一个好的方案,我们来回顾一下 prop
函数的作用,该函数用于获取某个对象中指定属性的属性值。因此我们期望用户输入的属性是对象上已存在的属性,那么如何限制属性名的范围呢?这时我们可以利用本文的主角
keyof
操作符:
1 2 3 4 function prop<T extends object, K extends keyof T>(obj : T, key : K) { return obj[key]; }
在以上代码中,我们使用了 TypeScript
的泛型和泛型约束。首先定义了 T 类型并使用 extends
关键字约束该类型必须是 object 类型的子类型,然后使用 keyof
操作符获取 T 类型的所有键,其返回类型是联合类型,最后利用
extends
关键字约束 K 类型必须为 keyof T
联合类型的子类型。
是骡子是马拉出来遛遛就知道了,我们来实际测试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Todo = { id : number; text : string; done : boolean; } const todo : Todo = { id : 1 , text : "Learn TypeScript keyof" , done : false } function prop<T extends object, K extends keyof T>(obj : T, key : K) { return obj[key]; } const id = prop (todo, "id" ); const text = prop (todo, "text" ); const done = prop (todo, "done" );
很明显使用泛型,重新定义后的
prop<T extends object, K extends keyof T>(obj: T, key: K)
函数,已经可以正确地推导出指定键对应的类型。那么当访问 todo
对象上不存在的属性时,会出现什么情况?比如:
1 const date = prop (todo, "date" );
对于上述代码,TypeScript 编译器会提示以下错误:
1 2 Argument of type '"date"' is not assignable to parameter of type '"id" | "text" | "done"' .
这就阻止我们尝试读取不存在的属性。
3.in
in
用来遍历联合类型:
1 2 3 4 5 6 type Keys = "a" | "b" | "c" type Obj = { [p in Keys ]: any }
4.infer
在条件类型语句中,可以用 infer
声明一个类型变量并且对它进行使用。
1 2 3 4 type ReturnType <T> = T extends ( ...args : any[] ) => infer R ? R : any;
以上代码中 infer R
就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
5.extends
有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过
extends 关键字添加泛型约束。
1 2 3 4 5 6 7 8 9 interface Lengthwise { length : number; } function loggingIdentity<T extends Lengthwise >(arg : T): T { console .log (arg.length ); return arg; }
现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
这时我们需要传入符合约束类型的值,必须包含 length 属性:
1 loggingIdentity ({ length : 10 , value : 3 });
索引类型
在实际开发中,我们经常能遇到这样的场景,在对象中获取一些属性的值,然后建立对应的集合。
1 2 3 4 5 6 7 8 9 10 11 let person = { name : "musion" , age : 35 , }; function getValues (person: any, keys: string[] ) { return keys.map (key => person[key]); } console .log (getValues (person, ["name" , "age" ])); console .log (getValues (person, ["gender" ]));
在上述例子中,可以看到 getValues (persion, [‘gender’]) 打印出来的是
[undefined],但是 ts 编译器并没有给出报错信息,那么如何使用 ts
对这种模式进行类型约束呢?这里就要用到了索引类型,改造一下 getValues
函数,通过 索引类型查询 和 索引访问
操作符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function getValues<T, K extends keyof T>(person : T, keys : K[]): T[K][] { return keys.map (key => person[key]); } interface Person { name : string; age : number; } const person : Person = { name : 'musion' , age : 35 } getValues (person, ['name' ]) getValues (person, ['gender' ])
编译器会检查传入的值是否是 Person
的一部分。通过下面的概念来理解上面的代码:
1 2 3 4 5 6 7 8 9 10 T[K]表示对象T的属性K所表示的类型,在上述例子中,T[K][] 表示变量T取属性K的值的数组 class Person { name :string; age :number; } type MyType = Person ['name' ];
介绍完概念之后,应该就可以理解上面的代码了。首先看泛型,这里有 T 和 K
两种类型,根据类型推断,第一个参数 person 就是 person,类型会被推断为
Person。而第二个数组参数的类型推断(K extends keyof T),keyof
关键字可以获取 T,也就是 Person 的所有属性名,即 [‘name’, ‘age’]。而
extends 关键字让泛型 K 继承了 Person 的所有属性名,即 [‘name’,
‘age’]。这三个特性组合保证了代码的动态性和准确性,也让代码提示变得更加丰富了
1 2 3 getValues (person, ["gender" ]);
映射类型
根据旧的类型创建出新的类型,我们称之为映射类型
比如我们定义一个接口
1 2 3 4 interface TestInterface { name : string; age : number; }
我们把上面定义的接口里面的属性全部变成可选
1 2 3 4 5 6 7 8 9 10 11 12 type OptionalTestInterface <T> = { [p in keyof T]+?:T[p] } type newTestInterface = OptionalTestInterface <TestInterface >
比如我们再加上只读
1 2 3 4 5 6 7 8 9 10 type OptionalTestInterface <T> = { +readonly [p in keyof T]+?:T[p] } type newTestInterface = OptionalTestInterface <TestInterface >
由于生成只读属性和可选属性比较常用,所以 TS
内部已经给我们提供了现成的实现 Readonly / Partial,
会面内置的工具类型会介绍.
内置的工具类型
Partial
Partial<T>
将类型的属性变成可选
定义
1 2 3 4 type Partial <T> = { [P in keyof T]?: T[P]; };
在以上代码中,首先通过 keyof T
拿到 T
的所有属性名,然后使用 in
进行遍历,将值赋给
P
,最后通过 T[P]
取得相应的属性值的类。中间的
?
号,用于将所有属性变为可选。
举例说明
1 2 3 4 5 6 7 8 interface UserInfo { id : string; name : string; } const xiaoming : UserInfo = { name : "xiaoming" , };
使用 Partial<T>
1 2 3 4 type NewUserInfo = Partial <UserInfo >; const xiaoming : NewUserInfo = { name : "xiaoming" , };
这个 NewUserInfo 就相当于
1 2 3 4 interface NewUserInfo { id?: string; name?: string; }
但是 Partial<T>
有个局限性,就是只支持处理第一层的属性,如果我的接口定义是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 interface UserInfo { id : string; name : string; fruits : { appleNumber : number, orangeNumber : number, }; } type NewUserInfo = Partial <UserInfo >; const xiaoming : NewUserInfo = { name : "xiaoming" , fruits : { orangeNumber : 1 , }, };
可以看到,第二层以后就不会处理了,如果要处理多层,就可以自己实现
DeepPartial(非内置)
1 2 3 4 5 6 7 8 9 type DeepPartial <T> = { [U in keyof T]?: T[U] extends object ? DeepPartial <T[U]> : T[U] }; type PartialedWindow = DeepPartial <T>;
Required
Required 将类型的属性变成必选
定义
1 2 3 4 type Required <T> = { [P in keyof T]-?: T[P] };
其中 -?
是代表移除 ?
这个 modifier
的标识。再拓展一下,除了可以应用于 ?
这个 modifiers
,还有应用在 readonly
,比如 Readonly<T>
这个类型
1 2 3 4 type Readonly <T> = { readonly [p in keyof T]: T[p]; }
Readonly
Readonly<T>
的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。
定义
1 2 3 4 type Readonly <T> = { readonly [P in keyof T]: T[P]; };
举例说明
1 2 3 4 5 6 7 8 9 interface Todo { title : string; } const todo : Readonly <Todo > = { title : "Delete inactive users" , }; todo.title = "Hello" ;
Pick
Pick 从某个类型中挑出一些属性出来
定义
1 2 3 4 type Pick <T, K extends keyof T> = { [P in K]: T[P]; };
举例说明
1 2 3 4 5 6 7 8 9 10 11 12 interface Todo { title : string; description : string; completed : boolean; } type TodoPreview = Pick <Todo , "title" | "completed" >; const todo : TodoPreview = { title : "Clean room" , completed : false , };
可能是TodoPreview
中只有title
和completed
属性
Record
Record<K extends keyof any, T>
的作用是将
K
中所有的属性的值转化为 T
类型。
定义
1 2 3 4 type Record <K extends keyof any, T> = { [P in K]: T; };
举例说明
1 2 3 4 5 6 7 8 9 10 11 interface PageInfo { title : string; } type Page = "home" | "about" | "contact" ; const x : Record <Page , PageInfo > = { about : { title : "about" }, contact : { title : "contact" }, home : { title : "home" }, };
ReturnType
用来得到一个函数的返回值类型
定义
1 2 3 4 5 6 type ReturnType <T extends (...args : any[]) => any> = T extends ( ...args : any[] ) => infer R ? R : any;
infer
在这里用于提取函数类型的返回值类型。ReturnType<T>
只是将 infer R 从参数位置移动到返回值位置,因此此时 R
即是表示待推断的返回值类型。
举例说明
1 2 type Func = (value: number ) => string; const foo : ReturnType <Func > = "1" ;
ReturnType
获取到 Func
的返回值类型为
string
,所以,foo
也就只能被赋值为字符串了。
Exclude
Exclude<T, U>
的作用是将某个类型中属于另一个的类型移除掉。
定义
1 2 type Exclude <T, U> = T extends U ? never : T;
如果 T
能赋值给 U
类型的话,那么就会返回
never
类型,否则返回 T
类型。最终实现的效果就是将 T
中某些属于 U
的类型移除掉。
举例说明
1 2 3 type T0 = Exclude <"a" | "b" | "c" , "a" >; type T1 = Exclude <"a" | "b" | "c" , "a" | "b" >; type T2 = Exclude <string | number | (() => void ), Function >;
Extract<T, U>
的作用是从 T
中提取出
U
。
定义
1 2 type Extract <T, U> = T extends U ? T : never;
举例说明
1 2 type T0 = Extract <"a" | "b" | "c" , "a" | "f" >; type T1 = Extract <string | number | (() => void ), Function >;
Omit
Omit<T, K extends keyof any>
的作用是使用
T
类型中除了 K
类型的所有属性,来构造一个新的类型。
定义
1 2 type Omit <T, K extends keyof any> = Pick <T, Exclude <keyof T, K>>;
举例说明
1 2 3 4 5 6 7 8 9 10 11 12 interface Todo { title : string; description : string; completed : boolean; } type TodoPreview = Omit <Todo , "description" >; const todo : TodoPreview = { title : "Clean room" , completed : false , };
NonNullable
NonNullable<T>
的作用是用来过滤类型中的
null
及 undefined
类型。
定义
1 type NonNullable <T> = T extends null | undefined ? nerver : T
举例说明
1 2 type T0 = NonNullable <string | number | undefined >; type T1 = NonNullable <string[] | null | undefined >;
Parameters
Parameters<T>
的作用是用于获得函数的参数类型组成的元组类型。
定义
1 2 type Parameters <T extends (...args : any) => any> = T extends (...args : infer P) => any ? P : never;
举例说明
1 2 3 4 type A = Parameters <() => void >; type B = Parameters <typeof Array .isArray >; type C = Parameters <typeof parseInt >; type D = Parameters <typeof Math .max >;
tsconfig.json 配置文件(TS –
15)
生成 tsconfig.json 文件
这个文件是通过 tsc --init
命令生成的
tsconfig.json 是 TypeScript 项目的配置文件。如果一个目录下存在一个
tsconfig.json 文件,那么往往意味着这个目录就是 TypeScript
项目的根目录。
tsconfig.json 包含 TypeScript
编译的相关配置,通过更改编译配置项,我们可以让 TypeScript 编译出
ES6、ES5、node 的代码。
配置详解
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" ]
上面的配置详解有点繁杂,我们或许可以将其分类一下
配置分类(compilerOptions
选项)
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 { "compilerOptions" : { "target" : "es5" , "module" : "commonjs" , "lib" : [], "allowJs" : true , "checkJs" : true , "jsx" : "preserve" , "declaration" : true , "sourceMap" : true , "outFile" : "./" , "outDir" : "./" , "rootDir" : "./" , "removeComments" : true , "noEmit" : true , "importHelpers" : true , "isolatedModules" : true , "strict" : true , "noImplicitAny" : true , "strictNullChecks" : true , "noImplicitThis" : true , "alwaysStrict" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "noImplicitReturns" : true , "noFallthroughCasesInSwitch" : true , "moduleResolution" : "node" , "baseUrl" : "./" , "paths" : {}, "rootDirs" : [], "typeRoots" : [], "types" : [], "allowSyntheticDefaultImports" : true , "sourceRoot" : "./" , "mapRoot" : "./" , "inlineSourceMap" : true , "inlineSources" : true , "experimentalDecorators" : true , "emitDecoratorMetadata" : true } }
常用的配置
终端命令
echo ’’>index.ts(创建一个空的名叫 index.ts 的文件)
tsc -init(创建一个 tsconfig.json)
del index.js(删除名叫 index.js 文件)
mkdir dist(创建一个名叫 dist 的文件夹)
tsc(运行)
1.include
指定编译文件默认是编译当前目录下所有的 ts 文件
这个是在中括号中填入路径,路径指向的那个 ts 文件会被编译出一个 js
文件出来。这个我们就可以用来编译指定文件
2.exclude
指定排除的文件
跟include
反过来了,除了写入的路径之外,其他全部编译
3.target
指定编译 js 的版本例如 es5 es6
有些低配置的浏览器是不兼容 es6 的,这个时候我们就可以将其编译成 es5
使其适配
4.allowJS
是否允许编译 js 文件
是否允许 TypeScript 帮你编译 js 文件,默认是不允许的
是否在编译过程中删除文件中的注释
6.rootDir
编译文件的目录
7.outDir
输出的目录
改变输出的目录,也就是编译后输出到这里设置的文件夹目录中
8.sourceMap
代码源文件
这个文件会打包压缩成一行,sourceMap
会记录行数,到时候会比较好找
9.strict
严格模式
严格模式的限制
10.module
默认 common.js 可选 es6 模式 amd umd 等
namespace 命名空间(TS – 16)
我们在工作中无法避免全局变量
造成的污染,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 const aa = 23 ;const aa = 66 ;
1 2 3 4 5 6 7 8 9 10 11 namespace A{ export const aa =23 } namespace B{ export const aa =66 } console .log (A.a );
实际编译成 js 文件的样子
1 2 3 4 5 6 7 "use strict" ;var A;(function (A ) { A.a = 1 ; })(A || (A = {})); console .log (A, a);
嵌套命名空间
就是提取内容的时候需要多.几次。比如下方,原本只需要 a.b,现在需要
a.b.value
编译成 js 文件的话,就是在 function 又套了一层
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' )
抽离命名空间
将命名空间的内容抽离出来,通过 import 引入到其他文件中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 expost namespace B{ export const a = 2 } import xx from './index2.ts' namespace A{ export namespace C{ export const D = 5 } } console .log (A.C .D ,B)
简化命名空间
可以给命名空间路径起个名字,然后直接使用这个名字就可以代替命名空间路径了
这个是不能够在 ts-node 的环境下去使用的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 expost namespace B{ export const a = 2 } import xx from './index2.ts' namespace A{ export namespace C{ export const D = 5 } } console .log (A.C .D ,B)import AAA = A.C .D console .log (AAA )import AAA = A.C console .log (AAA .D )
命名空间的合并
如果命名空间的命名一样的话(重名),会自动合并
1 2 3 4 5 6 7 8 9 10 11 12 13 namespace A{ export const b = 2 } namespace A{ export const d = 3 } namespace A{ export const b = 2 export const d = 3 }
三斜线指令(TS – 17)
三斜线指令是包含单个XML
标签的单行注释。
注释的内容会做为编译器指令使用。
三斜线指令仅 可放在包含它的文件的最顶端。
一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。
如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。
/// <reference path="..." />
指令是三斜线指令中最常见的一种。
它用于声明文件间的 依赖 。
三斜线引用告诉编译器
在编译过程中要引入的额外的文件。也可以认为是另一个
import
你也可以把它理解能
import
,它可以告诉编译器在编译过程中要引入的额外的文件
相较于抽离命名空间。范围更广,将整个文件都抽离出来了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 namespace A { export const fn = ( ) => 'a' } namespace A { export const fn2 = ( ) => 'b' } console .log (A);
这个时候小满将 outFile
打开了,这个的作用是:将输出文件合并为一个文件,并编译到指定的路径中
这个时候 样式的还在
这个时候,我们再打开
removeComments,他的作用是:删除编译后的所有的注释
编译好后,那些样式就不在了
引入声明文件
例如,把 /// <reference types="node" />
引入到声明文件,表明这个文件使用了 @types/node/index.d.ts
里面声明的名字;
并且,这个包需要在编译阶段与声明文件一起被包含进来。
仅当在你需要写一个 d.ts
文件时才使用这个指令。
使用的话,需要将声明文件装起来(npm install @types/node -D)
声明文件 d.ts(TS – 18)
声明文件 declare ,d.ts 中 d 的简写
当使用第三方库
时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
1 2 3 4 5 6 7 declare var 声明全局变量 declare function 声明全局方法 declare class 声明全局类 declare enum 声明全局枚举类型 declare namespace 声明(含有子属性的)全局对象 interface 和 type 声明全局类型
先:npm init -y
npm init -y
在文件夹下生成默认的
package.json
文件,使用命令生成的 package.json
文件内容与不使用命令生产的不一样
后:npm install express -S
正式线安装
npm install axios
从node_modules
的axios
的 package.json
可以发现 types:“index.d.ts”,可以发现声明文件已经被指定了
我们在去看 index.d.ts 文件可以看到最后通过 declare 将其导出了
在引入使用的时候,发现 express 在引入的时候爆红了
同样的在 node_nodules 可以找到 express 中的
package.json,发现他根本没有
types,也就是说没有指定声明文件。所以才会有爆红的这个问题
我们可以在根目录下创建一个 express.d.ts 文件
在文件中写入declare var express:() => any
这个时候,我们在使用 express
的时候,就发现可以使用了express()
,不会爆红了
另一种方法就是按照提示去装包,然后去 tsconfig.json
去导出一下"allowSyntheticDefaultImports":true
进行补全
npm
收录包大全
TS
没有流行起来的部分原因:有些第三方库没有写声明文件,那就没有提示还会报错了
给出了提示让你试一试npm i --save-dev @types/express
,@types
是编写声明文件库的规范格式。
如果装不上就说明社区没有写声明文件
image-20230223170402601
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import express from "express" ;xiaoyu = "大二学生" ; 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 ); });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 } declare var xiaoyu :string const express : Express export default express }
Mixins 混入(TS – 19)
TypeScript 混入 Mixins 其实 vue 也有 mixins 这个东西
你可以把他看作为合并
对象混入
可以使用 ES6
的 Object.assign 合并多个对象
此时 people 会被推断成一个交差类型 Name & Age & sex;
Object.assign()
Object.assign () 这个方法来实现浅复制
主要的用途是用来合并多个 JavaScript 的对象
Object.assign ()
接口可以接收多个参数,第一个参数是目标对象,后面的都是源对象,assign
方法将多个原对象的属性和方法都合并到了目标对象上面,如果在这个过程中出现同名的属性(方法),后合并的属性(方法)会覆盖之前的同名属性(方法)
Object.assign
拷贝的属性是有限制的,只会拷贝对象本身的属性(不会拷贝继承属性),也不会拷贝不可枚举 的属性
Object.assign 不会跳过那些值为 [null] 或 [undefined] 的源对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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);
类的混入
首先声明两个 mixins 类 (严格模式要关闭不然编译不过)
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 class A { type : boolean; changeType ( ) { this .type = !this .type ; } } class B { name : string; getName ( ) { return this .name ; } } class C implements A, B { type : boolean = false ; name : string = "小余" ; changeType : () => void ; getName : () => string; } mixins (C, [A, B]); function mixins (curClas: any, itemCls: any[] ) { itemCls.forEach (item => { console .log (item); Object .getOwnPropertyNames (item.prototype ).forEach (name => { console .log (name); curClas.prototype [name] = item.prototype [name]; }); }); } let ccc = new C (); console .log (ccc.type ); ccc.changeType (); console .log (ccc.type );
装饰器 Decorator(TS – 20)
Decorator 装饰器 是一项实验性特性,在未来的版本中可能会发生改变
它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能
若要启用实验性的装饰器特性,你必须在命令行
或
tsconfig.json
里启用编译器
选项
启用的名字叫experimentalDecorators
装饰器
装饰器 是一种特殊类型的声明,它能够被附加到类声明、方法、访问符、属性或者参数上
通过@
语法糖实现
使用方法:对于这个的实现我认为就是定义好了之后直接盖在你想对其使用目标的头顶上
以下两个代码块的 watcher 都是不支持传参的
1 2 3 4 5 6 const watcher : ClassDecorator = (target: Function ) => { console .log (target); }; @watcher class A {}
知识点复习:
prototype
对象是实现面向对象的一个重要机制。每个函数也是一个对象,它们对应的类就是
function,每个函数对象都具有一个子对象 prototype。
Prototype 表示了该函数的原型,prototype
表示了一个类的属性的集合。当通过 new 来生成一个类的对象时,prototype
对象的属性就会成为实例化对象的属性。
Prototype 与
prototype 的区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const watcher :ClassDecorator = (target:Function )=> { target.prototype .getName = <T>(name :T):T => { return name } } @watcher class A {} let a = new A ()a.getName () console .log ((<any>a).getName ("小满深夜骑单车" ))@watcher class B {} let b = new B ()console .log (b.getName ('666' ))
装饰器工厂
我认为其实就是多了一层壳,这层壳用来接收@watcher 的参数。里面 return
的那一层再用来接收 class A 这个构造器
其实也就是一个高阶函数 外层的函数接受值
里层的函数最终接受类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const watcher = (name=string):ClassDecorator => { return (target:Function ) => { target.prototype .getNames = () => { return name } target.prototype .getOptions = (): string => { return name } } @watcher ("小余的笔记" ) class A {} let a = new A ()console .log ((<any>a).getNames ())
装饰器组合
就是可以使用多个装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const watcher = (name : string): ClassDecorator => { return (target: Function ) => { target.prototype .getName = () => { return name; }; }; }; const log : ClassDecorator = (target: Function ) => { target.prototype .a = 213 ; }; @log @watcher ("小满" ) class 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 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 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 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 const met : ParameterDecorator = (...args ) => { console .log (args); }; class A { constructor ( ) {} setParasm (@met name: string = "213" ) {} } const a = new A ();
Rollup 构建 TS 项目(TS – 21)
这个视频我是属于过一遍的程度,本章节内容将直接把小满在 CSDN 的文章
copy 过来
Rollup 构建 TS 项目
安装依赖
全局安装 rollup npm install rollup-g
安装 TypeScript npm install typescript -D
安装 TypeScript 转换器 npm install rollup-plugin-typescript2
-D
安装代码压缩插件 npm install rollup-plugin-terser -D
安装 rollupweb 服务 npm install rollup-plugin-serve -D
安装热更新
npm install rollup-plugin-livereload -D
引入外部依赖 npm install rollup-plugin-node-resolve -D
安装配置环境变量用来区分本地和生产 npm install cross-env
-D
替换环境变量给浏览器使用 npm install rollup-plugin-replace
-D
配置 json 文件
npm init -y
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 { "name" : "rollupTs" , "version" : "1.0.0" , "description" : "" , "main" : "index.js" , "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" , "dev" : "cross-env NODE_ENV=development rollup -c -w" , "build" :"cross-env NODE_ENV=produaction rollup -c" }, "keywords" : [], "author" : "" , "license" : "ISC" , "devDependencies" : { "cross-env" : "^7.0.3" , "rollup-plugin-livereload" : "^2.0.5" , "rollup-plugin-node-resolve" : "^5.2.0" , "rollup-plugin-replace" : "^2.2.0" , "rollup-plugin-serve" : "^1.1.0" , "rollup-plugin-terser" : "^7.0.2" , "rollup-plugin-typescript2" : "^0.31.1" , "typescript" : "^4.5.5" } }
配置 rollup 文件
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 console .log (process.env );import ts from "rollup-plugin-typescript2" ;import path from "path" ;import serve from "rollup-plugin-serve" ;import livereload from "rollup-plugin-livereload" ;import { terser } from "rollup-plugin-terser" ;import resolve from "rollup-plugin-node-resolve" ;import repacle from "rollup-plugin-replace" ;const isDev = ( ) => { return process.env .NODE_ENV === "development" ; }; export default { input : "./src/main.ts" , output : { file : path.resolve (__dirname, "./lib/index.js" ), format : "umd" , sourcemap : true , }, plugins : [ ts (), terser ({ compress : { drop_console : !isDev (), }, }), repacle ({ "process.env.NODE_ENV" : JSON .stringify (process.env .NODE_ENV ), }), resolve ([".js" , ".ts" ]), isDev () && livereload (), isDev () && serve ({ open : true , openPage : "/public/index.html" , }), ], };
配置 tsconfig.json
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 { "compilerOptions" : { "target" : "es5" , "module" : "ES2015" , "sourceMap" : true , "esModuleInterop" : true , "forceConsistentCasingInFileNames" : true , "strict" : true , "skipLibCheck" : true } }
npm run dev 启动就可以尽情的玩耍了
webpack 构建 TS 项目
安装依赖
安装 webpack npm install webpack -D
webpack4 以上需要 npm install webpack-cli -D
编译 TS npm install ts-loader -D
TS 环境 npm install typescript
-D
热更新服务 npm install webpack-dev-server -D
HTML 模板 npm install html-webpack-plugin -D
配置文件
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 const path = require ("path" );const htmlWebpackPlugin = require ("html-webpack-plugin" );module .exports = { entry : "./src/index.ts" , mode : "development" , output : { path : path.resolve (__dirname, "./dist" ), filename : "index.js" , }, stats : "none" , resolve : { extensions : [".ts" , ".js" ], alias : { "@" : path.resolve (__dirname, "./src" ), }, }, module : { rules : [ { test : /\.ts$/ , use : "ts-loader" , }, ], }, devServer : { port : 1988 , proxy : {}, }, plugins : [ new htmlWebpackPlugin ({ template : "./public/index.html" , }), ], };
目录结构
TypeScript 高级 -
实战插件编写
目的
使用 TypeScript 去封装 local storage(支持设置过期时间) =>local
storage
本身无此机制,只能人为手动删除,否则会一直存放在浏览器当中,可不可以跟
cookie
一样设置一个有效期。如果一直存放在浏览器又感觉有点浪费,那我们可以把localStorage
进行二次封装实现该方案
实现思路
在存储的时候设置一个过期时间,并且存储的数据进行格式化方便统一校验,在读取的时候获取当前时间进行判断是否过期,如果过期进行删除即可。
事前准备
image-20230118181032581
写代码前需要进行 3
个改动(tsconfig.json
文件下的修改)
改动地方
改动原因
"module": "commonjs"
修改为"module": "ESNext"
,位于
27 行附近
"module"
选项用来指定 TypeScript
编译器生成
JavaScript 代码时使用的模块系统。"commonjs"
是
Node.js
默认的模块系统。当 "module"
设置为
"ESNext"
时,TypeScript
编译器会把
TypeScript
代码编译成ECMAScript
模块,这是最新的
ECMAScript
模块系统
"moduleResolution": "node"
功能打开,位于 29 行附近
当 "moduleResolution"
设置为 "node"
时,TypeScript
编译器会使用 Node.js
模块解析策略来解析模块路径。这意味着编译器会先在当前目录下的
node_modules 文件夹中查找模块,如果找不到就会去父目录的
node_modules
文件夹中查找,直到找到或者到达根目录 这样配置的好处是可以使用
Node.js
的模块解析机制来加载项目中的模块,并且可以使用
npm
和 yarn
来管理项目中的依赖
"strict": false
,位于 79 行附近
关掉严格模式
编写插件
1 2 3 4 export enum Dictionaries { expire = '__expire__' , permanent = 'permanent' }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Dictionaries } from "../enum" export type Key = string export type expire = Dictionaries .permanent | number export interface Data <T> { value : T [Dictionaries .expire ]: Dictionaries .expire | number } export interface Result <T> { message : string, value : T | null } export interface StorageCls { set : <T>(key: Key, value: T, expire: expire ) => void get : <T>(key: Key ) => Result <T | null > remove : (key: Key ) => void clear : () => void }
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 import { StorageCls , Key , expire, Data , Result } from "./type" ;import { Dictionaries } from "./enum" ;export class Storage implements StorageCls { public set<T = any>(key : Key , value : T, expire : expire = Dictionaries .permanent ) { const data = { value, [Dictionaries .expire ]: expire } localStorage .setItem (key, JSON .stringify (data)) } public get<T = any>(key : Key ): Result <T | null > { const value = localStorage .getItem (key) if (value) { const obj : Data <T> = JSON .parse (value) const now = new Date ().getTime () if (typeof obj[Dictionaries .expire ] == 'number' && obj[Dictionaries .expire ] < now) { this .remove (key) return { message : `您的${key} 已过期` , value : null } } else { return { message : "成功读取" , value : obj.value } } } else { console .warn ('key值无效' ) return { message : `key值无效` , value : null } } } public remove (key: Key ) { localStorage .removeItem (key) } public clear ( ) { localStorage .clear () } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" /> <meta http-equiv ="X-UA-Compatible" content ="IE=edge" /> <meta name ="viewport" content ="width=device-width, initial-scale=1.0" /> <title > 小余练习</title > </head > <body > <script type ="module" > import { Storage } from "./dist/index.js" ; const sl = new Storage (); sl.set ("a" , 123 , new Date ().getTime () + 5000 ); setInterval (() => { const a = sl.get ("a" ); console .log (a); }, 500 ); </script > </body > </html >
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 import ts from 'rollup-plugin-typescript2' import path from 'path' export default { input : './src/index.ts' , output : { file : path.resolve (__dirname, './dist/index.js' ) }, plugins : [ ts () ] } import ts from "rollup-plugin-typescript2" ;import path from "path" ;import { fileURLToPath } from "url" ;const metaUrl = fileURLToPath (import .meta .url );const dirName = path.dirname (metaUrl);export default { input : "./src/index.ts" , output : { file : path.resolve (dirName, "./dist/index.js" ), }, plugins : [ts ()], };
tsconfig.json 文件(不发了,太多了,就 3 个需要改动的地方)
打包
需要安装 3 个包(直接一次性安装好)
pnpm install rollup typescript rollup-plugin-typescript2
在package.json
中配置启动命令
image-20230118204531135
启动
在本地服务器中启动(Stop Live Server)
image-20230118205225841
实战 TS 编写发布订阅模式(TS –
22)
什么是发布订阅模式,其实小伙伴已经用到了发布订阅模式例如
addEventListener,Vue evnetBus
都属于发布订阅模式
简单来说就是 你(小满)要和 大满 二满
三满打球,大满带球,二满带水,三满带球衣。全都准备完成后开始打球。
思维导图
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 interface Evenet { on :(name:string,fn:Function ) => void , emit :(name:string,...args:Array <any> ) => void off :(name:string,fn:Function )=> void once :(name:string,fn:Function ) => void } interface List { [key :string]:Array <Function > } class Dispatch implements Evenet { list :List constructor ( ){ this .list = {} } on (name:string,fn:Function ){ const callback = this .list [name] || [] callback.push (fn) this .list [name] = callback console .log (this .list ); } emit (name:string,...args:Array <any> ){ let eventName = this .list [name] if (eventName){ eventName.forEach (fn => { fn.apply (this ,args) }) }else { console .error (`名称错误${name} ` ) } } off (name:string,fn:Function ){ let eventName = this .list [name] if (eventName && fn){ let index = eventName.findIndex (fns => fns === fn ) eventName.splice (index,1 ) console .log (eventName) }else { console .error (`名称错误${name} ` ) } } once (name:string,fn:Function ){ let de = (...args:Array <any> ) =>{ fn.apply (this ,args) this .off (name,de) } this .on (name,de) } } const o = new Dispatch ()o.on ('post' ,()=> { console .log (66 ); }) o.on ('post' ,(...args:Array <any> )=> { console .log (99 ,args) }) const fn = (...args:Array <any> ) => { console .log (args,2 ) } o.on ('post' ,fn) o.off ('post' ,fn) o.once ('post' ,(...args:Array <any> )=> { console .log (args,'once' ) }) o.emit ('post' ,1 ,false ,{name :"小满" }) o.emit ('post' ,2 ,false ,{name :"小满" })
上面在 off 删除中使用到的 splice 知识点补充
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 splice (index,len,[item])它也可以用来替换 / 删除 / 添加数组内某一个或者几个值(该方法会改变原始数组) index : 数组开始下标len : 替换 / 删除的长度item : 替换的值,删除操作的话 item 为空删除: var arr = ['a' ,'b' ,'c' ,'d' ];arr.splice (1 ,1 ); console .log (arr); var arr2 = ['a' ,'b' ,'c' ,'d' ] arr2.splice (1 ,2 );console .log (arr2); 替换: var arr = ['a' ,'b' ,'c' ,'d' ];arr.splice (1 ,1 ,'ttt' ); console .log (arr); var arr2 = ['a' ,'b' ,'c' ,'d' ];arr2.splice (1 ,2 ,'ttt' ); console .log (arr2); 添加: var arr = ['a' ,'b' ,'c' ,'d' ];arr.splice (1 ,0 ,'ttt' ); console .log (arr);
TS 进阶 协变 逆变
双向协变(新增内容 - 代号:冬瓜)
鸭子类型
所谓的类型兼容性,就是用于确定一个类型是否能赋值给其他的类型。typeScript
中的类型兼容性是基于结构类型 的(也就是形状),如果 A
要兼容 B 那么 A 至少具有 B 相同的属性。
“鸭子类型”
是一个术语,用于描述动态类型语言中的类型系统的一种特殊方式。在鸭子类型系统中,如果对象看起来像鸭子,那么它就是鸭子。
在 TypeScript
中,鸭子类型是一种使用接口(interfaces)和类型断言(type
assertions)来模拟动态类型系统的方法。这种方法允许定义一个对象的外观,而不需要严格的类型声明。
小满的解释:一只鸟 走路像鸭子
,游泳也像,做什么都像,那么这只鸟就可以成为鸭子类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 interface a { name : string; age : number; } interface b { name : string; age : number; sex : string; } let xiaoman : a = { name : "小满" , age : 24 , }; let xiaoyu : b = { name : "小余" , age : 20 , sex : "男" , };
协变
A B 两个类型完全不同但是竟然可以赋值并无报错 B 类型充当 A
类型的子类型,当子类型里面的属性满足 A
类型就可以进行赋值,也就是说不能少可以多,这就是协变
举例子:a 工作是前端,b
工作是全栈(全都干)。你是前端,我是全栈,我能覆盖掉你的工作,你覆盖不了我的工作。因为你的工作里面只有前端,而我的工作里面还有后端,服务器维护,等等杂七杂八的东西,这些你都不会,你一上,除了前端部分之外,你啥都看不懂,完成不了工作是会被赶走的。要求全栈的工作你可以会得更多,但是这门工作的要求你得达到,需要用到的技术你不能不会
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 interface a { name : string; age : number; } interface b { name : string; age : number; sex : string; } let xiaoman : a = { name : "小满" , age : 24 , }; let xiaoyu : b = { name : "小余" , age : 20 , sex : "男" , }; xiaoman = xiaoyu; xiaoyu = xiaoman;
逆变
在 TypeScript
中,逆变是一种类型系统的特性,可以在泛型(generics)和函数参数上使用。逆变允许我们定义一个泛型类型或函数参数,其类型可以是一个父类型,而不是子类型。
逆变一般发生于函数的参数上面
逆变可以帮助我们编写更灵活的代码,同时仍然保持类型安全性
小满的解释:这里比较绕,注意看fna
赋值 给
fnb
其实最后执行的还是fna
而
fnb
的类型能够完全覆盖fna
所以这一定是安全的,相反fna
的类型不能完全覆盖fnb
少一个
sex 所以是不安全的。 我的解释在下面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface a { name : string; age : number; } interface b { name : string; age : number; sex : string; } let fna = (params: a ) => {};let fnb = (params: b ) => {};fnb = fna;
双向协变
在逆变的基础上反过来
从只能fnb = fna
到能够fna = fnb
,也就是子类赋值给了父类
使用方式:tsconfig strictFunctionTypes 设置为 false 支持双向协变 fna
fnb 随便可以来回赋值
一般情况下建议不开启
1 2 3 4 5 "strictFunctionTypes" : false ,
image-20230211145443560
TS 进阶用法 proxy &
Reflect(TS – 23)
proxy:对象代理(是 ES6
新增的对象拦截器,能够监听到一个对象的变化)
Reflect:配合 proxy 来操作对象
Proxy
Proxy
对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)
target
要使用 Proxy
包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)。
handler
一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理
p
的行为。
handler.get()
本次使用的 get
属性读取操作的捕捉器。
handler.set()
本次使用的 set
属性设置操作的捕捉器。
Reflect
与大多数全局对象不同 Reflect
并非一个构造函数,所以不能通过 new
运算符 对其进行调用,或者将 Reflect
对象作为一个函数来调用。Reflect
的所有属性和方法都是静态的(就像 Math
对象)
Reflect.get(target, name,
receiver)
Reflect.get
方法查找并返回 target
对象的
name
属性,如果没有该属性返回 undefined
Reflect.set(target,
name,value, receiver)
Reflect.set
方法设置 target
对象的
name
属性等于 value
。
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 type Person = { name : string, age : number, text : string, }; const proxy = (object: any, key: any ) => { return new Proxy (object, { get (target, prop, receiver ) { console .log ("================>get" , prop); return Reflect .get (target, prop, receiver); }, set (target, prop, value, receiver ) { console .log ("================>set" , prop); return Reflect .set (target, prop, value, receiver); }, }); }; const logAccess = <T>(object : T, key : "name" | "age" | "text" ): T => { return proxy (object, key); }; let man : Person = { name : "小满" , age : 22 , text : "三秒真男人" , }; let man2 = logAccess ( { name : "小余" , }, "name" ); man.age = 30 ; man.age ; console .log (man);
泛型优化
1 2 3 4 5 6 7 8 9 10 11 12 13 const logAccess = <T>(object : T ,key :keyof T):T => { return proxy (object,key) } let man2 = logAccess ({ name :"小余" , id :925 },'id' ) let man2 = logAccess ({ name :"小余" , id :925 },'id2' )
优化完整版
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 type Person = { name : string, age : number, text : string } const proxy = (object: any, key: any ) => { return new Proxy (object, { get (target, prop, receiver ) { console .log (`get key======>${key} ` ); return Reflect .get (target, prop, receiver) }, set (target, prop, value, receiver ) { console .log (`set key======>${key} ` ); return Reflect .set (target, prop, value, receiver) } }) } const logAccess = <T>(object : T, key : keyof T): T => { return proxy (object, key) } let man : Person = logAccess ({ name : "小满" , age : 20 , text : "我的很小" }, 'age' ) let man2 = logAccess ({ id :1 , name :"小满2" }, 'name' ) man.age = 30 console .log (man);
TS 进阶用法 Partial & Pick(TS
– 24)
TypeScript 内置高级类型 Partial Pick
Partial
源码
1 2 3 4 5 6 7 type Partial <T> = { [P in keyof T]?: T[P]; };
手写实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Person = { name :string, age :number, text :string } type Par <T> = { [P in keyof T]?:T[P] }; type p = Partial <Person >
使用前(范例)
1 2 3 4 5 6 type Person = { name : string, age : number, }; type p = Partial <Person >;
使用后(范例)
1 2 3 4 type p = { name?: string | undefined , age?: number | undefined , };
Pick
从类型定义 T 的属性中,选取指定一组属性,返回一个新的类型定义。
源码
1 2 3 4 5 6 type Pick <T, K extends keyof T> = { [P in K]: T[P]; };
手写实现
1 2 3 4 5 6 7 8 9 10 type Person = { name : string, age : number, text : string, address : string, }; type Ex = "text" | "age" ; type A = Pick <Person , Ex >;
分析(需要结合手写的内容看)
1 2 3 4 5 type Pick <T, K extends keyof T> = { [P in K]: T[P]; }; type p = Pick <Person ,'age' |'name' >
TS 进阶用法 Record &
Readonly(TS – 25)
Readonly
和 Partial 很像是吧?只是将 Partial 替换成了 Readonly
源码
1 2 3 type Readonly <T> = { readonly [P in keyof T]: T[P]; };
手写实现
1 2 3 4 5 6 7 8 9 10 11 12 type R<T> = { readonly [P in keyof T]: T[P]; }; type Person = { name :string, age :number, text :string } type man = R<Person >
Record
1 keyof any 返回 string number symbol 的联合类型
2 in 我们可以理解成 for in P 就是 key 遍历 keyof any 就是 string
number symbol 类型的每一项
3 extends 来约束我们的类型
4 T 直接返回类型
做到了约束 对象的 key 同时约束了 value
源码
1 2 3 type Record <K extends keyof any, T> = { [P in K]: T; };
手写实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Rec <K extends keyof any,T> = { [P in K]: T; }; type key = string |number | symbol type Person ={ name :string, age :number, text :string } type K = "A" |"B" |"C" type B = Rec <K,Person > let obj :B = { A :{name :"小满" ,age :3 ,text :"三秒真男人" }, B :{name :"小余" ,age :18 ,text :"三小时真男人" }, C :{name :"狗洛" ,age :1 ,text :"零点三秒真男人" } }
TS 进阶用法 infer(TS – 26)
infer 是 TypeScript 新增到的关键字 充当占位符
我们来实现一个条件类型推断的例子
定义一个类型 如果是数组类型 就返回 数组元素的类型 否则 就传入什么类型
就返回什么类型
1 2 3 4 5 type TYPE <T> = T extends Array <any> ? T[number] : T type A = TYPE <string[]> type B = TYPE <(string|number)[]> type C = TYPE <boolean>
infer
使用 inter 修改
1 2 3 4 5 6 7 8 9 10 11 12 type TYPE <T> = T entends Array <infer U> ? U : T type TYPE <T> = T entends Array <infer U> ? U : never type A = TYPE <string[]> type B = TYPE <(string|number)[]> type T = [string,number] type uni = TYPE <T> type uni = TYPE <T>
infer 类型提取(TS – 27)
提取头部元素
T extends any[]
:对 T 进行泛型约束,一个 any
类型的数组
type First<T extends any[]> = T extends [infer one,infer two,infer three]
?
one:[]:对 T 进行泛型约束为数组类型,用 infer 提取,提取的变量名对应着
Arr 的 a、b、c。然后决定返回 one,也就是第一个元素还是空数组
1 2 3 4 5 6 7 type Arr = ['a' ,'b' ,'c' ] type First <T extends any[]> = T extends [infer one,infer two,infer three]? one :[] type First <T extends any[]> = T extends [infer First ,...any[]] ? First : [] type a = First <Arr >
提取尾部元素
将头尾反过来了
1 2 3 4 5 type Arr = ['a' , 'b' , 'c' ] type Last <T extends any[]> = T extends [...any[], infer Last ] ? Last : [] type c = Last <Arr >
剔除第一个元素 Shift
将除了第一个之外的其他通过 ES6 语法提取出来
1 2 3 4 5 type Arr = ['a' ,'b' ,'c' ] type First <T extends any[]> = T extends [unknown,...infer Rest ] ? Rest : [] type a = First <Arr >
剔除尾部元素 pop
将除了第最后一个之外的其他通过 ES6 语法提取出来
1 2 3 4 5 type Arr = ['a' ,'b' ,'c' ] type First <T extends any[]> = T extends [...infer Rest ,unknown] ? Rest : [] type a = First <Arr >
infer 递归(TS – 28)
需求:
有这么一个类型
1 type Arr = [1 , 2 , 3 , 4 ];
希望通过一个 ts 工具变成
1 type Arr = [4 , 3 , 2 , 1 ];
具体思路 首先使用泛型约束 约束只能传入数组类型的东西
然后从数组中提取第一个,放入新数组的末尾,反复此操作,形成递归
满足结束条件返回该类型
1 2 3 4 5 6 type Arr = [1 ,2 ,3 ,4 ] type ReverArr <T extends any[]> = T extends [infer First ,...infer rest] ? [...ReverArr <rest>,First ] : T type Arrb = ReverArr <Arr >