基本使用范例

基本写法对照表

TypescriptJavascript(ES6)说明
let isDone:boolean = truelet isDone = true定义布尔类型
let str:string = 'abc'let str = 'abc'
let num:number = 10let num = 10定义数字类型
let arr:number[] = [1, 2, 3]
let arrStr:string[] = ['a', 'b', 'c']

let arrX:Array= ['a', 'b', 'c']
let arr = [1, 2, 3]

let arrStr = ['a', 'b', 'c']
let arrX = ['a', 'b', 'c']
定义数组类型
let peopleInfo:[string, number] = ['male', 23]--定义元组类型(Tuple)
enum Direction { Up = 1, Down, Left, Right }--定义枚举类型(enum)
let s:any = 4--定义任意类型
let unusable:void = undefined--
let u:undefined = undefined

let n:null = null
let u = undefined

let n = null
never--never类型表示的是那些永不存在的值的类型

关于枚举类型的补充

enum Direction { Up, Down, Left, Right }
let up = Direction.Up
let down = Direction.Down
let left = Direction.Left
let right = Direction.Right
console.log(up, down, left, right)
// -> 0, 1, 2, 3

enum Direction { Up = 1, Down, Left, Right }
let up = Direction.Up
let down = Direction.Down
let left = Direction.Left
let right = Direction.Right
console.log(up, down, left, right)
// -> 1, 2, 3, 4

enum Direction { Up = 8, Down = 10, Left = 12, Right = 14 }
let up = Direction.Up
let down = Direction.Down
let left = Direction.Left
let right = Direction.Right
console.log(up, down, left, right)
// -> 8, 10, 12, 14

关于void和never的补充

// 没有返回值 返回个空
function warnUser():void {
  console.log("This is my warning message")
}

// 没有返回值,永远到不了
function error(message: string):never {
  throw new Error(message)
}

关于函数的基本用法

interface Person {
  name: string,
  age?: number // 可选属性
}

function getPersonName (person: Person): string {
  return person.name
}

const pName = getPersonName({ name: 'zhangsan', age: 20 })
console.log(pName)

const qName = getPersonName({ name: 'lisi' })
console.log(qName)

// 可选形参,无返回,且有以下三种写法
function add1 (x: number, y?: number): void {
  console.log(x, y)
}

const add2 = function (x: number, y?: number): void {
  console.log(x, y)
}

const add3 = (x: number, y?: number): void => {
  console.log(x, y)
}

总而言之,就是要明确的知道你传入的是什么,返回的是什么。不是心里知道,而是要通过代码体现出来。一般JS的规范就是要求你心里知道,记不住的写注释,TS从代码上体现出来,同时配合vscode这样的IDE和eslint语法检查,确实能很清楚知道输入的是什么,返回的是什么。

程序是写给人读的,只是偶尔让计算机执行一下。-- Donald Knuth

关于类的基本用法

interface AnimalInfo {
  name: string,
  age: number,
  gender: number,
  color?: string
}

// 父类
class Animal {
  // 静态方法
  static getTen (): number {
    return 10
  }

  // 属性
  // 公开属性
  public name: string
  // 受保护权限
  protected gender: number
  // 私有属性
  private age: number

  // 构造函数
  constructor (aniInfo: AnimalInfo) {
    this.name = aniInfo.name
    this.age = aniInfo.age
    this.gender = aniInfo.gender
  }

  // 方法
  getName (): string {
    return this.name
  }

  getAge (): number {
    return this.age
  }
}

// 继承了父类的方法和属性
class Dog extends Animal {
  color: string

  constructor (animaInfo: AnimalInfo) {
    super(animaInfo)
    this.color = animaInfo.color || ''
  }

  getColor (): string {
    return `${this.name}的颜色是: ${this.color}`
  }

  getGender (): number {
    return this.gender
  }
}

const jinMao = new Dog({
  name: '金毛',
  age: 2,
  gender: 1,
  color: '黄色'
})
// 调用父类方法
console.log(jinMao.getName()) // -> 金毛
// 显示属性
console.log(jinMao.getColor()) // -> 金毛的颜色是: 黄色
// 调用静态方法
console.log(Animal.getTen()) // -> 10
// 调用受保护的属性
console.log(jinMao.getGender()) // -> 1
// 调用私有属性
console.log(jinMao.getAge()) // -> 2

// 抽象类
abstract class Book {
  name: string

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

  getBookName (): void {
    console.log('Book name: ' + this.name)
  }

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

class JSBook extends Book {
  printBook (): void{
    console.log('This book is printing.')
  }
}
let book = new JSBook('Javascript')
book.getBookName()
book.printBook()

Interface的说明

interface Person {
  name: string,
  age?: number, // 可选属性
  readonly code: string // 只读 定义变量使用const,而属性使用readonly
}

const createPerson = (info: Person): string => {
  console.log(info.name)
  return 'creaeted'
}

const result = createPerson({
  name: 'zhangsan',
  age: 10,
  code: '430x'
})
console.log(result)

// 类与接口
// 门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它:

interface Alarm {
  alert: () => void;
}

class Door {
}

class SecurityDoor extends Door implements Alarm {
  alert () {
      console.log('SecurityDoor alert');
  }
}

class Car implements Alarm { // 实现多个接口 class Car implements Alarm, Light {
  alert () {
      console.log('Car alert');
  }
}

声明文件

声明文件必需以 .d.ts 为后缀。其实这个文件对于业务没有什么用,但是对TS有用。举个例子,项目中使用了第三方库,而这个第三方库没有ts版本。ts在编译过程中是不知道这个库是什么东西,所以需要显示的告诉ts,我知道我用的是什么,也知道自己在做什么。

现在大部分库都有自己的声明文件,在安装项目依赖,如 @types/koa ,就是在安装koa的声明文件。

大约是这么理解,有出入以后再补充。

理论上用.d.ts文件,是先需要有代码实现,然后才有声明文件,而且声明文件是为没有ts实现的js库而准备的。也可以说是为了欺骗编译器准备。告诉编译器我知道我在做什么,实际上只有天知道。

泛型

在定义接口、函数、类等的时候,无法确定其参数类型,等到调用的时候才能确定,这种场景就需要用到泛型。

场景1:传递进入什么类型返回什么类型

function returnAny<T> (arg: T): T {
  return arg;
}

const a = returnAny<number>(1);
const b = returnAny<string>('aa');
console.log(a, b); // -> 1, aa

场景2:传入一个数组,将其中互换位置

function swap<T, U> (tup: [T, U]): [U, T] {
  return [tup[1], tup[0]];
}
const c = swap(['a', 1]);
console.log(c); // -> [1, 'a']

场景3:动态输出object的value

const d = {
  method: '123',
  path: 'ss',
  action: (method: string, path: string): void => {
    console.log(method, path)
  }
};
Object.keys(d).forEach(key => {
  console.log(d[key])
})

上面这段写法移植到TS会报错:

  1. 元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{ method: string; path: string; action: (method: string, path: string) => void; }"。
  2. 在类型 "{ method: string; path: string; action: (method: string, path: string) => void; }" 上找不到具有类型为 "string" 的参数的索引签名。

用泛型可以解决,与场景一有点类似,但是更具体一些

const getProperty = <T, K extends keyof T>(o: T, name: K): T[K] => {
  return o[name];
}
Object.keys(d).forEach(key => {
  console.log(getProperty(d, key))
})

泛型约束

interface withLength {
  length: number
}
function Logger<T extends withLength> (arg:T): T {
  console.log(arg.length);
  return arg;
}
Logger('abc');
Logger(['a', 'b']);
Logger({length: 12});
// Logger(23); // 没有length不能传递进入

装饰器

使用装饰器需要 tsconfig.json  配置 experimentalDecorators  为true。 装饰器是对类、函数、属性之类的一种装饰,可以针对其添加一些额外的行为。通俗的理解可以认为就是在原有代码外层包装了一层处理逻辑。即使拿掉了也不影响原有的功能。

应用场景举例:

  1. 打logger
  2. 验证数据的正确性

简单的测试使用:

function log (target: any, name: string, descriptor: any) {
  console.log(target, name, descriptor)
  const _value = descriptor.value;
  // 改写方法 这里基本就可以做任何你想做的事情
  descriptor.value = function (x: number, y: number): number {
    console.log(`算式:${x} + ${y} = ${_value.apply(this, arguments)}`);
    return _value.apply(this, arguments)
  }
  return descriptor
}

class Foo {
  @log
  add (x: number, y: number): number {
    return x + y
  }
}

let x = new Foo()
let y = x.add(1, 2)
console.log(y) // -> 算式:1 + 2 = 3 -> 3

上面的例子有个问题,装饰器没有办法传参。可以使用装饰器工厂。

function configurable (value: boolean) {
  console.log(1, value)
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      descriptor.configurable = value;
      console.log(2, value)
  };
}

class Foo {
  @configurable(true)
  add (x: number, y: number): number {
    return x + y
  }
}

reflect-metadata

Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。关联 tsconfig.json 里配置 emitDecoratorMetadata 选项为 true 。安装npm i reflect-metadata --save

要弄清这个需要弄清ES6的 Reflect 和 Proxy