TypeScript入门

编译器安装

现阶段TypeScript需要被其编译器编译为Javascript文件才能被浏览器识别并执行,编译器的安装非常简单,在Node环境下执行下面的命令就行了。

$npm i -g typescript

检测是否安装成功

$tsc -v
Version 3.0.1

使用编译器

tsc --target es5 script.ts

数据类型

类型 说明 示例
Boolean let isDone: boolean = false;
Number 所有的值都是浮点值 let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
String 支持’’,"",``格式 let color: string = ‘red’;
Array let list: number[] = [1, 2, 3];
let list: Array = [1, 2, 3];
Tuple 元组,一种类似数组的数据结构,数组元素类型可以不同 let x: [string, number];
x = [‘abc’, 123];
Enum 值默认从0开始 enum Color {Red = 1, Green, Blue};
let c: Color = Color.Red;
Any 值为动态类型,主要是为了兼容引入的第三方JS库,防止编译时因为类型问题报错 let notSure: any = 4;
notSure = ‘abc’;
notSure = false;
Void function(): void {}
Null, Undefined 是其他类型的子类型,所以可以当作值赋给其他类型。
但是当 --strictNullChecks 时只能赋值给它们自己和void类型的变量
let n: null = null;
let u: undefined = undefined;
Never 表示函数永远都不会返回(抛异常) function error(message: string): never {throw new Error(message);}
Object 非基本类型的变量(number,string,boolean,symbol,null,undefined)都属于Object类型变量

类型断言

let someValue: any = 'this is a string';

let strLen: number = (<string> someValue).length;
// JSX中只允许下面这种方式
let strLen: number = (someValue as string).length;

变量声明

和Javascript ES6中的形式一致,支持constvarlet,大多数场景下TS推荐使用let

解构

数组

let input:Array<number> = [1, 2, 3];

let [first, second] = input;
console.log(first); // 1
console.log(second); // 2

// 忽略
let [, , third] = input;
console.log(third); //3

// 交换
[second, first] = [first, second];

// 函数参数
function f([first, second]: [number, string]): void {}

对象

let o = {a: 'foo', b: 12, c: 'bar'};

let {a, b} = o;
let {a, b} : {a: string, b: number} = o;

// 更改属性名称
let {a: newAName, b: newBName} = o;

// 默认值
function f(wholeObject: {a: string, b?: number}):void {
	let {a, b = 1001} = wholeObject;
}

// 方法参数
let C = {a: string, b?: number}
function f({a, b}:C):void {}

// 方法参数默认值
function f({a = '', b} = {b: ''}):void {}

分解

// 数组
let first = [1, 2];
let second = [3, 4];

let bothPlus = [0, ...first, ...second, 5];

// 对象
let defaults = {food: 'rice', price: '100$', ambiance: 'noisy'};

let search = {...defaults, food: 'rich'};

接口

ts中的interface用来约束对象和类。

声明方式

function printLabel(labelObj: {label: string}): void {
	console.log(labelObj.label);
}
interface LabelledValue {
	label: string;
}

function printLabel(labelObj: LabelledValue): void {
	console.log(labelObj.label);
}

// 允许对象拥有额外的属性
interface SquareConfig {
	color?: string;
	width?: number;
	[propName: string]: any;
}

约束方法类型

// 方法参数名称可以不一样
interface SearchFunc {
	(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string): boolean {}
mySearch = function(source, subString) {}

约束数组

interface StringArray {
	[index: number]: string; 
}

let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr = myArray[0];

约束类

interface ClockInterface {
	// 约束类的属性
	currentTime: Date;
	// 约束类的方法
	setTime(d: Date):void;
	// 约束类的构造函数
	new (h: number, m: number);
}

class Clock implements ClockInterface {
	currentTime: Date;
	
	constructor(h: number, m: number) {}
	
	setTime(d: Date):void {
		this.currentTime = d;
	}	
}
interface ClockConstructor {
	new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
	tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
	return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
	constructor(hour: number, minute: number) {}

	tick() {
		console.log("beep beep");
	}
}

class AnalogClock implements ClockInterface {
	constructor(hour: number, minute: number) {}

	tick() {
		console.log("tick tock");
	}
}

let digital = createClock(DigitalClock, 12, 18);
let analog = createClock(AnalogClock, 7, 32);

扩展接口 extends

interface Shape {
	color: string;
}

interface Length {
	width: number;
}

interface Square extends Shape, Length {
	sideLength: number;
}

let square = <Square>{};

square.color = "blue";
square.sideLength = 10;
class Control {
	private state: any;
}

interface SelectableControl extends Control {
	select(): void;
}

// Error: Property 'state' is missing in type 'Image'
class Image implements SelectableControl {
	select() {};
}

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

混合接口

interface Counter {
	(start: number): string;
	interval: number;
	reset(): void;
}

function getCounter(): Counter {
	let counter = <Counter>function(start: number){};
	counter.interval = 120;
	counter.reset = function() {};
	return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 50;

误区一:

class Animal {
	name: string;
}

class Dog extends Animal {
	breed: string;
}

interface NotOkay {
	[x: number]: Animal;
	[x: string]: Dog;
}

上面的代码会发生错误,由于JS使用number类型的Index访问属性时,是先将其转换为string类型的,这就和后面的String类型Index产生了冲突。解决方法是number类型的属性类型应该是string类型的属性类型的子类型。

正确的方式:

interface Okay {
	[x: number]: Dog;
	[x: string]: Animal;
}

可选属性 Optional

使用"?"标记

interface SquareConfig {
	color?: string;
	width: number;
}

只读属性

使用readonly标记。

interface Point {
	readonly x: number;
	readonly y: number;
}

使用ReadonlyArray<T>修饰只读数组属性。

let ro: ReadonlyArray<number> = [1, 2, 3];

ro[0] = 12; // error
ro.push(1); // error
ro.length = 1; // error
a = ro; // error

const 用于定义常量, readyonly用于定义属性。

类 Class

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return `hello,${this.greeting}`;
  }
}

let greeter = new Greeter("willsky");
console.log(greeter.greet());// hello,willsky

类的继承

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

class Snake extends Animal {
  constructor(name: string) {
    super(name); // TS规定子类构造函数必须有此语句
  }

  move(distance: number = 5) {
    console.log("Slithering...");
    super.move(distance);
  }
}

let sam = new Snake("Sammy the Python");
sam.move(); 
// Slithering...
// Sammy the Python moved 5m.

修饰符

关键字 说明 示例
public 默认修饰符 public name: string;
public move() {}
private 如果类的一个成员被private修饰,那么它只能在类中可见。 private number: string;
private move() {}
protected 自身和子类"中"可见。构造函数被其修饰表示该类不能被实例化但是可以被继承。 protected name: string;protected move() {}

getter、setter

当ts编译为ES5或更高版本的JS时,支持属性的getter、setter

tsc --target es5 script.ts

如果只有getter没有setter,则自动将属性设置为readonly

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) {
  console.log(employee.fullName);
}

静态属性

class Grid {
	// 直接使用类名进行访问
	static origin = {x: 0, y: 0};
}

抽象类

和Java的抽象类一个道理。

abstract class Animal {
	abstract makeSound(): void;
	move(): void {
		console.log("roaming the earth...");
	}
}

方法 function

TS 支持方法重载。

泛型 generic

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

let myIndentity: {<T>(arg: T): T} = identity;
interface GenericInterface<T> {
	(arg: T): T;
}

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

let myIdentity: GenericInterface<number> = identity;
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 loggingIdentity<T>(arg: T): T {
	console.log(arg.length); // Error: T doesn't have .length
	return arg;
}

interface Lengthwise {
	length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
	console.log(arg.length); // no Error
	return arg;
}

loggingIdentity(3); // Error, number doesn't have a .length property

loggingIdentity({length: 10, value: 3}); // no Error

枚举 enum

enum Direction {
	Up, // 从0开始
	Down, // 1
	Left, // 2
	Right, // 3
}
enum Direction {
	Up = 1,
	Down, // 2
	Left, // 3
	Right, // 4
}
enum Response {
	No, Yes,
}

function respond(message: Response): void {}
respond(Response.Yes);
enum E {
	A = f(),
	B = 0,
	C = "abc",
}

常量枚举

// 成员都是确定的,不能通过计算得来。
const enum Directions {
	Up, Down, Left, Right
}

Ambient enums

用来描述一个已经存在的枚举的结构。

和普通枚举的区别是:普通枚举成员如果没有初始化语句,则默认为常量成员;而Ambient enum 则默认该成员为动态成员(computed)。

declare enum Enum {
	A = 1, // const member
	B, // computed member
	C = 2	
}

TypeScript的类型推断

依据值类型进行推断。

类型兼容性

interface Named {
  name: string;
}

class Person {
  name: string;
}

let p: Named;

p = new Person();
interface Named {
	name: string;
}

let x: Named;
let y = {name: 'alice', location: 'seattle'};
x = y; // no Error
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // no Error
x = y; // Error
interface Empty<T> {}

let x: Empty<string>;
let y: Empty<boolean>;

y = x; // No Error, because x matches structure of y
interface NotEmpty<T> {
	data: T;
}

let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y; // Error, because x and y are not compatible
let identity = function<T>(x: T): T {}

let reverse = function<T>(y: U): T {}

identity = reverse; // Ok, because (x: any) => any  matches  (y: any) => any

高级类型

function extend<T, U>(first: T, second: U): T & U {
  let result = <T & U>{};

  for (let id in first) {
    (<any>result)[id] = (<any>first)[id];
  }

  for (let id in second) {
    if (!result.hasOwnProperty(id)) {
      (<any>result)[id] = (<any>second)[id];
    }
  }

  return result;
}

class Person {
  constructor(public name: string) {}
}

interface Loggable {
  log(): void;
}

class ConsoleLogger implements Loggable {
  log(): void {}
}

let jim = extend(new Person("jim"), new ConsoleLogger());
let n = jim.name;
jim.log();
function padLeft(value: string, padding: string | number) {
	// ...
}

let indentedString = padLeft("hello world", 1);
interface Bird {
	fly();
	layEggs();	
}

interface Fish {
	swim();
	layEggs();
}

function getSmallPet(): Fish | Bird {
	// ...
}

let pet = getSmallPet();
pet.layEggs(); // no Error
pet.swim(); // Error

Nullable

let s = "foo";
s = null; // Error: null is not assignable to 'string'

let sn: string | null = "bar";
sn = null; // no Error

sn = undefined; // Error: 'undefined' is not assignable to 'string | null'

###类型断言

function broken(name: string | null): string {
	return name.charAt(0) + '. the '; // Error: 'name' is possibly null
}

function fixed(name: string | null): string {
	return name!.charAt(0) + '. the ';// no Error
}

String Literal Types

type Easing = "ease-in" | "ease-out" | "ease-in-out";

class UIElement {
  animate(dx: number, dy: number, easing: Easing) {
    switch (easing) {
      case "ease-in":
        break;
      case "ease-out":
        break;
      case "ease-in-out":
        break;
      default:
    }
  }
}

Symbols

symbol在ES5规范中提出,与stringnumber类似是一个基本数据类型。

每个symbol都是唯一的,不管其初始化时的参数是多少。

let sym0 = Symbol();
let sym1 = Symbol("key");
let sym2 = Symbol("key");

sym1 === sym2; // false

symbol可以作为对象的属性key:

const sym = Symbol();
//let sym: any = Symbol();

let obj = {
	[sym]: "value"
}

console.log(obj[sym]); // "value"

迭代器和生成器

for…in, for…of

let a = [4, 5, 6];

for (let i in a) {
  console.log(i); // 0 1 2
}

for (let n of a) {
  console.log(n); // 4 5 6
}

模块 Module

和JS相同。

命名空间

从TypeScript 1.5开始,内部的模块被命名空间namespace所替代,外部模块不变。ES5环境下, module x {namespace x {等价。

namespace可以认为是模块内部的一种组织代码的模式。

namespace Validations {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }

  export class LetterOnlyValidtor implements StringValidator {
    isAcceptable(s) {
      return /^[A-Za-z]+$/.test(s);
    }
  }

  export class ZipCodeValidtor implements StringValidator {
    isAcceptable(s) {
      return /^[0-9]+$/.test(s);
    }
  }
}

let validators: { [s: string]: Validations.StringValidator } = {};
validators["ZIP Code"] = new Validations.ZipCodeValidtor();
validators["Letters Only"] = new Validations.LetterOnlyValidtor();

多个文件同一个命名空间

Validation.ts

namespace Validation {
  export interface StringValidator {
    isAcceptable(s: string): boolean;
  }
}

LettersOnlyValidator.ts

/// <reference path="Validation.ts" />
namespace Validation {
  export class LetterOnlyValidtor implements StringValidator {
    isAcceptable(s) {
      return /^[A-Za-z]+$/.test(s);
    }
  }
}

ZipCodeValidator.ts

/// <reference path="Validation.ts" />
namespace Validation {
  export class ZipCodeValidtor implements StringValidator {
    isAcceptable(s) {
      return /^[0-9]+$/.test(s);
    }
  }
}

test.ts

/// <reference path="Validation.ts" />
/// <reference path="LettersOnlyValidator.ts" />
/// <reference path="ZipCodeValidator.ts" />

let validators: { [s: string]: Validations.StringValidator } = {};
validators["ZIP Code"] = new Validations.ZipCodeValidtor();
validators["Letters Only"] = new Validations.LetterOnlyValidtor();