TypeScript 入门:高级类型

531 阅读5分钟

​类型断言

const nums = [2,66,88]
// TS 的类型推断无法确定res 的结果
const res = nums.find(i => i >0)

​// 而此时,res 就不能当作纯数字使用
// const t = res * res // res 报错// 我们根据代码逻辑,知道res 一定是number,就可以直接断言
const n1 = res as number
// 使用res断言后的结果 n1
const n2 = n1 * n1
console.log(n2)

断言的语法规则就是变量xx as 类型xx或者<类型xx> 变量xx,意思就是告诉 TS ,我的代码一定是某种类型的,放心使用。<类型xx> 变量 xx 的方式在 React 中会与 JSX 语法规则冲突,因此一般更建议使用 as 语法规则

接口 Interfaces

基本使用

functionprint(post:Objpost){
  // 传入的对象中,必须有 title 和 content
  console.log(post.title)
  console.log(post.content)
}
// 但是,并不能保证调用者一定传入符合条件的对象
print({name:'xiling'})
使用接口进行内容约束:
// 定义接口
interface Objpost{
  // 属性名:类型 //使用逗号和分号或者不写都可以
  title:string
  content:number
}

​// 形参使用接口,标注传入的内容,符合接口的定义
functionprint(post:Objpost){
  // 传入的对象中,必须有 title 和 content
  console.log(post.title)
  console.log(post.content)
}
print({name:'xiling'}) // 报错
print({title:'lisi',content:66})

可选与只读属性

// 定义接口
interface Objpost {
  // 属性名:类型 //使用逗号和分号或者不写都可以
  title: string
  content: number

​  subtitle?: string // 可选成员属性
  readonly summary: string // 只读属性,一旦赋值则不可修改
}​

// 形参使用接口,标注传入的内容,符合接口的定义
functionprint(post: Objpost) {
  // 传入的对象中,必须有 title 和 content
  console.log(post.title)
  console.log(post.content)
}​

let obj:Objpost = { title:'lisi',content:66,summary:'xiling' }
obj.summary='liuneng'// 修改只读属性,报错
print(obj)

动态成员

interfaceCache {
  // [定义动态成员:类型]:值类型
  [props:string]:string
}

​const obj: Cache = {}
// 动态添加对象成员
obj.title='javaScript'
obj.content='html'​

console.log(obj)

类的使用

基本使用

class Person{
  // 在类中初始化属性定义
  name:string='lisi'// 定义初始值
  age:numberconstructor(name:string,age:number){
    this.name=name
    this.age=age
    this.sex='男'// 没有初始化的成员会报错
  }

​  SayHi(msg:string):void{
    console.log(`Hello ${this.name},${msg}`)
  }
}

访问修饰符

class Person{
  // 类中的修饰符有三种 public  private  protected
  // public 公有属性,任何地方都可以访问
  // protected 受保护属性,只有自己和继承的子类中能访问
  // private 私有属性,只在类内部访问
  public name:string='lisi'
  private age:number
  protected gender:booleanconstructor(name:string,age:number){
    this.name=name
    this.age=age
    this.gender=true
  }​

  SayHi(msg:string):void{
    console.log(`Hello ${this.name},${msg}`)
    console.log(this.gender)
  }
}​

class Student extends Person{
  sya(){
    this.gender
  }
}​

let stu =newStudent('lisi',66)
stu.name
stu.age// 私有属性,外部访问报错
stu.gender// 受保护属性,外部访问报错

类的只读属性

class Person{
  // 设置成员为只读属性,一旦赋值,不可修改
  public readonly name:string='lisi'constructor(name:string,age:number){
    this.name=name
  }​

  SayHi(msg:string):void{
   this.name='liuneng'// 修改报错
  }
}

类与接口

// 人和动物都有运动和吃东西的行为
// 都有这样的行为,但确实不一样的;
// 这样的情况下,我们就可以使用接口描述不同的行为约束class Person {
  eat(food: string) {
    console.log(`使用筷子:${food}`)
  }

​  run(distance: number) {
    console.log(`双脚:${distance}`)
  }
}

​class Animal {
  eat(food: string) {
    console.log(`撕咬的吃:${food}`))
  }​

  run(distance: number) {
    console.log(`四蹄${distance}`)
  }
}

人和动物都有运动和吃东西的行为,都有这样的行为,但确实不一样的。这样的情况下,我们就可以使用接口描述不同的行为约束。

// 接口只做行为的约束,不做具体方法的实现
interface eatAndrun {
  eat(food: string):void
  run(distance: number):void
}

​// 使用 implements 关键字进行约束
class Person implements eatAndrun {
  eat(food: string) {
    console.log(`使用筷子:${food}`)
  }
  // 如果缺少约束的成员,则会报错
  // run(distance: number) {
  //   console.log(`双脚:${distance}`)
  // }
}

但是在实际的开发中,接口约束会更加的细致,就类似摩托车也能跑,但并不是人或动物,细致的约束会更加灵活。

// 对接口进行细致化的拆分
interface eat {
  eat(food: string):void
}
​interface run {
  run(distance: number):void
}​

// 使用多个接口,需要逗号,隔开
class Person implements eat,run {
  eat(food: string) {
    console.log(`使用筷子:${food}`)
  }
  // 如果缺少约束的成员,则会报错
  run(distance: number) {
    console.log(`双脚:${distance}`)
  }
}

抽象类与抽象方法

抽象类

与接口类似,但是接口只能进行规范约束不能具体实现,而抽象类可以具体实现。抽象类的定义很简单,只需要在类声明的前面添加 abstract 就可以了。

abstract class Animal {
  eat(food:string):void{
    console.log(`撕咬的吃:${food}`)
  }
}

类一旦被抽象,就不能被实例化,只能使用子类继承使用。

// newAnimal() // 报错--无法创建抽象类的实例
class Dog extends Animal{
 
}
抽象方法
abstract class Animal {
  eat(food:string):void{
    console.log(`撕咬的吃:${food}`)
  }
  // 定义抽象方法run , 需要在继承的子类中实现(必须实现)
  abstract run(distance:number):void
}​

class Dog extends Animal{
  // 抽象方法必须在子类中实现  run(distance: number):void {    console.log(distance)  }}

泛型

在定义函数、接口或者类的时候,没有指定具体的类型,等到使用时才会具体指定的一种特征。

// 函数定义时,参数及返回值并不能确定类型
// 或者,函数需要在运行时固定类型
// 函数名后面使用 <占位符> ,在不确定类型的地方,使用 :占位符
function createArray<T>(length: number, value: T): T[] {
  const arr =Array<T>(length).fill(value)
  return arr
}​

// 函数调用时,使用函数名<泛型的具体类型> (实参1,实参2)
createArray<string>(3,'xl') 

类型声明

我们在进行项目开发时,肯定会用到一些第三方模块,但是这些第三方模块不一定是 TS 的,那就无法使用强类型的友好开发体验。

以 lodash 为例,使用 npm install lodash 安装后,通过 import 语法引入。

// 无法找到模块“lodash”的声明文件。
import {camelCase} from'lodash'// camelCase 将字符串转为驼峰格式
const res =camelCase('hello xiling') // 没有类型提示
console.log(res)

引入后就出现了错误提示:'无法找到模块“lodash”的声明文件。'

同时提示中也告诉我们安装 npm install @types/lodash -D 模块来解决这个问题,已开发依赖的方式安装即可,@types/lodash 其实就是声明文件,成功安装之后,操作就消失了。

而有的模块直接拥有声明文件,这时就不需要额外安装扩展库了。

但是,有一些相对陈旧的库,在使用时自身没有声明文件,也没有对应的文件库这就需要我们自己在代码中进行处理了。

我们依然以 lodash 为例,npm uninstall @types/lodash 卸载类型声明文件,模拟这种情况,无法找到模块“lodash”的声明文件。

camelCase 也没有类型提示:因为确实没有类型声明文件,所以,我们尝试解决 camelCase 的类型提示,其实只需要使用 declare 关键字进行标注就可以了:

// 无法找到模块“lodash”的声明文件。
import {camelCase} from'lodash'declare functioncamelCase(input:string):string// camelCase 将字符串转为驼峰格式
const res =camelCase('hello xiling') // 没有类型提示
console.log(res)