typescript学习笔记
https://gitee.com/samuelsue/typescript-learning-notes
配置ts运行环境 需要安装的依赖
1 2 3 4 5 6 7 8 9 10 11 12 "devDependencies" : { "@typescript-eslint/eslint-plugin" : "^3.1.0" , "@typescript-eslint/parser" : "^3.1.0" , "clean-webpack-plugin" : "^3.0.0" , "eslint" : "^7.2.0" , "html-webpack-plugin" : "^4.3.0" , "ts-loader" : "^7.0.5" , "webpack" : "^4.43.0" , "webpack-cli" : "^3.3.11" , "webpack-dev-server" : "^3.11.0" , "webpack-merge" : "^4.2.2" }
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 const path = require ('path' )const HtmlWebpackPlugin = require ('html-webpack-plugin' );const {CleanWebpackPlugin } = require ('clean-webpack-plugin' );module .exports = { entry : './src/index.ts' , output : { filename : 'main.js' , path : path.resolve (__dirname,'../dist' ) }, resolve : { extensions : ['.js' , '.ts' , '.tsx' ] }, module : { rules : [ { oneOf :[ { test : /\.tsx?$/ , use : 'ts-loader' , exclude : /node_modules/ } ] } ] }, devtool : process.env .NODE_ENV === 'development' ?'eval-source-map' :'cheap-module-source-map' , plugins : [ new HtmlWebpackPlugin ({ template : './src/template/index.html' }), new CleanWebpackPlugin () ], devServer : { contentBase : '../dist' , compress : true , port : 8090 , open : true } }
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 module .exports = { "parser" : "@typescript-eslint/parser" , "parserOptions" : { "project" :"./tsconfig.json" , "ecmaFeatures" : { "jsx" : true }, "ecmaVersion" : 2018 , "sourceType" : "module" }, "extends" :[ "plugin:@typescript-eslint/recommended" ], "plugins" : [ "@typescript-eslint" ], "rules" : { }, "ignorePatterns" : [ "webpack.config.js" , ".eslintrc.js" ] };
package.json添加脚本命令
1 2 3 4 5 "scripts" : { "dev" : "webpack-dev-server --mode=development --config ./build/webpack.config.js" , "build" : "webpack --mode=production --config ./build/webpack.config.js" , "lint" : "eslint src --ext .js,.ts --fix" } ,
1. 基本类型
boolean
number(支持二进制0b1101
,八进制0o173
,十六进制0x7b
)
string
数组:
number[], string[],......
Array<number'>....
联合类型 (string|number)[]
元组类型
1 let tuple :[string ,number ,boolean ] = ['a' ,1 ,false ] 长度和元素类型顺序都有要求
枚举类型
1 2 3 4 5 6 7 enum Users { CREATOR , ADMIN , USER = 3 } console .log (Users .CREATOR )console .log (Users [3 ])
any类型
void类型 (undefined,在非strict模式下可以是null)
1 2 3 4 5 const consoleText = (txt :string ):void => { console .log (txt) } consoleText ('123' )
undefined
null
never类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function error (message: string ): never { throw new Error (message); } function fail ( ) { return error ("Something failed" ); } function infiniteLoop ( ): never { while (true ) { } } const neverVal = ():never => { throw new Error ('abc' ) } myStr = neverVal ()
object(eslint不推荐使用)
1 2 3 4 5 6 function printObj (obj:object ):void { console .log (obj) } printObj ({ name : 'Samuel' })
类型断言 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const getLength = (target : (string | number )): number => { if ((<string >target).length || (target as string ).length === 0 ) { return (<string >target).length } else { return target.toString ().length } } getLength (123 )
类型断言有两种写法
(<类型>变量)
(变量 as 类型)
React只能这么写,因为<>会被当成标签
2 Symbol Symbols是不可改变且唯一的。主要用法是用来做对象的属性名,这样可以保证属性名独一无二不被mixin覆盖
1 2 3 4 5 6 7 8 9 10 11 let s1 = Symbol ();let s2 = Symbol ();s1 === s2 let s1 = Symbol ('foo' );let s2 = Symbol ('foo' );s1 === s2
类型转换 Symbol不可以和其他类型的值做运算,但是有toString方法,且可以转换成布尔
1 2 3 4 5 6 7 8 9 10 11 12 let sym = Symbol ('My symbol' );"your symbol is " + sym1 + symString (sym) sym.toString () Boolean (sym) !sym
用处 Symbol可以用来解决强耦合的某一个具体的字符串或数字
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 function getArea (shape, options ) { let area = 0 ; switch (shape) { case 'Triangle' : area = .5 * options.width * options.height ; break ; } return area; } const shapeType = { triangle : 'Triangle' }; function getArea (shape, options ) { let area = 0 ; switch (shape) { case shapeType.triangle : area = .5 * options.width * options.height ; break ; } return area; } const shapeType = { triangle : Symbol () };
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
1 2 3 4 5 6 7 8 9 10 const nameSymbol = Symbol ('name' )const obj = { [nameSymbol]: 'Samuel' , age :18 } for (const key in obj) { console .log (key) } console .log (Object .keys (obj)) console .log (Object .getOwnPropertyNames (obj))
但是可以被Object.getOwnPropertySymbols()
和Reflect.ownKeys()
获取
1 console .log (Object .getOwnPropertySymbols (obj))
Symbol.for(),Symbol.keyFor() 有时,我们希望重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点。
1 2 3 4 let s1 = Symbol .for ('foo' );let s2 = Symbol .for ('foo' );s1 === s2
Symbol.keyFor()
会返回一个使用Symbol.for(key)
登记过的key
1 2 3 4 5 let s1 = Symbol .for ("foo" );Symbol .keyFor (s1) let s2 = Symbol ("foo" );Symbol .keyFor (s2)
内置Symbol值 Symbol.hasInstance 对象的Symbol.hasInstance
属性,指向一个内部方法。当其他对象使用instanceof
运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo
在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
。
1 2 3 4 5 6 7 8 class MyClass { [Symbol .hasInstance ](foo) { console .log (foo) } } [1 , 2 , 3 ] instanceof new MyClass ()
Symbol.isConcatSpreadable 对象的Symbol.isConcatSpreadable
属性等于一个布尔值,表示该对象用于Array.prototype.concat()
时,是否可以展开。
1 2 3 4 5 6 7 let arr1 = ['c' , 'd' ];['a' , 'b' ].concat (arr1, 'e' ) arr1[Symbol .isConcatSpreadable ] let arr2 = ['c' , 'd' ];arr2[Symbol .isConcatSpreadable ] = false ; ['a' , 'b' ].concat (arr2, 'e' )
Symbol.species 对象的Symbol.species
属性,指向一个构造函数。创建衍生对象时,会使用该属性。
1 2 3 4 5 6 7 8 9 class MyArray extends Array {} const a = new MyArray (1 , 2 , 3 );const b = a.map (x => x);const c = a.filter (x => x > 1 );b instanceof MyArray c instanceof MyArray
子类MyArray
继承了父类Array
,a
是MyArray
的实例,b
和c
是a
的衍生对象。你可能会认为,b
和c
都是调用数组方法生成的,所以应该是数组(Array
的实例),但实际上它们也是MyArray
的实例。
1 2 3 4 5 6 7 8 9 class MyArray extends Array { static get [Symbol .species ]() { return Array ; } } const a = new MyArray ();const b = a.map (x => x);b instanceof MyArray b instanceof Array
上面代码中,a.map(x => x)
生成的衍生对象,就不是MyArray
的实例,而直接就是Array
的实例。
有关字符串的3个Symbol
Symbol.match
Symbol.replace
Symbol.split
1 2 3 String .prototype .replace (searchValue, replaceValue)searchValue[Symbol .replace ](this , replaceValue)
Symbol.iterator 对象的Symbol.iterator
属性,指向该对象的默认遍历器方法。
1 2 3 4 5 6 7 8 9 const myIterable = {};myIterable[Symbol .iterator ] = function * () { yield 1 ; yield 2 ; yield 3 ; }; [...myIterable] myIterable.next ()
Symbol.toPrimitive 当对象转换为基本类型时调用这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let obj = { [Symbol .toPrimitive ](hint) { switch (hint) { case 'number' : return 123 ; case 'string' : return 'str' ; case 'default' : return 'default' ; default : throw new Error (); } } }; 2 * obj 3 + obj obj == 'default' String (obj)
Symbol.toStringTag 对象的Symbol.toStringTag
属性,指向一个方法。这个属性可以用来定制Object.prototype.toString.call(obj)的返回的[object Object]
或[object Array]
中object
后面的那个字符串。
1 2 3 4 5 6 7 class Collection { get [Symbol .toStringTag ]() { return 'xxx' ; } } let x = new Collection ();Object .prototype .toString .call (x)
3. 接口 限制对象属性 使用接口限制对象的属性和属性类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface NameInterface { firstName : string , lastName : string } function getFullname ({firstName, lastName}: NameInterface ): string { return `${firstName} ·${lastName} ` } console .log (getFullname ({ firstName : "Samuel" , lastName : 'Sue' }))
可选属性 可选属性的限制
1 2 3 4 5 6 7 8 9 10 11 interface VegetableInterface { color?: string , type : string } function VegInfo ({color, type }: VegetableInterface ): string { return `${color || '' } ${type } ` } console .log (VegInfo ({type : 'Onion' }))
多余属性 对于多余属性,有三种方法解除属性限制
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 enum Gender { Female , Male , } interface PersonInfo { name : string , age : number , gender : Gender , [props : string ]: any } function logPerson ({name, age, gender}: PersonInfo ): void { console .log (`Person:${name} ,${age} years old,${gender} ` ) } logPerson ({ name : 'Samuel' , age : 25 , gender : Gender .Male , hobby :'Basketball' }) logPerson (({ name : 'Samuel' , age : 25 , gender : Gender .Male , hobby :'Basketball' }as PersonInfo )) const person = { name : 'Samuel' , age : 25 , gender : Gender .Male , hobby :'Basketball' } logPerson (person)
注意,剩余属性(属性索引)是规定了其他属性的类型的,举个例子:
1 2 3 4 interface Role { [prop :string ]:number , }
只读属性 1 2 3 4 5 6 7 8 interface ArrInterface { 0 : number , readonly 1 : string } const arr :ArrInterface = [123 ,'hahah' ]
属性类型 1 2 3 4 5 6 7 8 9 10 interface RoleDict { [id :string ]:number } const role :RoleDict = { 12 :123 , } console .log (role['12' ])
接口继承 1 2 3 4 interface Tomato extends VegetableInterface { radius : number }
函数接口 函数接口: 注意函数类型写法(arg:string,arg2:number):void
,即(参数:类型...):返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface CounterInterface { (): void , counter : number } const countFn = ():CounterInterface => { const fn = function ( ) { fn.counter ++ } fn.counter = 0 return fn } const countFunc :CounterInterface = countFn ()countFunc ()console .log (countFunc.counter )countFunc ()console .log (countFunc.counter )
4. 函数 函数类型 1 2 3 4 5 6 7 interface AddFnInterface { (x : number ,y : number ):number } type Add = (x: number , y: number ) => number const addFunc : Add = (x: number , y: number ) => x + y
可选参数,默认参数 1 2 3 type AddFnType = (x: number , y?: number ) => number const fn2 : AddFnType = (x = 3 , y ) => x + (y || 0 )
剩余参数 1 2 3 4 type SumFnType = (...args: number [] ) => number const fn3 : SumFnType = (...args ) => args.reduce ((previousValue, currentValue ) => previousValue + currentValue, 0 )console .log (fn3 (1 , 2 , 3 , 4 , 5 , 6 ))
函数重载 1 2 3 4 5 6 7 8 9 10 11 12 13 function splitChar (x: number ): string []function splitChar (x: string ): string []function splitChar (x: any ): any { if (typeof x === 'string' ) { return x.split ('' ) } else { return (x as number ).toString ().split ('' ) } } console .log (splitChar (123456 ))console .log (splitChar ('我是爸爸' ))
5. 泛型 对于函数,传入的参数类型事前无法确定,在调用时确定,且返回值类型也需要根据参数类型确定,同时对于这种动态的写法我们有时仍需要对类型做判断以调用不同类型对象的方法。
这时候我们需要的就是泛型
基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 const genArr = <T>(val : T, times : number ): T[] => { return new Array (times).fill (val) } type GenArr = <T>(val: T, times: number ) => T[]const genFn : GenArr = (val, times ) => { return new Array (times).fill (val) } genArr (123 ,3 ).map (item => item.toFixed ())console .log (genArr ('爸爸' ,3 ).map (item => item.split ('' )))
泛型约束(结合接口) 1 2 3 4 5 6 7 8 9 10 interface WithLength { length : number } const genFn2 = <T extends WithLength >(val : T, times : number ): T[] => { return new Array (times).fill (val) } genFn2 ([1 ,2 ,3 ],2 )genFn2 ('爸爸' ,2 )
理解泛型中的keyof
http://semlinker.com/ts-keyof/
https://juejin.cn/post/6844903826558812167
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 interface WithLength { length : number } const genFn2 = <T extends WithLength >(val : T, times : number ): T[] => { return new Array (times).fill (val) } interface Person { name : string ; age : number ; gender : string ; } class Teacher { constructor (private info:Person ){} getInfo<T extends keyof Person >(key : T):Person [T] { return this .info [T]; } } const teacher = new Teacher ({ name : 'sssssss' , age : 18 , gender : 'male' }) const test = teacher.getInfo ('age' );console .log (test);
typeof语法的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const COLORS = { red : 'red' , blue : 'blue' } type Colors = typeof COLORS let color : Colors ;color = 'red' color = 'blue' color = 'yellow'
泛型约束(结合索引类型) 1 2 3 4 5 6 7 8 9 10 11 12 function showProp<T, K extends keyof T>(obj : T, prop : K) { return obj[prop] } const person1 = { name : 'Samuel' , age : 26 , gender : 'Male' } console .log (showProp (person1,"age" ))
6. 类 es6的类 补充知识点:
set/get关键字操作属性值
私有化方法/属性(不是语法上的添加_
,真正意义上的私有化)
new.target
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Parent { constuctor ( ){ if (new .target ===Parent ){ throw new Error ('父类不允许实例化' ) } } } let privateSymbol = new Symbol ('name' )class Child extends Parent { constructor (name ){ super () this [privateSymbol] = name console .log (new .target ) } getName ( ){ return this [privateSymbol] } } export Child new Child ()
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 class Parent { constructor ( ){ this .name = 'Parent' } showName ( ){ return this .name } static getName ( ){ return Parent .name } } class Child extends Parent { constructor ( ){ super () this .name = 'Child' } showParentName ( ){ return super .showName () } static getParentName ( ){ return super .getName () } } Child .getParentName () new Child ().getParentName ()
TS中的类 属性修饰符
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 27 class Parent { protected x : number protected constructor (x: number ) { this .x = x } protected getX ( ) { return this .x } } class Child extends Parent { constructor (x: number ) { super (x); } getX (): number { return super .getX (); } } const ch = new Child (234 )console .log (ch.getX ())
只读属性 1 2 3 4 5 6 7 8 9 10 11 12 class ReadOnlyInfo { public readonly info : string constructor (info: string ) { this .info = info } } const rInfo = new ReadOnlyInfo ('只能读,不能改' )console .log (rInfo)
参数修饰符 1 2 3 4 5 6 7 8 9 class A { constructor (private name: string ) { } } const a = new A ('Samuel' )console .log (a)
静态成员属性 1 2 3 4 5 6 7 8 9 class B { public static readonly pi = 3.1415926 private static num = 2333 } console .log (B.pi )
可选属性/参数, 存取器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Human { private _name : string public age?: number constructor (name: string , age?: number , public gender?: string ) { this ._name = name this .age = age } public set name (newVal: string ) { this ._name = newVal } public get name () { return this ._name .replace (/./ , substring => substring.toUpperCase ()) } } const h = new Human ("samuel" )h.name = 'zhangboy' console .log (h.name )
抽象类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 abstract class MyFile { protected constructor (public size: number ) { } public abstract save (): void } class MediaFile extends MyFile { constructor (public size: number , public type : string ) { super (size) } save (): void { console .log ('save media file' ) } play (): void { console .log ('play media File' ) } }
类实现接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 interface player { supportType : string [], play : () => void } class MusicPlayer implements player { play (): void { console .log ('play music' ) } supportType : string []; constructor (...types: string [] ) { this .supportType = types } } const player = new MusicPlayer ('mp3' , 'wav' , 'wma' , 'acc' )player.play ()
接口继承类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Point { protected x : number ; protected y : number ; protected constructor (x: number , y: number ) { this .x = x this .y = y } } interface Point3 d extends Point { z : number ; } class AcPoint extends Point implements Point3 d { z : number ; constructor (x: number , y: number , z: number ) { super (x, y) this .z = 312 } }
类类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class TestE { public info : string constructor ( ) { console .log ('constructor exec' ) this .info = 'hhh' } } const testFn1 = <T>(c : new () => T): T => { return new c () } testFn1 (TestE )
7. 枚举 枚举值编号 1 2 3 4 5 6 7 8 9 10 const f = 400 enum Code { OK , Fault = f, Error = 3 , Success } console .log (Code [400 ])
字符串枚举 1 2 3 4 5 6 enum reply { Error = 'Sorry, an error has occurred' , Success = 'No problem' , Failed = Error , }
异构枚举(不推荐)1 2 3 4 5 enum XX { a = 1 , b = 'hehe' , }
枚举成员类型和联合枚举类型 如果一个枚举里所有成员的值都是字面量类型的值,那么这个枚举的每个成员和枚举本身都可以作为类型来使用。字面量枚举成员需满足以下条件:
不带初始值的枚举成员,例如 enum E { A }
值为字符串字面量,例如 enum E { A = 'hello' }
值为数字字面量,或者带有一元 -
符号的数字字面量,例如 enum E { A = 1 },enum E { A = -1 }
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 enum ShapeKind { Circle , Square } interface Circle { kind : ShapeKind .Circle radius : number } let c : Circle = { kind : ShapeKind .Square , radius : 100 } enum Status { on, off } interface Light { status : Status } const light :Light = { status : Status .off , }
const枚举 普通的枚举在编译后会实实在在创建一个对象(枚举名同名),然后让变量指向对象的值(枚举值),使用const枚举后不会创建这个对象,而是直接用
1 2 3 4 5 6 7 8 const enum Animal { Dog , Cat , GoldFish } const kitty = Animal .Cat
8. 类型推断和类型兼容 类型推断
ts会自动推断写的代码是什么类型
当你不需要ts自动推断类型的时候 需要用到类型断言
最佳通过类型推断
上下文类型推断
1 2 3 4 let a = 1 ;let b = [1 ,2 ]
类型兼容
当一个类型Y可以被赋值给另外一个类型X时,我们就可以说类型X兼容类型Y
结构之间兼容,成员少的兼容成员多的
函数之间兼容,参数多的兼容参数少的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let fn1 = (x : string , y : string ): void => { console .log (11 ) } let fn12 = (x : string ): void => { console .log (22 ) } fn1 = fn12 interface Aitf { name :string } interface Bitf { name :string , age :number } let aObj : Aitf = {name :'Samuel' }let bObj : Bitf = {name :'Samuel' ,age :25 }aObj = bObj
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function shk (x: number , y: number ): number function shk (x: string , y: string ): string function shk (x: any , y: any ): any { return x + y } function shl (x: number , y: number ): number function shl (x: any , y: any ): any { return x + y } let fna = shlfna = shk
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 class AC { protected num :number private age :number constructor (public name:string ,age:number ) { this .num = 13 this .age = age } } class ACsub extends AC { constructor (public name:string ,age:number ) { super (name,age); this .num = 15 } } class BC { constructor (public name:string ) { } } let ax :BC = new BC ('name' ) ax = new AC ('hh' ,17 ) ax = new ACsub ('hh' ,25 ) console .log (ax)
9. 高级类型 交叉类型 交叉类型是将多个类型合并为一个类型。 可以理解成逻辑与
1 2 3 4 5 const mergeObj = <T, U>(a : T, b : U): T & U => { let res = {} as T & U res = Object .assign (a, b) return res }
联合类型 可以理解成或
1 2 3 4 const showRes = (): string | number => { const arr = ['Samuel' , 123 ] return Math .random () > .5 ? arr[0 ] : arr[1 ] }
注意一点,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Bird { fly ():void layEggs ():void } interface Turtle { swim ():void layEggs ():void } function getSmallPet ():Bird |Turtle { return {} as Bird |Turtle } getSmallPet ().layEggs ()
类型保护 如果我们想确定变量的类型,比如是不是string类型或者其他类型,以往我们需要做大量的类型断言,ts提供类型保护机制,可以方便使用
1 2 3 4 5 6 7 8 9 10 function isString (x: string | number ): x is string { return typeof x === 'string' } const res1 = showRes () if (isString (res1)) { console .log ('string length:' , res1.length ) } else { console .log ('number:' , res1.toFixed ()) }
typeof做简单的类型保护
typeof能判断的类型有:string/number/boolean/symbol/undefined/function,其他都是object
ts中用typeof做类型保护只能判断string/number/boolean/symbol,其他类型不会识别为类型保护
1 2 3 4 5 6 if (typeof res1 === 'string' ) { console .log ('string length:' , res1.length ) } else { console .log ('number:' , res1.toFixed ()) }
intanceof做类型保护
判断是否是特定类创建的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Axx { public age = 13 } class Bxx { public name = 'Sam' } function getRandomAB ( ) { return Math .random () > .5 ? new Axx () : new Bxx () } const rsAB = getRandomAB ()if (rsAB instanceof Axx ) { console .log (rsAB.age ) } else { console .log (rsAB.name ) }
null/undefined类型断言 1 2 3 4 5 6 7 8 function getPrefixStr (x:number |null ) { function addPrefix (prefix:string ):string { return prefix + x!.toFixed ().toString () } x = x || 0.1 return addPrefix ('pr' ) }
类型别名 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Container <T> = { value : T };type Tree <T> = { value : T; left : Tree <T>; right : Tree <T>; } type LinkedList <T> = T & { next : LinkedList <T> };interface Person { name : string ; } var people : LinkedList <Person >;var s = people.name ;var s = people.next .name ;var s = people.next .next .name ;var s = people.next .next .next .name ;
type可以给抽象类取别名,但是注意别名不能使用extends,implement
1 2 3 4 5 6 7 8 9 10 11 12 13 14 abstract class AbsC { protected name :string protected constructor ( ) { this .name = 'Samuel' } } type absc = AbsC class CCC extends AbsC { constructor ( ) { super (); } }
字面量类型 1 2 3 4 5 6 7 8 9 type Direction = 'east' |'west' |'south' |'north' const DirFn = (direc:Direction ) => { console .log (direc) } DirFn ('west' )
可辨识联合
具有普通的单例类型属性 —可辨识的特征 。
一个类型别名包含了那些类型的联合—联合 。
此属性上的类型保护。
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 Square { kind : "square" ; size : number ; } interface Rectangle { kind : "rectangle" ; width : number ; height : number ; } interface Circle { kind : "circle" ; radius : number ; } type Shape = Square | Rectangle | Circle ; function area (s: Shape ) { switch (s.kind ) { case "square" : return s.size * s.size ; case "rectangle" : return s.height * s.width ; case "circle" : return Math .PI * s.radius ** 2 ; } }
索引类型 keyof
返回索引属性名构成的联合类型
1 2 3 4 5 6 interface ACtp2 { name : string , age : number } let k : keyof ACtp2
索引访问操作符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function pluck<T, K extends keyof T>(o : T, names : K[]): T[K][] { return names.map (n => o[n]); } interface Person { name : string ; age : number ; } let person : Person = { name : 'Jarid' , age : 35 }; let strings : string [] = pluck (person, ['name' ]);
在普通的上下文里使用T[K]
需要保证K extends keyof T
使用[]
意味着取出属性(值或类型)
1 2 3 4 5 6 7 8 interface Types { a : number , b : string , c : boolean , } type AllTypes = Types [keyof Types ]
映射类型 简单例子:将一个类型的所有属性都变成可读
1 2 3 type ReadOnlyTypes <T> = { readonly [P in keyof T]: T[P] }
内置的映射类型:
Readonly 只读
Partial 可选
Pick<T,K> T里面保留K(可以是联合类型)属性
Record<K extends keyof any, T> 创建一个类型,K代表键值的类型, T代表值的类型
1 2 3 4 5 6 7 8 9 10 function record<K extends string | number , T, U>(obj : Record <K, T>, fn : (x: T ) => U): Record <K, U> { const res : any = {} for (const key in obj) { res[key] = fn (obj[key]) } return res } console .log (record (person2, (p ) => p.toString ().length ))
Omit<T,K> 实现排除已选的属性
1 2 3 4 5 6 type User = { id : string , name : string , email : string } type UserWithoutEmail = Omit <User , 'email' >;
Exclude<T,U>
从T中提出可以赋值给U的类型
Extract<T, U>
-- 提取T
中可以赋值给U
的类型。
NonNullable<T>
-- 从T
中剔除null
和undefined
。
ReturnType<T>
-- 获取函数返回值类型。
InstanceType<T>
-- 获取构造函数类型的实例类型。
可以显式的指定添加或着删除修饰符
1 2 3 4 5 6 type RemoveReadonly <K> = { -readonly [P in keyof K]: K[P] } type noReadOnlyTypes = RemoveReadonly <readTypess>
unknown类型
any 和 unknown https://zhuanlan.zhihu.com/p/104296850
任何类型都可以赋值给unknown类型
在没有类型断言或者基于控制流的类型细化时,unknown不可以赋值给其他类型
在没有类型断言或者基于控制流的类型细化时,不可以对unknown类型对象进行任何操作
unknown与其他类型组成交叉类型, 就等于其他类型
unknown与其他类型组成联合类型, 等于unknown
never是unknown的子类型
keyof unknown得到never
unknown类型只能进行===或!==操作, 不能进行其他操作
unknown类型不能访问属性,作为函数调用以及作为类创建对象
使用类型映射时如果映射的属性是unknown类型,就不会产生映射
1 2 3 4 5 6 type NumsType <T> = { [P in keyof T]: number } type xxd = NumsType <any > type xxs = NumsType <unknown >
条件类型 extends条件类型
1 2 3 type Typs <T> = T extends string ? string : number let vals2 : Typs <'2' > let vals3 : Typs <false >
分布式条件类型
1 2 3 type onlyString<T> = (T extends string ? T : never )type someTypes = 'a' | 'b' | 123 | false type resultTypes = onlyString<someTypes>
条件类型加索引类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type reserveFunction<T> = { [P in keyof T]: T[P] extends () => void ? P : never }[keyof T] interface Part { user : 'Sam' , code : 132 , subparts : Part [] updateTime :()=> void , thankU (): void } type Res2 = reserveFunction<Part >const obj2 : Res2 = 'updateTime'
内置条件类型
Exclude<T,U> 从T里面排除U
1 type typesA = Exclude <'a' |'b' |'c' , 'a' |'b' >
Extract<T,U> 从T中选取可以赋值给U的类型
1 type typesB = Extract <'a' |'b' |'c' , 'a' |'b' >
NonNullable<T> 去掉null类型(undefined,null)
1 type typesC = NonNullable <'a' | 'b' | 'c' | null | undefined >
ReturnType<T> T的返回值类型
1 type typesD = ReturnType <() => string >
InstanceType<T> ts中类有2种类型, 静态部分的类型和实例的类型, 所以T如果是构造函数类型, 那么InstanceType可以返回他的实例类型
1 2 3 4 5 6 7 8 9 10 11 12 class AClass { constructor ( ) { console .log ('AClass' ) } } interface AConstructor { new (): string } type typeE = InstanceType <typeof AClass > type typeF = InstanceType <AConstructor >
Parameters<T> 获取函数参数类型
1 2 3 4 5 interface A{ (a :number , b :string ):string []; } type A1 = Parameters <A>
ConstructorParameters 获取构造函数的参数类型
1 2 3 4 5 6 7 class AClass { constructor (public name:string ) { console .log ('AClass' ) } } type typeG = ConstructorParameters <typeof AClass >
推断元素类型 1 2 3 4 5 6 7 8 9 10 type Type2 <T> = T extends any [] ? T[number ] : T type typesX = Type2 <string []> type typesX2 = Type2 <boolean []> type Type3 <T> = T extends Array <infer U> ? U : Ttype typesY = Type3 <string []> type typesY2 = Type3 <boolean []> type typesY3 = Type3 <[false ,123 ,'sam' ]>
1 2 3 4 5 type Parameters <T extends (...args : any ) => any > = T extends (...args : infer P) => any ? P : never ;type ConstructorParameters <T extends new (...arg : any ) => any > = T extends new (...arg : infer P) => any ? P : never
10. 命名空间 为了防止全局变量过多,使用命名空间来管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 namespace Components { export class Header { constructor ( ) { const elem = document .createElment ('div' ); elem.innerText = 'this is header' ; document .body .appendChild (elem); } } export class Content {} export class Footer {} export interface User { name :string } export namespace Sub { export class Test {} } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 namespace Home { export class Page { constructor ( ) { new Components .Header (); new Components .Content (); new Components .Footer (); } } }
这个不太好用,好像没有挺多应用。
在给第三方库的描述文件中有很多引用。
11. 描述文件 针对全局的描述文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 declare var $ : (param:()=>void ) => void ;interface JqueryInstance { html : (html:string ) => JqueryInstance } declare function $ (readyFunc: ()=>void ):void declare function $ (selector:string ):JqueryInstance declare namespace $ { namespace fn { class init {} } }
针对模块的描述文件
1 2 3 4 5 6 7 8 9 10 11 12 13 declare module 'jquery' { interface JqueryInstance { html : (html:string ) => JqueryInstance } declare function $ (readyFunc: ()=>void ):void declare function $ (selector:string ):JqueryInstance export = $; }
12. 装饰器 装饰器本身就是一个函数
类装饰器 类装饰器接收的参数是构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function testDecorator (constructor :new (...args :any [])=>any ) { constructor.prototype .getName = () => { console .log ('dell' ) } console .log ('decorator' ) } function testDecorator1 (constructor :any ) { console .log ('decorator1' ) } @testDecorator1 @testDecorator class Test {}(new Test () as any ).getName ()
装饰器如果要支持接收参数,那么可以用闭包做装饰器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function decor1 (flag :boolean ) { if (flag) { return function (constructor:any ) { constructor.prototype .info = () => { console .log ('dell' ) } } } return function (constructor:any ) {} } @decor1 (true ) class Test1 {}
如果类装饰器返回一个值,他会使用提供的构造函数来替换类的声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function testDecor<T extends new (...args :any [])=>any >(constructor :T) { return class extends constructor { name = 'lee' ; getName () { console .log (this .name ) } } } @testDecor class Person { name : string ; constructor (name :string ) { this .name = name } } (new Person ('sam' ) as any ).getName ()
装饰器工厂函数写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function decoratorFactory () { return function <T extends new (...args :any [])=>any >(constructor :T) { return class extends constructor { name = 'lee' ; getName () { console .log (this .name ) } } } } const Person1 = decoratorFactory ()(class { name : string ; constructor (name :string ) { this .name = name } }) new Person1 ('sam' ).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 function funcDecor (target : any , key :string , descriptor :PropertyDescriptor ) { console .log (target) console .log (key) console .log (descriptor) const oldVal = descriptor.value as (...args :any [])=> any descriptor.value = function (...args:any [] ) { return oldVal.apply (this , args) + 'NO!!!!!!!!!!!' } } class UObj { name :string constructor (name :string ) { this .name = name } @funcDecor getName () { return this .name } } const objU = new UObj ('sam' )console .log (objU.getName ())
访问器装饰器 注意: set和get不能同时绑定,目前用处不知道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function visitDecor (target :any , key :string , descriptor :PropertyDescriptor ) { console .log ('descriptor:' , descriptor) } class XObj { private _name :string ; constructor (name :string ) { this ._name = name } @visitDecor get name () { return this ._name } set name (name :string ) { this ._name = name } }
访问器装饰器和方法装饰器区别:
访问器装饰器的descriptor有set,get, 没有value和writable
方法装饰器没有set/get,有value和writable
属性装饰器 属性装饰器不接受descriptor参数,但是可以返回一个新的descriptor来覆盖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function propDecorator (target :any , key :string ):any { target[key] = 'NoNoNO' } class XaObj { @propDecorator name='Dell' } const xao = new XaObj ()xao.name = 'Samuel' console .log (xao.name ) console .log ((xao as any ).__proto__ .name )
参数装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function paramDecorator (target : any , method : string , paramIndex :number ) { console .log (target, method, paramIndex) } class Sth { getInfo (name :string , @paramDecorator age : number ) { console .log (name, age) } } new Sth ().getInfo ('Sam' , 30 )
执行顺序
元数据可以理解为在任意对象上挂载了一个Map,用于绑定键值信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import 'reflect-metadata' function Role (name :string ):ClassDecorator { return target => { Reflect .defineMetadata ('role' , name, target.prototype ) console .log (target) } } @Role ('admin' )class Post {}const metaData = Reflect .getMetadata ('role' , Post .prototype )console .log (metaData)const np = new Post ()const anotherMd = Reflect .getMetadata ('role' , (np as any ).__proto__ )console .log (anotherMd)
13. 问题 import type
https://juejin.cn/post/6844904034952806414