npm i -g typescript
tsc -v
Version 5.1.6
示例代码:
let age:number = 18
注意:说明上序代码中的 :number 就是类型注解
作用:为代码添加类型约束,约定了是什么类型就只能给该类型付同类型的值。否则会报错!
1、数组类型的两种写法,推荐用第一种
let arrNumber: number[] = [1, 2, 3]
let arrString: Array = ['1', '2', '3']
注意:一个数组中存在多个类型的数据
//加上小括号说明,数组内的类型可以是小括号里的任何类型
let arr: (number | string | null)[] = [null, 1, '23']
//不加小括号,说明arr即可以是Number类型,也可以是string类型的数组
let arr2: number | string[] = 123
let arr3: number | string[] = ['1', '2', '3']
解释:|在ts中叫联合类型
type CustomArray = (number | string)[]
let arr1: CustomArray = ['1', 1]
let arr2: CustomArray = ['1', 1]
注意:
注意:
function add(number1:number,number2:number):number{
return number1 + number2
}
console.log(add(1,2));
let add = (number1: number, number2: number): number => {
return number1 + number2
}
console.log(add(1, 3));
注意:这种方法只能用于函数表达式
let add: (number1: number, number2: number) => number = (number1, number2) => {
return number1 + number2
}
console.log(add(1, 3));
注意:如果函数没有返回值,那么函数返回值就是void(就是空值的意思)
function onVoid(num:number):void{
console.log(num);//执行结果:12
}
onVoid(12)
注意:使用函数实现某个功能时,函数参数可传可不传。这种情况在给函数指定参数类型时,就用到可选参数,
可选参数:在可传可不传的参数后面加 ?
注意:可选参数后面不能再写必传参数
function mySlice(start?: number, end?: number) {
console.log('开始', start, '结束', end);
}
mySlice()//开始 undefined 结束 undefined
mySlice(1)//开始 1 结束 undefined
mySlice(1, 2)//开始 1 结束 2
注意:js中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么类型的属性和方法)
解释:
对象类型写法:
let obj: { name: string; age: number; onSay(): void } = {
name: '李玉',
age: 21,
onSay() {
console.log('111');
}
}
let obj2: {
name: string
age: number
onSay: (e:number) => void
} = {
name: '简隋英',
age: 28,
onSay(e) {
console.log(e);
}
}
注意:
function myAxios(config: { url: string, methods?: object }) {
}
let config = {
url:'https://www.baidu.com'
}
myAxios(config)
注意:当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的效果。
解释:
interface IPeron {
name: string
age: number
onSay: () => void
}
let obj: IPeron = {
name: '李玉',
age: 21,
onSay() {
}
}
interface(接口)和type(类型别名)的对比
* 接口interface只能为对像指定类型
* 类型别名type,不仅可以对对象指定类型,实际上可以为任意类型指定别名
*type用=(等号),interface不用= (等号)
interface例子:
interface IPeron {
name: string
age: number
onSay: () => void
}
let obj: IPeron = {
name: '李玉',
age: 21,
onSay() {
}
}
type例子:
type IPeron = {
name: string
age: number
onSay: () => void
}
let obj: IPeron = {
name: '李玉',
age: 21,
onSay() {
}
}
type例子:
type NumStr = number | string
注意: 如果两个接口之间有相同的属性或方法,可以将公共属性和方法抽离出来,通过继承来实现复用。
如下列,这两个接口都有x,y属性,重复写两次太过繁琐。
interface IPearn1 {
x:string,
y:number
}
interface Ipearn2{
x:string,
y:number,
id:number
}
更好的方式:使用extends关键字的方法去实现继承
interface IPearn1 {
x:string,
y:number
}
interface Ipearn2 extends IPearn1{
id:number
}
场景:
在地图中使用经纬度来标记坐标信息。
可以使用数组来标记坐标,那么,该数组中只有两个元素,并且这两个元素都是数值类型。
let position: number[] = [12, 23]
注意:更好的方法是使用元组(Tuple),元组类型是另一种类型的数组,它确切的知道包含多少个元素,以及特定索引对应的类型。
let position: [number, number] = [12, 45]
解释:
注意:在TS中,某些没有声明却指出类型的地方,TS的类型推论机制会帮助提供类型。所以,因为有类型推论的存在,有的地方可以沈略类型注解( ; )
let aa = 12
let bb = 'dddd'
function add(num1: number, num2: number) {
return num1 + num2
}
add(1, 2)
注意:有时你会比TS更加明确一个值的类型,此时,可以使用类型断言来指定更具体的类型
比如:
注意:getElementById方法返回值的类型是HTMLElement,该类型只包含所有标签的公共属性或方法,不包含a标签特有的href等属性。
因此,这个类型太宽泛(不具体),无法超做href等a标签特有的属性的方法。
解决方式:这种情况下就需要使用类型断言指定更加具体的类型。
解释:
技巧:在浏览器控制台,通过console.dir()打印DOM元素,在属性列表的后面,即可看到该元素的类型。
思考以下代码,两个变量的类型分别是?
通过TS类型推论机制,可以得到答案:
解释:
注意:此处的 'Hello TS'就是字面量类型。也就是说某个特定字符串也可以作为TS中的类型。
除字符串外,任何的JS字面量(比如、对象、数字等)都可以作为类型使用。
let str1 = 'Hello'
const str2: 'Hello' = 'Hello'
let age: 18 = 18
使用模式:字面量类型配合联合类型(|)一起用
使用场景:用来表示一组明确的可选值列表。
比如贪吃蛇游戏中,游戏的方向就只有(上下左右)任意一个
解释:参数direction的值只能是up/down/left/right中的任意一个。
优势:相比于string类型,使用字面量类型更加精确、严谨。
注意:形参direction的类型为枚举Direction,那么,实参的值就应该是枚举Direction成员中的任意一个。
enum Direction { UP, Down, Left, Right }
function changeDirection(direction: Direction) {
console.log(direction);
}
changeDirection(Direction.UP)
changeDirection(Direction.Down)
changeDirection(Direction.Left)
changeDirection(Direction.Right)
解释:类似于JS中的对象,直接通过点(.)语法访问枚举成员。
问题: 我们将枚举成员作为函数的实参,它的值是什么?
解释:通过将鼠标移入Direction.UP可以看到枚举成员UP的值是0
注意:枚举成员是有值的,默认为:从0开始自增的数字
我们把,枚举成员的值为数字枚举
当然也可以给枚举中的成员初始化值:
//UP=10, Down=11, Left=12, Right=13
enum Direction { UP = 10, Down, Left, Right }
//UP=10, Down='abc', Left=8, Right=4
enum Direction { UP = 10, Down = 5, Left = 8, Right = 4 }
注意:枚举成员的值是字符串
//UP=UP, Down='Down', Left='Left', Right='Right'
enum Direction { UP = 'UP', Down = 'Down', Left = 'Left', Right = 'Right' }
function changeDirection(direction: Direction) {
console.log(direction);
}
changeDirection(Direction.UP)
changeDirection(Direction.Down)
changeDirection(Direction.Left)
changeDirection(Direction.Right)
注意:字符串枚举没有自增长行为,因此,字符串枚举成员都必须拥有初始值
枚举是TS为数不多的非JS类型级拓展(不仅仅是类型)的特征之一
因为:其他类型不仅仅被当做类型,而是枚举不仅用于类型还提供值(枚举成员都是有值的)
也就是说,其他类型会在编译成JS代码时直接移除,但是,枚举类型会被编译成JS代码。
TS:
//UP=UP, Down='Down', Left='Left', Right='Right'
enum Direction { UP = 'UP', Down = 'Down', Left = 'Left', Right = 'Right' }
function changeDirection(direction: Direction) {
console.log(direction);
}
changeDirection(Direction.UP)
changeDirection(Direction.Down)
changeDirection(Direction.Left)
changeDirection(Direction.Right)
JS:
//UP=UP, Down='Down', Left='Left', Right='Right'
var Direction;
(function (Direction) {
Direction["UP"] = "UP";
Direction["Down"] = "Down";
Direction["Left"] = "Left";
Direction["Right"] = "Right";
// Direction为对象,UP,Down。。。为属性加赋值
})(Direction || (Direction = {}));
//传入Direction=understand||Direction = {}
function changeDirection(direction) {
console.log(direction);
}
changeDirection(Direction.UP);
changeDirection(Direction.Down);
changeDirection(Direction.Left);
changeDirection(Direction.Right);
说明:枚举类型和前面说到的字面量类型+联合类型组合功能类似,都用来表示一组明确的可选值列表。
一般情况下,推荐使用字面量类型+联合类型,因为相比于枚举这种方式更加简洁、高效。
原则:不推荐使用any!这会让TS变成"AnyScript"(失去类型保护的优势)
因为当值的类型为any时,可以对该值进行任意超做,并且不会有任何提示。
其他隐式具有any类型的情况:
众所周知,JS提供了typeof运算符,用来在js中获取数据的类型
console.log(typeof 'hello'); //打印 string
实际上TS也提供了typeof操作符:可以在类型上下文中引用变量或属性的类型(类型查询)。
使用场景:根据已有变量的值,来获取该值的类型,来简化类型书写。
let parameter = {
x: 10,
y: 20
}
function forMater(point: { x: number, y: number }) {}
forMater(parameter)
let parameter = {
x: 10,
y: 20
}
let p = {
x: 0,
y: 0
}
function forMater(point: typeof p) {}
forMater(parameter)
TS全面支持ES5中引入的class关键字
class Person{
//添加属性的方法有以下两种
name1:string
name2 = '简隋英'
}
const p = new Person()
p.name1//属性是string
p.name2//属性是string
解释:
class Person{
//添加属性的方法有以下两种
name1:string
name2 = '简隋英'
constructor(str1:string,str2:string){
this.name1 = str1
this.name2 = str2
}
}
const p = new Person('李玉','简隋英')
p.name1//属性是string
p.name2//属性是string
解释:
class Person {
x = '简隋英'
y = '李玉'
scale(name1: string, name2: string): void {
this.x = name1
this.y = name2
}
}
const p = new Person()
p.scale('江停', '严峫')
console.log(p.x, p.y);
解释:方法的类型注解(参数和返回值)于函数相同。
注意:类的继承有两种方式extends(继承父类)和implements(实现接口)
class Name{
onBackName(){
console.log('江停');
}
}
class Person extends Name{
}
const p = new Person()
p.onBackName() //结果打印:江停
解释:
interface Singable{
name:string
sing():void
}
class Person implements Singable{
name: string
sing(): void {
console.log('夫妻双双把家还!');
}
}
解释:
类成员的可见性,可以使用TS来控制class的方法或属性对于class外的代码是否可见。
可见性修饰符包括:1、public(公有的)2、protected(受保护的)3、privated(私有的)
class Person {
name = '江停'
public sing(name: string): void {
console.log(this.name + name);
}
}
const p = new Person()
p.sing('严峫')//结果打印:江停严峫
注意:
class Name {
protected member() {
console.log('吴雩');
}
cole() {
this.member//这里可以调用
}
}
const nameClass = new Name()
nameClass.cole()//这个方法可以调用
nameClass.member()//这个方法不可以调用
class Person extends Name {
name = '江停'
sing(name: string): void {
this.member()
}
}
const p = new Person()
p.sing('严峫')//结果打印:江停严峫
p.cole()//这个方法可以调用
p.member()//这个方法不可以调用
注意:
class Name {
private member() {
console.log('吴雩');
}
cole() {
this.member//这里可以调用
}
}
const nameClass = new Name()
nameClass.cole()//这个方法可以调用
nameClass.member()//这个方法不可以调用
class Person extends Name {
name = '江停'
sing(name: string): void {
this.member()//这个方法不可以调用
}
}
const p = new Person()
p.sing('严峫')//结果打印:江停严峫
p.cole()//这个方法可以调用
p.member()//这个方法不可以调用
注意:
出来可见修饰符以外,还有一个常见修饰符就是:readonly(只读修饰符)
readonly:表示只读,用来防止构造函数以外的属性对他进行赋值
注意:只能在constructor中修改属性,其他地方不能。且readonly只能用于属性不能用于方法。
class Person{
readonly age:number = 18
constructor(age1:number){
this.age = age1 //只能在这里改
}
changAge(){
this.age = 23 //修改属性会报错
}
}
const p = new Person(21)
注意:如果 readonly的属性没有增加类型注解,那么值的属性就是他的初始值,相当于一个常量。就算是在constructor里也不能改。
class Person{
readonly age = 18
constructor(age1:number){
this.age = age1 //这里也不能改
}
changAge(){
this.age = 23 //修改属性会报错
}
}
const p = new Person(21)
interface IPerson{
readonly name:string
}
let obj:IPerson ={
name:'严峫'
}
obj.name = '江停'//name属性标记只读,所以不能被复制
let obj: { readonly name: string } = {
name: '严峫'
}
obj.name = '江停'//name属性标记只读,所以不能被复制
解释:
两种类型系统:结构化类型系统和标明类类型系统
TS采用的是结构化类型系统,也叫做duck typing(鸭子类型),类型检查关注的是值所具备的性状
也就是说,结构化类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型。
class Name {
x:number
y:number
}
class Name2{
x:100
y:200
}
const p:Name = new Name2()
解释:
注意:结构化类型系统中,如果两个对象具有相同的形状,则认为他们属于同一类型,这种说法不完全准确。
更准确的说法是:对于对象类型来说,y的成员至少于x相同,则认为x兼容y(成员多的可以赋值给少的)
class Point {
x: number
y: number
}
class Point3D {
x: number
y: number
z: number
}
const p: Point = new Point3D
解释:
除了class之外,TS中的其他类型也存在相互兼容的情况,包括:1、接口兼容 。2、函数兼容性等。
接口之间的兼容性,类似于class,并且,class和interface之间也可以兼容
函数之间的兼容性比较复杂,需要考虑:1、参数个数 2、参数类型 3、返回值类型
参数个数,参数多的兼容参数少的(或者说,参数少的可以赋值给多的)
type F1 = (a:number) =>void
type F2 = (a:number, b:number)=>void
let a:number = 12
let f1:F1 = (a) => {
return null
}
let f2:F2 = f1
const arr = ['a', 'b', 'c']
arr.forEach(() => { })
arr.forEach((item) => { })
解释:
参数类型:相同位置的参数类型要相同,(原始类型)或兼容(对象类型)
type F1 = (a: number) => void
type F2 = (a: number) => void
let f1:F1
let f2:F2
let num = 12
f2 = (num) => {}
f1 = f2
f2 = f1
interface Point2D {
x: number
y: number
}
interface Point3D {
x: number
y: number
z: number
}
type F2 = (p: Point2D) => void
type F3 = (p: Point3D) => void
let p1 = { x: 12, y: 10 }
let f2: F2 = (p1) => { }
let f3: F3 = f2
f2 = f3//会报错,多的不能赋值给少的
解释:
type F5 = () => string
type F6 = () => string
let f5:F5 = () => {return '111'}
let f6:F6 = f5
type F7 = () => { name: string }
type F8 = () => { name: string, age: number }
let f7: F7 = () => {
return {
name:'李玉'
}
}
let f8: F8 = () => {
return {
name: '简隋英',
age: 28
}
}
f7 = f8
console.log(f7);//打印结果:[Function: f8]
解释:
交叉类型(&):功能类型接口继承(extends),用于组合多个类型为一个类型(常用于对象类型)
interface Person{
name:string
}
interface Content extends Person{
age:number
}
let PersonContent:Content = {
name:'简隋英',
age:27
}
interface Person{
name:string
}
interface Content{
age:number
}
type PersonContent = Person & Content
let obj:PersonContent = {
name:'简隋英',
age:27
}
解释:使用交叉类型后,新的类型PersonContent就同时具备了Person 和 Content的所有属性类型。
type PersonContent = { name: string, age: number }
交叉型(&)和接口型(extends)的对比:
interface A {
fn: (value: number) => string
}
interface B extends A{//B不兼容
fn: (value: string) => string
}
interface A {
fn: (value: number) => string
}
interface B {//B不兼容
fn: (value: string) => string
}
type C = A & B
说明:以上代码,接口继承会报错(类型不兼容);交叉类型没有错误,可以简单理解为:
fn: (value: string | number) => string
泛型是在保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。
需求 :创建一个id函数,传入什么数据就返回该数据本身。(也就是说参数和返回值类型相同)
function id(value:number):number {
return value
}
比如:id(10)调用以上函数就会直接返回10本身。但是,该函数只能用于数值类型,无法用于其他类型。
为了能让函数接收任意类型,可以将参数类型修改为any,但是,这样就会失去TS的类型保护,类型不安全。
function id(value:any):any {
return value
}
解释:泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活复用。
function id(value: Type): Type {
return value
}
解释:
function id(value: Type): Type {
return value
}
const num1 = id(10)
const str = id('江停')
const beal = id(true)
解释:
同样,如果传入类型string,函数id参数和返回值的类型就是string
这样,通过泛型就做到了让id函数于多种不同的类型一起工作,实现了复用的同时保证了类型安全
function id(value: Type): Type {
return value
}
const num1 = id(10)
const str = id('江停')
const beal = id(true)
解释:
推荐:使用这种简化的方式调用泛型函数,使代码变得更短,更加易读
说明:当编译器无法推断类型或者类型不准确时,就需要显式的传入类型参数
泛型约束:默认情况下,泛型函数的类型变量Type可以代表多个类型,这导致无法访问任何属性。
比如:id('a')调用函数时获取参数长度:
function id(value: Type): Type {
console.log(value.length);//这个代码报错
return value
}
const num1 = id([10,12])
解释:Type可以代表任意类型,无法保证一定存在length属性,比如number类型就没有length。
此时,就需要为泛型添加约束来收缩类型(缩小类型的取值范围)
添加泛型的收缩属性,主要有以下两种方式:指定更加具体的类型 和 添加约束
function id(value: Type[]): Type[] {
console.log(value.length);//这个代码报错
return value
}
const num1 = id([10,12])
比如:将类型修改为Type[](Type类型的数组)。因为只要是数组就一定存在length属性,因此就可以访问了
interface ILength{
length:number
}
function id(value: Type): Type {
console.log(value.length);//这个代码报错
return value
}
const num1 = id([10,12])
const str = id('江停')
const beal = id({length:12,name:'江停'})
解释:
注意:传入的实参(比如,数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容性
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如:第二个类型变量受第一个类型变量约束)。
比如,创建一个函数来获取对象中属性的值:
function getProp(obj:Type,key:Key){
return obj[key]
}
let person = {name:'江停',age:31}
console.log(getProp(person,'name'));
解释: