TypeScript 学习指南

认识 TypeSCript

  • TypeScript 是一种由微软开发的开源、跨平台的编程语言。它是 JavaScript 的超集,最终会被编译为 JavaScript 代码
  • TypeScript 主要有 3 大特点:
    • 它是最终还是归于JavaScript
      • TypeScript 可以编译出纯净、 简洁的 JavaScript 代码,并且可以运行在任何浏览器上、Node.js 环境中和任何支持 ECMAScript 3(或更高版本)的 JavaScript 引擎中
    • 强大的类型系统:
      • 允许 JavaScript 开发者在开发 JavaScript 应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构
    • 支持ES6+ 的新特性语法
  • 总结:为什么是要学习它,是因为,目前越来越多开发项目中使用到它,而且随着Vue3的到来,TS也越来越受欢迎,所以学好它是一份高薪选择

安装 TypeScript

  • 我们想要使用 TypeScript,那么就要对其进行一个安装
  • 安装命令: npm install -g typescript
  • 安装完毕后使用命令:tsc -V 检测是否安装ok

编写 TypeScript 程序

  • 在你的src目录下新建一个 helloworld.ts 后缀名的文件进行以下的代码编辑
function greeter(person) {
  return 'Hello, ' + person
}

let user = 'Yee'

console.log(greeter(user))
  • 对 ts 的代码进行编译

    • 以上的代码还是我们熟悉的 js 代码,只不过它的后缀名是 .ts 结尾的,那么需要对其进行转换为 js 的代码
    • 在命令行上,运行 TypeScript 编译器: tsc helloworld.ts
    • 输出结果为一个 helloworld.js 文件,它包含了和输入文件中相同的 JavsScript 代码
    • 随后在命令上再次运行: node helloworld.js,就会在控制台上看到:Hello, Yee
  • 简化运行 TS 的步骤

    • 安装命令: npm i ts-node -g
    • 使用方式: ts-node hello.ts, 这样的操作只有输出一行命令就可以了

类型注解

  • TypeScript 里的类型注解是一种轻量级的为函数或变量添加约束的方式
  • 在TS中的常用基础类型可以分为两类: js已有的类型、ts新增加的类型
    • JS已有的类型
      • 基本类型: number、string、boolean、null、undefined、symbol
      • 对象类型: object【数组、Data、Math】等等
    • TS新增类型:
      • 联合类型、自定义类型、接口、元祖、字面量类型、枚举、void、any 等
  • 接下来让我们看看 TypeScript 工具带来的高级功能。 给 person 函数的参数添加 : string 类型注解,如下:
function greeter(person: string) {
  return 'Hello, ' + person
}

let user = 'Yee'

console.log(greeter(user))
  • 如果我们把 greeter() 接收的数据修改为一个数组,那么就会发生以下的错误:
    • error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
布尔值
  • 最基本的数据类型就是简单的 true/false 值,在 JavaScript 和 TypeScript 里叫做 boolean
let isDone: boolean = false
数字
  • 和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。 这些浮点数的类型是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量
  let a: number = 10 // 十进制
  let b: number = 0b1010 // 二进制
  let c: number = 0o12 // 八进制
  let d: number = 0xa // 十六进制
字符串
  • 和 JavaScript 一样,可以使用双引号(")或单引号(')表示字符串
let name: string = 'tom'
name = 'jack'
// name = 12 // error
let age: number = 12
const info = `My name is ${name}, I am ${age} years old!`
undefined 和 null
  • TypeScript 里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null。 它们的本身的类型用处不是很大
let u: undefined = undefined
let n: null = null
  • 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给 number 类型的变量
数组
  • TypeScript 像 JavaScript 一样可以操作数组元素。 有两种方式可以定义数组
  // 第一种方式:可以在元素类型后面接上[],表示由此类型元素组成的一个数组
  let a: number[] = [1, 6, 8]
  
  // 第二种方式是使用数组泛型,Array<元素类型>
  let b: Array<number> = [1, 8, 6]
元祖【Tuple】
  • 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string 和 number 类型的元组
let t1: [string, number]
t1 = ['hello', 10] // OK
t1 = [10, 'hello'] // Error

// 当访问一个已知索引的元素,会得到正确的类型
console.log(t1[0].substring(1)) // OK
console.log(t1[1].substring(1)) // Error, 'number' 不存在 'substring' 方法
枚举
  • enum 类型是对 JavaScript 标准数据类型的一个补充。 使用枚举类型可以为一组数值赋予友好的名字
enum Color {
  Red,
  Green,
  Blue
}

// 枚举数值默认从0开始依次递增
// 根据特定的名称得到对应的枚举数值
let myColor: Color = Color.Green // 0
console.log(myColor, Color.Red, Color.Blue)
any
  • 有的时候我们不确定是什么类型的情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any 类型来标记这些变量
let notSure: any = 4
notSure = 'maybe a string'
notSure = false // 也可以是个 boolean
void
  • 某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void
object
  • object 表示非原始类型,也就是除 number,string,boolean之外的类型
  • 使用 object 类型,就可以更好的表示像 Object.create 这样的 API
 function fun(obj: object): object {
   console.log(obj)
   return {
     name: '凹凸曼',
   }
 }
 console.log(fun({name: 'ABC'}))
联合类型
  • 联合类型(Union Types)表示取值可以为多种类型中的一种
  // 需求 1: 定义一个一个函数得到一个数字或字符串值的字符串形式值
  function toString2(x: number | string): string {
    return x.toString()
  }
  
  // 需求 2: 定义一个一个函数得到一个数字或字符串值的长度
  function getLength(x: number | string) {
    // return x.length // error
    if (x.length) {
      // error
      return x.length
    } else {
      return x.toString().length
    }
  }
类型断言
  • 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
    类型断言有两种形式。 其一是“尖括号”语法, 另一个为 as 语法
/*
类型断言(Type Assertion): 可以用来手动指定一个值的类型
语法:
    方式一: <类型>值
    方式二: 值 as 类型  tsx中只能用这种方式
*/

/* 需求: 定义一个函数得到一个字符串或者数值数据的长度 */
function getLength(x: number | string) {
  if ((<string>x).length) {
    return (x as string).length
  } else {
    return x.toString().length
  }
}
console.log(getLength('abcd'), getLength(1234))
类型推断
  • 类型推断: TS 会在没有明确的指定类型的时候推测出一个类型 有下面 2 种情况: 1. 定义变量时赋值了, 推断为对应的类型. 2. 定义变量时没有赋值, 推断为 any 类型
/* 定义变量时赋值了, 推断为对应的类型 */
let b9 = 123 // number
// b9 = 'abc' // error

/* 定义变量时没有赋值, 推断为any类型 */
let b10 // any类型
b10 = 123
b10 = 'abc'

接口

  • 我们使用接口(Interfaces)来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)
/*
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型
接口: 是对象的状态(属性)和行为(方法)的抽象(描述)
接口类型的对象
    多了或者少了属性是不允许的
    可选属性: ?
    只读属性: readonly
*/

/*
需求: 创建人的对象, 需要对人的属性进行一定的约束
  id是number类型, 必须有, 只读的
  name是string类型, 必须有
  age是number类型, 必须有
  sex是string类型, 可以没有
*/

// 定义人的接口
interface IPerson {
  readonly id: number
  name: string
  age: number
  sex?: string
}

const person1: IPerson = {
  id: 1,
  name: 'tom',
  age: 20,
  sex: '男'
}
// 类型检查器会查看对象内部的属性是否与 IPerson 接口描述一致, 如果不一致就会提示类型错误
函数类型
  • 接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。
  • 为了使用接口表示函数类型,我们需要给接口定义一个调用签名。它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型
/*
接口可以描述函数类型(参数的类型与返回的类型)
*/

interface SearchFunc {
  (source: string, subString: string): boolean
}
  • 这样定义后,我们可以像使用其它接口一样使用这个函数类型的接口。 下例展示了如何创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量。
const mySearch: SearchFunc = function(source: string, sub: string): boolean {
  return source.search(sub) > -1
}

console.log(mySearch('abcd', 'bc'))
类类型
  • 类的类型可以通过接口来实现
/*
  类类型: 实现接口
    1. 一个类可以实现多个接口
    2. 一个接口可以继承多个接口
*/

interface Alarm {
  alert(): any
}

interface Light {
  lightOn(): void
  lightOff(): void
}

class Car implements Alarm {
  alert() {
    console.log('Car alert')
  }
} 
  • 一个类可以实现多个接口
class Car2 implements Alarm, Light {
  alert() {
    console.log('Car alert')
  }
  lightOn() {
    console.log('Car light on')
  }
  lightOff() {
    console.log('Car light off')
  }
}
接口继承接口
  • 和类一样,接口也可以相互继承。能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
  interface LightableAlarm extends Alarm, Light {}

  • JavaScript 程序员将能够使用基于类的面向对象的方式。 使用 TypeScript,我们允许开发者现在就使用这些特性,并且编译后的 JavaScript 可以在所有主流浏览器和平台上运行,而不需要等到下个 JavaScript 版本
/*
类的基本定义与使用
*/

class Greeter {
  // 声明属性
  message: string

  // 构造方法
  constructor(message: string) {
    this.message = message
  }

  // 一般方法
  greet(): string {
    return 'Hello ' + this.message
  }
}

// 创建类的实例
const greeter = new Greeter('world')
// 调用实例的方法
console.log(greeter.greet())
继承
  • 在 TypeScript 里,我们可以使用常用的面向对象模式。 基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类
/*
类的继承
*/

class Animal {
  run(distance: number) {
    console.log(`Animal run ${distance}m`)
  }
}

class Dog extends Animal {
  cry() {
    console.log('wang! wang!')
  }
}

const dog = new Dog()
dog.cry()
dog.run(100) // 可以调用从父中继承得到的方法

/**
  这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里,Dog 是一个 派生类,它派生自 Animal 基类,通过 extends 关键字。 派生类通常被称作子类,基类通常被称作超类。

因为 Dog 继承了 Animal 的功能,因此我们可以创建一个 Dog 的实例,它能够 cry() 和 run()。
**/
修饰符:public
  • public 是公共的,任何成员都是可以进行访问的
private【私有的】
  • 当成员被标记成 private 时,它就不能在声明它的类的外部访问。
protected【仅在子类中进行使用】
  • protected 修饰符与 private 修饰符的行为很相似,但有一点不同,protected成员在派生类中仍然可以访问
/*
访问修饰符: 用来描述类内部的属性/方法的可访问性
  public: 默认值, 公开的外部也可以访问
  private: 只能类内部可以访问
  protected: 类内部和子类可以访问
*/

class Animal {
  public name: string

  public constructor(name: string) {
    this.name = name
  }

  public run(distance: number = 0) {
    console.log(`${this.name} run ${distance}m`)
  }
}

class Person extends Animal {
  private age: number = 18
  protected sex: string = '男'

  run(distance: number = 5) {
    console.log('Person jumping...')
    super.run(distance)
  }
}

class Student extends Person {
  run(distance: number = 6) {
    console.log('Student jumping...')

    console.log(this.sex) // 子类能看到父类中受保护的成员
    // console.log(this.age) //  子类看不到父类中私有的成员

    super.run(distance)
  }
}

console.log(new Person('abc').name) // 公开的可见
// console.log(new Person('abc').sex) // 受保护的不可见
// console.log(new Person('abc').age) //  私有的不可见
多态
  • 父类型的引用指向了子类型的对象,多态是同一个行为具有多个不同表现形式或形态的能力
  // 定义一个 Animal 的父类
  class Animal {
    name: string,
    constructor(name: string) {
      this.name = name
    }
    run(distance: number) {
      console.log(`奔跑了${distance}公里`)
    }
  }

  // 定义 Dog 这么一个子类
  class Dog extends Animal {
    constructor(name: string) {
      // 调用父类的构造
      super(name)
    }
    // 重写父类中的实例方法
     run(distance: number = 8) {
      console.log(`奔跑了${distance}公里`)
    }
  }

  // 定义一个 Cast 的子类
  class Cat extends Animal {
    constructor(name: string) {
      // 调用父类的构造
      super(name)
    }
    // 重写父类中的实例方法
     run(distance: number = 6) {
      console.log(`奔跑了${distance}公里`)
    }
  }

  // 实例化对象
  const animal: Animal = new Animal('动物')
  animal.run()
  
  // 实例化 子类
  const dog: Dog = new Dog('旺财')
  dog.run()
  
  const cat: Cat = new Cat('小白咪')
  cat.run()
  
  console.log('==================')
  // 可以理解为 Dog 是 属于Animal 的, Animal 包括了 Dog
  const dog1: Animal = new Dog('往往')
  dog1.run()
getters/setters
  • TypeScript 支持通过 getters/setters 来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
  • 下面来看如何把一个简单的类改写成使用 get 和 set。 首先,我们从一个没有使用存取器的例子开始。
class Person {
  firstName: string = 'A'
  lastName: string = 'B'
  get fullName() {
    return this.firstName + '-' + this.lastName
  }
  set fullName(value) {
    const names = value.split('-')
    this.firstName = names[0]
    this.lastName = names[1]
  }
}

const p = new Person()
console.log(p.fullName)

p.firstName = 'C'
p.lastName = 'D'
console.log(p.fullName)

p.fullName = 'E-F'
console.log(p.firstName, p.lastName)
static 静态成员
  • 在类中通过 static 关键字进行修饰的成员称之为是静态成员【属性,方法】
  • 静态成员在使用的时候是通过 类名.静态成员 的方法格式进行调用的
  // 定义一个类
  class Person {
    static name: string
    constructor(name: string) {
       this.name = name
    }
     static sayHi() {
      console.log('Hi~')
    }
  }

  Person.name = '小美'
  console.log(person.name)

  Person.sayHi()
抽象类
  • 抽象类做为其它派生类的基类使用。 它们不能被实例化。不同于接口,抽象类可以包含成员的实现细节。
  • abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法
/*
抽象类
  不能创建实例对象, 只有实现类才能创建实例
  可以包含未实现的抽象方法
*/

abstract class Animal {
  abstract cry()

  run() {
    console.log('run()')
  }
}

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

const dog = new Dog()
dog.cry()
dog.run()
函数以及函数类型
// 函数添加类型
function add(x: number, y: number): number {
  return x + y
}

let myAdd = function(x: number, y: number): number {
  return x + y
}

// 可选参数 在 TypeScript 里我们可以在参数名旁使用 ? 实现可选参数的功能
function buildName(firstName: string = 'A', lastName?: string): string {
  if (lastName) {
    return firstName + '-' + lastName
  } else {
    return firstName
  }
}

console.log(buildName('C', 'D'))
console.log(buildName('C'))
console.log(buildName())
函数重载
  • 函数重载: 函数名相同, 而形参不同的多个函数 在 JS 中, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的 但在 TS 中, 与其它面向对象的语言(如 Java)就存在此语法
/*
函数重载: 函数名相同, 而形参不同的多个函数
需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也可以接收2个number类型的参数进行相加
*/

// 重载函数声明
function add(x: string, y: string): string
function add(x: number, y: number): number

// 定义函数实现
function add(x: string | number, y: string | number): string | number {
  // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
  if (typeof x === 'string' && typeof y === 'string') {
    return x + y
  } else if (typeof x === 'number' && typeof y === 'number') {
    return x + y
  }
}

console.log(add(1, 2))
console.log(add('a', 'b'))
// console.log(add(1, 'a')) // error

泛型

  • 指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定具体类型的一种特性。
使用函数泛型
function createArray2<T>(value: T, count: number) {
  const arr: Array<T> = []
  for (let index = 0; index < count; index++) {
    arr.push(value)
  }
  return arr
}
const arr3 = createArray2<number>(11, 3)
console.log(arr3[0].toFixed())
// console.log(arr3[0].split('')) // error
const arr4 = createArray2<string>('aa', 3)
console.log(arr4[0].split(''))
// console.log(arr4[0].toFixed()) // error
多个泛型参数的函数
function swap<K, V>(a: K, b: V): [K, V] {
  return [a, b]
}
const result = swap<string, number>('abc', 123)
console.log(result[0].length, result[1].toFixed())
泛型接口
  • 在定义接口时, 为接口中的属性或方法定义泛型类型 在使用接口时, 再指定具体的泛型类型
interface IbaseCRUD<T> {
  data: T[]
  add: (t: T) => void
  getById: (id: number) => T
}

class User {
  id?: number //id主键自增
  name: string //姓名
  age: number //年龄

  constructor(name, age) {
    this.name = name
    this.age = age
  }
}

class UserCRUD implements IbaseCRUD<User> {
  data: User[] = []

  add(user: User): void {
    user = { ...user, id: Date.now() }
    this.data.push(user)
    console.log('保存user', user.id)
  }

  getById(id: number): User {
    return this.data.find(item => item.id === id)
  }
}

const userCRUD = new UserCRUD()
userCRUD.add(new User('tom', 12))
userCRUD.add(new User('tom2', 13))
console.log(userCRUD.data)
泛型类
  • 在定义类时, 为类中的属性或方法定义泛型类型 在创建类的实例时, 再指定特定的泛型类型
class GenericNumber<T> {
  zeroValue: T
  add: (x: T, y: T) => T
}

let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function(x, y) {
  return x + y
}

let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = 'abc'
myGenericString.add = function(x, y) {
  return x + y
}

console.log(myGenericString.add(myGenericString.zeroValue, 'test'))
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 12))
泛型约束
  • 如果我们直接对一个泛型参数取 length 属性, 会报错, 因为这个泛型根本就不知道它有这个属性
interface Lengthwise {
  length: number
}

// 指定泛型约束
function fn2<T extends Lengthwise>(x: T): void {
  console.log(x.length)
}
posted @ 2022-08-08 13:09  小学生学Web前端  阅读(95)  评论(0)    收藏  举报