前言 官方文档:TypeScript中文网
基础数据类型
和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。这些浮点数的类型是 number
。除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015中引入的二进制和八进制字面量。
1 2 3 let a : string = "abc" let b : number = 123 let c : boolean = true
模板字符串 使用模板字符串可以定义多行文本和内嵌表达式
使用 **(`)符号包裹 ******,并且以 ${expr}
这种这种形式嵌入表达式
1 2 let name : string = 'tom' ;let hello : string = `my name is ${name} `
数组 Array
直接在类型后加上 []
1 let list : number [] = [1 , 2 , 3 ];
使用数组泛型,Array<元素类型>
1 let list : Array <number > = [1 , 2 , 3 ];
元组 Tuple
存储的数据类型可不相同,元素数量和类型是固定的
1 2 let x : [stirng, number ];x = ['hello' , 10 ]
枚举 enum
能为一组数值赋值为对应的编号
1 2 3 4 5 6 enum Color {red, green, blue}enum Color {red = 0 , green = 1 , blue = 2 }enum Color {red = 1 , green = 2 , blue = 4 }
根据键名赋键值,类型可以为 number 也可以为枚举对象
1 2 3 enum Color {red, green, blue}let x : Color = Color .red let x : number = Color .green
根据键值赋键名
1 2 enum Color {red, green, blue}let x : string = Color [0 ]
Any 在变量类型位置时,可以使用 any
类型
1 2 3 let x : any [] = [];x.push ('Hello' ); x.push (1 );
Void void
通常用于没有返回值的函数
1 2 3 funcation fun (): void { console .log ("hello,world" ) }
void
类型类型的变量只能赋值为 null
和 undefined
1 let unusable : void = undefined ;
Null 和 Undefined
在默认情况下,null 和 undefined 是所有类型的子类 ,可以将 null 和 undefined 赋值给任何类型的变量
但在严格模式下,null 和 undefined 只能赋值给它们对应的类型,在这个模式下如果想传入一个 string 或 null 或 undefined 类型,可以使用联合类型 string|null|undefined
1 2 let x : string |null |undefined = null
never never
类型表示不存在值的类型,用于抛出异常的函数,或无限循环下去的函数
1 2 3 function error ( ): never { throw new Error ("error" ) }
Object Object 表示非原始类型,是除了 number
,string
,boolean
,symbol
,null
或 undefined
之外的类型
可以便于更好使用像 Object.create
这样的 API
使用 typeof
函数返回非原始类型变量都是 Object
1 2 3 4 5 6 7 declare function create (o: object | null ):void ; create ({ prop : 0 }); create (null ); create (42 ); create ("string" ); create (false ); create (undefined );
类型断言 第一种方式:使用 <>
(不推荐)
1 let someValue : any = "this is a string" ; let strLength : number = (<string >someValue).length ;
第二种方式:使用 as
1 2 let someValue : any = "this is a string" ;let strLength : number = (someValue as string ).length ;
在 tsx 中(react 项目里)由于尖括号和标签冲突,只能使用 as
的形式
变量声明 var、let 和 const
使用 var
声明一个变量,在其他函数内部是可以访问的,正是因为 var 存在着这样的问题,我们更推荐使用 let
let 声明的变量只能在它的语法作用域或者块作用域使用
const
的作用域与 let
类似,只是 const
在被创建后不能改变
解构 数组解构 语法:let [x, y] = array
1 2 3 4 let input = [1 , 2 ]; let [first, second] = input; console .log (first); console .log (second);
1 2 3 4 5 6 let input = [1 , 2 ];function f ([first, second]: [number , number ] ) { console .log (first);console .log (second); } f (input);
1 2 3 let [frist, ...rest] = [1 , 2 , 3 , 4 , 5 ]console .log (frist)console .log (rest)
1 let [, second, , fourth] = [1 , 2 , 3 , 4 ];
对象解构 语法: let {x, y} = class
,结构的变量名要和对象的键名对应
下面的代码里的 a 和 b 没有指明类型,ts 也能正常运行,这是因为当你使用对象解构时,编译器会自动判断对象属性的类型
1 2 let o = { a : "foo" , b : 12 , c : "bar" }; let { a, b } = o;
将对象解构用于函数声明中
type 作用就是给类型起一个新名字,和接口一样,用来描述对象或函数的类型,?
表示该类型可选
1 2 3 4 5 type C = { a : string , b?: number } function f ({ a, b }: C ): void {}function f ({ a, b }: {a:string , b:number } ): void {}
展开 三个点 ...
被称为扩展运算符。用于可迭代对象展开到每个元素
合并数组
1 2 3 const arr1 = [1 , 2 , 3 ];const arr2 = [4 , 5 , 6 ];const mergedArray = [...arr1, ...arr2];
复制数组
1 2 const originalArray = [1 , 2 , 3 ];const newArray = [...originalArray];
添加元素
1 2 3 const arr1 = [1 , 2 , 3 ];const arr2 = [4 , 5 , 6 ];arr1.push (...arr2);
函数参数
1 2 3 4 5 6 function myFunction (a: number , b: number , c: number ) { console .log (a + b + c); } const args = [1 , 2 , 3 ];myFunction (...args);
对象扩展
1 2 3 4 const obj1 = { a : 1 , b : 2 };const obj2 = { c : 3 , d : 4 };const mergedObject = { ...obj1, ...obj2 };
接口 与 type
类似,都能用于定义变量的类型
1 2 3 interface InterfaceExample { key : string ; } type TypeExample = { key : string ; }
接口和类型的区别
interface 与 type 能相互扩展:
1 2 3 4 5 6 7 8 9 10 11 12 interface Name { name : string ; } interface User extends Name { age : number ; }type Name = { name : string ; }type User = Name & { age : number };type Name = { name : string ; } interface User extends Name { age : number ; }interface Name { name : string ; }type User = Name & { age : number ; }
interface
可以定义多次,属性会被合并,而 type
不能被定义多次
1 2 3 interface User { name : string age : number } interface User { sex : string } let user :User ={name :'wang' ,age :1 ,sex :'man' }
type
能定义映射类型
1 2 3 4 5 6 7 8 9 10 11 12 interface aType { name : string ; age : number ; }type ReadonlyType <T> = { readonly [key in keyof T]: T[key]; }; type newType = ReadonlyType <aType>
type
能定义条件类型
1 2 3 4 5 6 7 8 9 type arrayType = number [];type ElementType <T> = T extends Array <infer U> ? U : T;type newType = Elementtype <arraytype>
可选属性 在属性后加 ?
,则该属性是可有可无的
1 2 3 4 5 6 7 8 interface IPerson { name : string ; age?: number ; } let tom : Iperson = { name : 'tom' , }
任意属性 语法:[propName: type]: any
,能允许类型添加别的属性
1 2 3 4 5 6 7 8 interface IPerson { name : String ; [propName : string ]: any } let tom : Iperson = { name : 'tom' , gender : 'male' }
只读属性 被创建后不能被修改,用于对象在创建后就不能更改的属性
1 2 3 4 5 6 interface IPerson { readonly id : string ; name : string } let p1 : IPerson = { id :001 , name :wdm};p1.id = 002 ;
只读数组类型 ReadonlyArray<T>
类型与 Array<T>
类似,只不过都是不可变的数据
1 2 3 4 5 6 let a : number [] = [1 , 2 , 3 , 4 ];let ro : ReadonlyArray <number > = a;ro[0 ] = 12 ; ro.push (5 ); ro.length = 100 ; a = ro;
在最后一行代码,将 ReadonlyArray
赋值给一个普通的数组也是不行的,但是可以用类型重写,将 ReadonlyArray
强制转换为数组
可变属性 如果要使类型的属性可变,可以使用 [propName: string]: any
1 2 3 4 5 6 7 8 interface SquareConfig { color?: string ; width?: number ; [propName : string ]: any ; } let squareOptions = { colour : "red" , width : 100 };let mySquare = createSquare (squareOptions);
函数类型 接口能描述变量类型外,还能描述函数类型
1 2 3 interface SearchFunc { (source : string , subString : string ): boolean ; }
这能用于创建一个函数类型的变量,将一个函数赋值给一个变量
1 2 3 4 5 let mySearch : SearchFunc ;mySearch = function (source: string , subString: string ) { let result = source.search (subString); return result > -1 ; }
函数里的参数名不必与接口定义的参数名相同
同时也可以不指定类型, TypeScript 会推断出参数的类型,因为已经指定了变量的函数类型
1 2 3 4 5 let mySearch : SearchFunc ;mySearch = function (src, sub ) { let result = src.search (sub); return result > -1 ; }
可索引类型 用于描述哪些可以通过索引得到数据的类型,类似于数组和字典,比如 a[10]
或 ageMap["daniel"]
只能使用 string
或 number
来作为索引
1 2 3 4 5 6 7 8 interface StringArray { [index : number ]: string ; } let myArray : StringArray ;myArray = ["Bob" , "Fred" ]; let myStr : string = myArray[0 ];
也可以同时使用 string
和 number
作为索引
下面的代码在使用 number
索引时,由于 Animal
是父类型,也会匹配到 Dog
,因此发生冲突 正确的做法是 number
返回的类型是 string
返回的类型的子类型
因为 number
实际上在 typescript 内部执行的时候会转换为 string
,所以这也要求 string
索引本身对应的类型是兼容所有的,也就是说 string 索引对应的类型必须是其他类型的父类。
举一个反例,比如 number
索引返回的类型是 Animal
,TS 转换成 string
来去查找,但是 string
索引返回的类型是 Dog
,Animal
不是 Dog
类型,编译器会报错。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Animal { name : string ; } class Dog extends Animal { breed : string ; } interface NotOkay { [x : number ]: Animal ; [x : string ]: Dog ; } interface NotOkay { [x : number ]: Dog ; [x : string ]: Animal ; }
字符串索引能够描述 dictionary
形式的变量,但是要保证所有 string
类型的属性返回值都和字符串索引的返回类型一致
因为字符串索引声明了 obj.property
和 obj["property"]
两种形式都可以
1 2 3 4 5 interface NumberDictionary { [index : string ]: number ; length : number ; name : string }
如何为既有索引又有普通属性的变量初始化:
1 2 3 4 5 const dict : NumberDictionary = { '1' : 2 , '2' : 3 , length : 2 , };
类的类型 和 java 中的接口作用一致,用于抽象一个类的数据结构(ADT)
1 2 3 4 5 6 7 8 9 10 11 12 interface ClockInterface { currentTime : Date ; setTime (d : Date ); } class Clock implements ClockInterface { currentTime : Date ; setTime (d: Date ) { this .currentTime = d; } constructor (h: number , m: number ) { } }
接口继承 和类一样,接口也可以继承一个或多个接口
1 2 3 4 5 6 7 8 9 10 11 interface Shape { color : string ; } interface Square extends Shape { sideLength : number ; } let square = <Square >{};square.color = "blue" ; square.sideLength = 10 ;
混合类型 使用接口的混合类型,你会得到一个对象同时也能当作函数来使用
创建混合类型对象的步骤:
先通过函数表达式,将函数分配给一个变量
再使用 Object.assign()
方法将对象属性分配给该变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface Counter { (start : number ): string ; interval : number ; reset (): void ; } function getCounter ( ): Counter { const counterFunction = function (start: number ): string { return '' ; }; const counter : Counter = Object .assign (counterFunction, { interval : 123 , reset : function ( ) { } }); return counter; } let c = getCounter ();c (10 );c.reset (); c.interval = 5.0 ;
接口继承类 当一个接口继承了类后,该接口只能被这个类或其子类所实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Control { private state : any ; } interface SelectableControl extends Control { select (): void ; } class Button extends Control implements SelectableControl { select ( ) { } } class Image implements SelectableControl { select ( ) { } }
类 继承 属性修饰符
修饰符
子类
实例
能否被修改
public(默认)
√
√
√
protected
√
×
√
private
×
×
√
readonly
关键字用于将类成员声明为只读 。这意味着成员的值只能在声明或构造函数 中进行设置,不能在其他地方修改。readonly
可以与public
、private
和protected
修饰符结合使用。
public 在TypeScript里,成员都默认为public
。
1 2 3 4 5 6 7 class Animal { public name : string ; public constructor (theName: string ) { this .name = theName; } public move (distanceInMeters: number ) { console .log (`${this .name} moved ${distanceInMeters} m.` ); } }
protected protected
成员只能在类和子类中可以访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person { protected name : string ; constructor (name: string ) { this .name = name; } } class Employee extends Person { private department : string ; constructor (name: string , department: string ) { super (name) this .department = department; } public getElevatorPitch ( ) { return `Hello, my name is ${this .name} and I work in ${this .department} .` ; } } let howard = new Employee ("Howard" , "Sales" );console .log (howard.getElevatorPitch ());console .log (howard.name );
构造函数也可以被标记成 protected
。这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Person { protected name : string ; protected constructor (theName: string ) { this .name = theName; } } class Employee extends Person { private department : string ; constructor (name: string , department: string ) { super (name); this .department = department; } public getElevatorPitch ( ) { return `Hello, my name is ${this .name} and I work in ${this .department} .` ; } } let howard = new Employee ("Howard" , "Sales" );let john = new Person ("John" );
TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。
然而,当我们比较带有 private
或 protected
成员的类型的时候,情况就不同了。如果其中一个类型里包含一个 private
成员,那么只有当另外一个类型中也存在这样一个 private
成员,并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。对于 protected
成员也使用这个规则。
下面来看一个例子,更好地说明了这一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Animal { private name : string ; constructor (theName: string ) { this .name = theName; } } class Rhino extends Animal { constructor ( ) { super ("Rhino" ); } } class Employee { private name : string ; constructor (theName: string ) { this .name = theName; } } let animal = new Animal ("Goat" );let rhino = new Rhino ();let employee = new Employee ("Bob" );animal = rhino; animal = employee;
private 当成员被标记成private
时,它就不能在声明它的类的外部访问
1 2 3 4 5 6 class Animal { private name : string ; constructor (theName: string ) { this .name = theName; } } new Animal ("Cat" ).name ;
readonly 你可以使用 readonly
关键字将属性设置为只读的。只读属性必须在声明时或构造函数里被初始化。
1 2 3 4 5 6 7 8 9 class Octopus { readonly name : string ; readonly numberOfLegs : number = 8 ; constructor (theName : string ) { this .name = theName; } } let dad = new Octopus ("Man with the 8 strong legs" );dad.name = "Man with the 3-piece suit" ;
存取器 TypeScript 支持通过 get
和 set
来截取对对象属性的操作。它能帮助你有效的控制对对象成员的读取和修改。
先将属性定义为 private
, get
用来截取对象属性的读取, set
用来截取对象属性的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 let passcode = "secret passcode" ;class Employee { private _fullName : string ; get fullName (): string { return this ._fullName ; } set fullName (newName: string ) { if (passcode && passcode === "secret passcode" ) { this ._fullName = newName; } else { console .log ("Error: Unauthorized update of employee!" ); } } } let employee = new Employee ();employee.fullName = "Bob Smith" ; if (employee.fullName ) { alert (employee.fullName ); }
静态属性 static
修饰符使得该属性只存在于该对象中,所有由该对象创建的实例都使用同一个属性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Grid { static origin = {x : 0 , y : 0 }; calculateDistanceFromOrigin (point: {x: number ; y: number ;} ) { let xDist = (point.x - Grid .origin .x ); let yDist = (point.y - Grid .origin .y ); return Math .sqrt (xDist * xDist + yDist * yDist) / this .scale ; } constructor (public scale : number ) { } } let grid1 = new Grid (1.0 ); let grid2 = new Grid (5.0 ); console .log (grid1.calculateDistanceFromOrigin ({x : 10 , y : 10 }));console .log (grid2.calculateDistanceFromOrigin ({x : 10 , y : 10 }));
抽象类 抽象类做为其它派生类的基类使用,它们一般不会直接被实例化。不同于接口,抽象类可以包含成员的实现细节
函数 函数声明和函数表达式 函数声明和函数表达式的区别: - 函数声明会在浏览器在执行代码时被扫描,创建一个变量来储存指向该函数的引用 - 函数表达式则是将函数赋值给一个变量来储存
1 2 3 4 5 6 7 8 9 10 11 12 function add (x: number ,y: number ): number { return x + y; } let add = function (x: number , y: number ): number { return x + y; } let add = (x : number , y : number ): number => { return x + y }
接口定义函数 接口内要用 ()
包裹函数的参数
1 2 3 4 5 6 interface Add {(x : number , y : number ): number ; } let add : IAdd = function (x: number , y: number ): number { return x+y };
可选参数 参数后加 ?
表示该参数是可选的,要对没有传入可选参数的情况做一个判断,通常可选参数都有个默认值,也就是默认参数
1 2 3 4 5 6 7 8 function name (firstName: string , lastName?:string ){ if (lastName){ return firstName+' ' +lastName; }else { return firstName; } } name ('lilming' );
默认参数 可选参数和默认参数类似,可以有也可以没有。但是可选参数必须在必选参数后面;默认参数没有这个限制
1 2 3 4 function name (lastName:string = 'wang' ,firstName: string ){ return firstName + ' ' + lastName; } name ('daiming' );
获取剩余参数 ...
在变量之前可以获取剩下的其他变量
1 2 3 4 5 6 7 function push (a:any [], ...items:any [] ){ items.forEach ((item )=> { a.push (item) }) } let a = [1 ,2 ,3 ]push (a,4 ,5 ,6 ,7 )
函数重载 利用联合类型参数,通过 if
判断参数类型
1 2 3 4 5 6 7 8 9 function reverse (x: number ): number ;function reverse (x: string ): string ;function reverse (x: number | String ): number | String | void { if (typeof x ==='number' ){ return Number (x.tostring .split ('' ).reverse ().join ('' )); }else { return x.split ('' ).reverse .join ('' ); } }
this 和箭头函数 箭头函数会捕获包含它们的上下文中的 this
值,箭头函数中的 this
和外层的 this
是一致的。而普通函数中的 this
指向全局对象(在浏览器中是 window
,在 Node.js 中是 global
)
下面的例子中,一个对象的函数返回了一个函数,被返回的函数内使用了该对象的属性,则该函数要用箭头函数 ,而不是普通的函数
因为在该函数被创建之后(被赋值了之后),他的 this
需要是对象的,而不是它自身的,这就需要使用箭头函数
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 interface Card { suit : string ; card : number ; } interface Deck { suits : string []; cards : number []; createCardPicker (this : Deck ): () => Card ; } let deck : Deck = { suits : ["hearts" , "spades" , "clubs" , "diamonds" ], cards : Array (52 ), createCardPicker : function (this : Deck ) { return () => { let pickedCard = Math .floor (Math .random () * 52 ); let pickedSuit = Math .floor (pickedCard / 13 ); return {suit : this .suits [pickedSuit], card : pickedCard % 13 }; } } } let cardPicker = deck.createCardPicker ();let pickedCard = cardPicker ();alert ("card: " + pickedCard.card + " of " + pickedCard.suit );
重载 如果一个函数可能会有不同类型的参数传入,采用可选参数和 if
来处理会使代码可读性下降很多,可以采用函数重载来限定不同类型的参数
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function padding (all: number );function padding (topAndBottom: number , leftAndRight: number );function padding (top: number , right: number , bottom: number , left: number );function padding (a: number , b?: number , c?: number , d?: number ) { if (b === undefined && c === undefined && d === undefined ) { b = c = d = a; } else if (c === undefined && d === undefined ) { c = a; d = b; } return { top : a, right : b, bottom : c, left : d }; }
之后在调用时,就只能使用前面三种的参数形式
1 2 3 4 5 padding (1 ); padding (1 , 1 ); padding (1 , 1 , 1 , 1 ); padding (1 , 1 , 1 );
64 4 16 20 16
数组处理 map
对数组的每一个元素进行操作
filter
对数组的元素进行筛选
reduce
按顺序对数组的元素进行操作,并将返回的结果传递到下一次
1 2 3 4 5 6 7 8 9 10 11 <template> {{arr}} </template> <script lang="ts" setup> import { onMounted, ref } from 'vue'; const arr = ref<number[]>([]) onMounted(() => { arr.value.push(1) // 在onMounted内部不触发更新 }); arr.value.push(2) // 触发更新 </script>