TypeScript学习笔记-part2

四、TypeScript高级类型

(一)概述

TS中的高级类型有很多,重点学习以下高级类型:

  1. class类
  2. 类型兼容性
  3. 交叉类型
  4. 泛型 和 keyof
  5. 索引签名类型 和 索引查询类型
  6. 映射类型

(二)class类

1.class类的基本使用

TypeScript全面支持ES2015引入的class关键字,并为其添加了类型注解和其他语法(eg:可见性修饰符等)

class的基本使用:

TypeScript学习笔记-part2_第1张图片

解释: 

  1. 根据TS中的类型推论,可以知道Person类的实例对象p的类型是Person。
  2. TS中的class,不仅提供了class的语法功能,也作为一种类型存在

实例属性初始化:

// 实例属性初始化
class Person{
  age: number 
  gender = '男' // (property) Person.gender: string
  // gender:string = '男'
}
const p = new Person()
p.age
p.gender

解释:

  1. 声明成员age,类型为number(没有初始值)。
  2. 声明成员gender,并设置初始值,此时,可省略类型注解(TS类型推论为string类型)。

2.class的构造函数

构造函数:

class Person {
  age: number
  gender: string

  constructor(age: number, gender: string) {
    this.age = age
    this.gender = gender
  }
}

const p = new Person(18, '男')
console.log(p.age,p.gender)// 18 男

解释:

  1. 成员初始化(eg:age:number)后,才可以通过this.age来访问实例成员。
  2. 需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要返回值类型。

3.class实例方法

class Point {
  x = 10
  y = 20

  scale(n: number):void {
    this.x *= n
    this.y *= n
  }
}

const p = new Point()

p.scale(10)

console.log(p.x,p.y)// 100 200

解释:方法的类型注解(参数和返回值)与函数用法相同。

4.class继承

类继承的两种方式:

  1. extends(继承父类)
  2. implements(实现接口)

说明:JS中只有extends,而implements是TS提供的。

1)extends继承

// extends继承
class Animal {
  move() {
    console.log('Moving along')
  }
}

class Dog extends Animal {
  bark() {
    console.log('汪汪')
  }
}

const dog = new Dog()

dog.move()// Moving along
dog.bark()// 汪汪

解释:

  1. 通过extends关键字实现继承
  2. 子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法。

2)implements继承

// implements继承
interface Singable {
  sing(): void
  name : string
}

class Person implements Singable {
  name = 'zs'
  sing(){
    console.log('小苹果')
  }
}
const p = new Person()
p.sing()

解释:

  1. 通过implements关键字让class实现接口。
  2. Person类实现接口Singable意味着,Person类中必须提供Singable接口中指定的所有方法和属性。

5.class类的可见性修饰符

类成员可见性:可以使用TS来控制class的方法或属性对于class外的代码是否可见

可见性修饰符包括:

  1. public(公有的)
  2. protected(受保护的)
  3. private(私有的)

1)public :表示公有的、公开的公有成员可以被任何地方访问,默认可见性。

// public
class Animal {
  protected move() {
    console.log('Moving along')
  }
}
const a = new Animal()
a.move()

class Dog extends Animal{
  bark() {
    console.log('汪汪')
  }
}

const d = new Dog()
d.bark()
d.move()

解释:

  1. 在类属性或方法前面添加public关键字,来修饰该属性或方法是共有的。
  2. 因为public是默认可见性,所有,可以直接忽略

2)protected 表示受保护的,仅对其声明所在类和子类中(非实例对象)可见。

TypeScript学习笔记-part2_第2张图片

 解释:

  1. 在类属性或方法前面添加protected关键字,来修饰该属性或方法是受保护的。
  2. 在子类的方法内部可以通过this来访问父类中受保护的成员,但是,对实例不可见

3)private 表示私有的,只在当前类中可见,对实例对象以及子类也是不可见的。

TypeScript学习笔记-part2_第3张图片

 解释:

  1. 在类属性或方法前面添加private关键字,来修饰该属性或方法是私有的。
  2. 私有的属性或方法只在当前类中可见,对子类和实例对象也都是不可见的。

 6. 只读修饰符:readonly

除了可见性修饰符之外,还有一个常见修饰符就是:readonly(只读修饰符)。

readonly:表示只读用来防止在构造函数之外对属性进行赋值

TypeScript学习笔记-part2_第4张图片

 TypeScript学习笔记-part2_第5张图片

解释:

  1. 使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法
  2. 注意:属性age后面的类型注解(eg:这里的number)如果不加,则age的类型为18(字面量类型)。
  3. 接口或者{}表示的对象类型内,也可以使用readonly

 

TypeScript学习笔记-part2_第6张图片  

(三)类型兼容性

1. 类型兼容性的说明

两种类型系统:

  1. Structural Type System(结构化类型系统)
  2. Nominal Type System(标明类型系统)

TS采用的是结构化类型系统,也叫做duck typing(鸭子类型),类型检查关注的是值所具有的形状。

也就是说,在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。

class Point {
  x: number
  y: number
}

class Point2D {
  x: number
  y: number
}


const p: Point = new Point2D()

解释:

  1. Point和Point2D是两个名称不同的类。
  2. 变量p的类型被显示标注为Point类型,但是它的值却是Point2D的实例,并且没有类型错误。
  3. 因为TS是结构化类型系统,只检查Point和Point2D的结构是否相同(相同,都具有x和y两个属性,属性类型也相同)。
  4. 但是,如果在Nominal Type System中(eg:C#、Java等),它们是不同的类,类型无法兼容。

TypeScript学习笔记-part2_第7张图片

2.对象之间的类型兼容性

注意:在结构化类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型,这种说法并不准确。

更准确的说法:对于对象类型来说,y的成员至少与x相同,则x兼容y(成员多的可以赋值给少的)

class Point {
  x: number
  y: number
}

class Point3D {
  x: number
  y: number
  z: number
}

const p: Point = new Point3D()

p.x
p.y
p.z // 报错,无法访问,p不存在该属性//类型“Point”上不存在属性“z”

解释:

  1. Point3D的成员至少与Point相同,则Point兼容Point3D。
  2. 所以,成员多的Point3D可以赋值给成员少的Point。

除了class外,TS中其他类型也存在相互兼容的情况,包括:

  1. 接口兼容性
  2. 函数兼容性

3.接口之间的类型兼容性

接口之间的兼容性,类似于class。并且,class和interface之间也可以兼容。

interface Point {
  x: number
  y: number
}
interface Point2D {
  x: number
  y: number
}

interface Point3D {
  x: number
  y: number
  z: number
}


let p1: Point 
let p2: Point2D = { x: 4, y: 5 }
let p3: Point3D = { x: 6, y: 7, z: 8 }

p1 = p2
p2 = p3
// 错误
// p3 = p1 //类型 "Point" 中缺少属性 "z",但类型 "Point3D" 中需要该属性。
// 类与接口之间也是兼容的
interface Point {
  x: number
  y: number
}
interface Point2D {
  x: number
  y: number
}
class Point3D{
  x: number
  y: number
  z: number
}

let p1: Point 
let p2: Point2D = { x: 4, y: 5 }
let p3: Point2D = new Point3D()

p2 = p3
p2 = new Point3D()

4.函数之间的类型兼容性

函数之间兼容性比较复杂,需要考虑:

  1. 参数个数
  2. 参数类型
  3. 返回值类型

1)参数个数,参数多的兼容参数少的(或者说,参数少的可以赋值给多的

// 参数个数:少的赋值给多的(多的兼容少的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void

let f1: F1  = (a)=>{}
let f2: F2 = f1 // 需要给f1初始值,不然报错:在赋值前使用了变量“f1”。

解释:

  1. 参数少的可以赋值给参数多的,所以,f1可以赋值给f2。
  2. 数组forEach方法的第一个参数是回调函数,该示例中类型为:(value:string,index:number,array:string[])=>void。、
  3. 在JS中省略用不到的函数参数实际上是很常见的,这样的使用方式,促成了TS中函数类型之间的兼容性。
  4. 并且因为回调函数是有类型的,所以,TS会自动推导出参数item、index、array的类型。

2)参数类型,相同位置的参数类型要相同(原始类型)或兼容(对象类型)

type F1 = (a: number) => void
type F2 = (a: number) => void

let f1: F1 = (a) => { }

let f2: F2 = f1 

TypeScript学习笔记-part2_第8张图片

解释:函数类型F2兼容函数类型F1,因为F1和F2的第一个参数类型相同。

TypeScript学习笔记-part2_第9张图片

解释:

  1. 注意,此处与前面讲到的接口兼容性冲突。
  2. 技巧:将对象拆开,把每个属性看做一个个参数,则,参数少的(f2)可以赋值给参数多的(f3) 。

3)返回值类型,只关注返回值类型本身即可:

// 返回值类型
type F5 = () => string
type F6 = () => string
let f5: F5 = () => { return ''}
let f6: F6 = f5


type F7 = () => { name: string }
type F8 = () => { name: string; age: number }
let f7: F7 
let f8: F8 = () => {
  return {
    name: 'zs',
    age: 18
  }
}
f7 = f8

解释:

  1. 如果返回值类型是原始类型,此时两个类型要相同,比如,F5和F6。
  2. 如果返回值类型是对象类型,此时成员多的可以赋值给成员少的,比如,F7和F8。

(四)交叉类型

1. 交叉类型

交叉类型(&):功能类似于接口继承(extends),用于组合多个类型为一个类型常用于对象类型)。

interface Person {
  name: string
}

interface Contact {
  phone: string
}

type PersonDetail = Person & Contact

let obj: PersonDetail = {
  name: 'zs',
  phone: '1333'
}

解释:使用交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型。

相当于:

type PersonDetail {name: string;phone: string}

2.交叉类型 和 接口 之间的对比说明

交叉类型(&)和接口继承(extends)的对比:

  • 相同点:都可以实现对象类型的组合。
  • 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同
// 接口继承
interface A {
  fn: (value: number) => string
}

interface B extends A{
  fn: (value: string) => string
}

TypeScript学习笔记-part2_第10张图片

// 交叉类型
interface A {
  fn: (value: number) => string
}

interface B {
  fn: (value: string) => string
}

type C = A & B// fn: (value: string | number) => string

 说明:以上代码,接口继承会报错(类型不兼容);

        交叉类型没有错误,可以简单理解为:fn: (value: string | number) => string

(五)泛型和keyof

1. 泛型

1)泛型的基本使用

泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、class中。

需求:创建一个id函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)。

2)

(六)索引签名类型 和 索引查询类型

(七)映射类型

五、TypeScript 类型声明文件

六、React中使用TypeScript(暂未学React,跳过)

七、Vue中使用TypeScript

你可能感兴趣的:(typescript,学习,javascript)