Typescript 学习笔记

基础类型

定义数组

let list: number[] = [1, 2, 3] // 元素类型后面接上 []
// 或者 数组泛型
let list: Array<number> = [1, 2, 3]

元组类型

它允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。

let x: [string, number] // 数组长度为2,第一个为字符串,第二个是数字
// Initialize it
x = ['hello', 10] // OK
// Initialize it incorrectly
x = [10, 'hello'] // Error

当访问一个越界的元素,会使用联合类型替代

x[3] = 'world' // OK, 字符串可以赋值给(string | number)类型

console.log(x[5].toString()) // OK, 'string' 和 'number' 都有 toString

x[6] = true // Error, 布尔不是(string | number)类型

枚举

默认情况下,从 0 开始为元素编号 (数字枚举)

enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green

// 即
enum Color {
  Red = 0,
  Green = 1,
  Blue = 2,
}
let c: Color = Color.Green

也可以手动的指定成员的数值

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4,
}
let c: Color = Color.Green

反向映射:数字枚举类型提供的一个便利是你可以由枚举的值得到它的名字(可以把把数字 1 换成字符串“1”试试

enum Color {
  Red = 1,
  Green,
  Blue,
}
let colorName: string = Color[2]

console.log(colorName) // 显示'Green'因为上面代码里它的值是2

类型断言

let someValue: any = 'this is a string'

let strLength: number = (someValue as string).length

// 或者

let someValue: any = 'this is a string'

let strLength: number = (<string>someValue).length

接口

可选属性及额外的属性检查

// 绕过额外的属性检查
interface SquareConfig {
  color?: string
  width?: number
  [propName: string]: any
}

函数类型接口

interface SearchFunc {
  (source: string, subString: string): boolean
}

let mySearch: SearchFunc = (src: string, sub: string): boolean => {
  let result = src.search(sub)
  return result > -1
}

类类型

// 接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。
interface ClockInterface {
  currentTime: Date
  setTime(d: Date)
}

class Clock implements ClockInterface {
  currentTime: Date
  setTime(d: Date) {
    this.currentTime = d
  }
  constructor(h: number, m: number) {}
}

继承接口

interface Shape {
  color: string
}

interface PenStroke {
  penWidth: number
}

interface Square extends Shape, PenStroke {
  sideLength: number
}

let square = <Square>{}
// let square: Square = {}
square.color = 'blue'
square.sideLength = 10
square.penWidth = 5.0

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的 private 和 protected 成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。

class Control {
  private state: any
}

interface SelectableControl extends Control {
  select(): void
}

class Button extends Control implements SelectableControl {
  select() {}
}

class TextBox extends Control {
  select() {}
}

// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
  select() {}
}

class Location {}

基础例子

class Animal {
  name: string
  constructor(theName: string) {
    this.name = theName
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`)
  }
}

class Snake extends Animal {
  constructor(name: string) {
    super(name)
  }
  move(distanceInMeters = 5) {
    console.log('Slithering...')
    super.move(distanceInMeters)
  }
}

class Horse extends Animal {
  constructor(name: string) {
    super(name)
  }
  move(distanceInMeters = 45) {
    console.log('Galloping...')
    super.move(distanceInMeters)
  }
}

let sam = new Snake('Sammy the Python')
let tom: Animal = new Horse('Tommy the Palomino')

sam.move()
tom.move(34)

公共,私有与受保护的修饰符

当成员被标记成 private 时,它就不能在声明它的类的外部访问,值类也不行。
protected 修饰符与 private 修饰符的行为很相似,但有一点不同, protected 成员在派生类中仍然可以访问。

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) // 错误
class Person {
  protected name: string
  protected constructor(theName: string) {
    this.name = theName
  }
}

// Employee 能够继承 Person
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') // 错误: 'Person' 的构造函数是被保护的.

存取器

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)
}

只带有 get 不带有 set 的存取器自动被推断为 readonly

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract 关键字是用于定义抽象类和在抽象类内部定义抽象方法。

抽象类中的抽象方法不包含具体实现并且必须在派生类中实现

abstract class Department {
  constructor(public name: string) {}

  printName(): void {
    console.log('Department name: ' + this.name)
  }

  abstract printMeeting(): void // 必须在派生类中实现
}

class AccountingDepartment extends Department {
  constructor() {
    super('Accounting and Auditing') // 在派生类的构造函数中必须调用 super()
  }

  printMeeting(): void {
    console.log('The Accounting Department meets each Monday at 10am.')
  }

  generateReports(): void {
    console.log('Generating accounting reports...')
  }
}

let department: Department // 允许创建一个对抽象类型的引用
department = new Department() // 错误: 不能创建一个抽象类的实例
department = new AccountingDepartment() // 允许对一个抽象子类进行实例化和赋值
department.printName()
department.printMeeting()
department.generateReports() // 错误: 方法在声明的抽象类中不存在

泛型

泛型函数:给 identity 添加了类型变量 T。 T 帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T 当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。

function identity<T>(arg: T): T {
  return arg
}

// T 在定义函数的时候是一个代表类型的变量。这里调用的时候明确的指定了 T 是 string 类型,并做为一个参数传给函数,使用了 <> 括起来而不是 ()
let output = identity<string>('myString') // type of output will be 'string'
function identity<T, U>(value: T, message: U): T {
  console.log(message)
  return value
}

console.log(identity<number, string>(68, 'Semlinker'))
console.log(identity(68, 'Semlinker')) // 编译器自行推断

泛型接口

interface Identities<V, M> {
  value: V
  message: M
}

function identity<T, U>(value: T, message: U): Identities<T, U> {
  console.log(value + ': ' + typeof value)
  console.log(message + ': ' + typeof message)
  let identities: Identities<T, U> = {
    value,
    message,
  }
  return identities
}

console.log(identity(68, 'Semlinker'))

泛型类

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
}

泛型约束

确保属性存在

function identity<T>(arg: T[]): T[] {
  console.log(arg.length)
  return arg
}

// or
function identity<T>(arg: Array<T>): Array<T> {
  console.log(arg.length)
  return arg
}

检查对象上的键是否存在

首先了解 keyof 操作符。keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

interface Person {
  name: string
  age: number
  location: string
}

type K1 = keyof Person // "name" | "age" | "location"
type K2 = keyof Person[] // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person } // string | number
enum Difficulty {
  Easy,
  Intermediate,
  Hard,
}

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]
}

let tsInfo = {
  name: 'Typescript',
  supersetOf: 'Javascript',
  difficulty: Difficulty.Intermediate,
}

let difficulty: Difficulty = getProperty(tsInfo, 'difficulty') // OK

let supersetOf: string = getProperty(tsInfo, 'superset_of') // Error

泛型参数默认类型

interface A<T = string> {
  name: T
}

const strA: A = { name: 'Semlinker' }
const numB: A<number> = { name: 101 }

枚举

数字枚举

enum E {
  A = getSomeValue(),
  B, // error! 'A' is not constant-initialized, so 'B' needs an initializer
}

装饰器

待续……