TypeScript使用小记

1.安装TypeScript命令行工具

TypeScript 的命令行工具安装:cnpm install -g typescript

编译TypeScript 文件:tsc index.ts

TypeScript 编写的文件以 .ts 为后缀

2.TypeScript数据类型

布尔值:

let isTrue: boolean = false

数字:

let dec: number = 6
let hex: number = 0xf00d
let bin: number = 0b1010
let oct: number = 0o744

字符串:

let name: string = "James Lee"
let sentence: string = `Hello, my name is ${name}.

Null和Undefined:

TypeScript 里,undefinednull两者各自有自己的类型分别叫做undefinednull。默认情况下nullundefined是所有类型的子类型。

let m: undefined = undefined
let n: null = null

对象:

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

数组:

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

枚举型:

使用枚举类型可以为一组数值赋予友好的名字

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

默认情况下,从0开始为元素编号。 也可以手动的指定成员的数值:

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4
}
let c: Color = Color.Green;
let d: string = Color[4]

任意值:

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用any类型来标记这些变量。在对现有代码进行改写的时候,any类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。

let num: any = 4
num = "James Lee" // OK
num = false // OK

只知道一部分数据的类型时,any类型也是有用的。 比如一个数组,它包含了不同的类型的数据:

let list: any[] = [1, true, "free"];
list[1] = 100;

空值:

void类型像是与any类型相反,表示没有任何类型。 当一个函数没有返回值时,其返回值类型是void

function warnUser(): void {
  alert("This is my warning message");
}

声明一个void类型的变量时,只能为它赋予undefinednull

let unusable: void = undefined

never:

never类型表示的是那些永不存在的值的类型。 never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使any也不可以赋值给never

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
  throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
  return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
  while (true) {}
}

元组tuple:

元组类型允许表示一个已知元素数量类型的数组,各元素的类型不必相同。 比如,定义一对值分别为stringnumber类型的元组。

let x: [string, number];
x = ["hello", 10]; // OK
x = [10, "hello"]; // Error

当访问一个已知索引的元素,会得到正确的类型:

console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error

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

x[3] = "world"; // OK, 字符串可以赋值给(string | number)类型
console.log(x[5].toString()); // OK
x[6] = true; // Error, 布尔不是(string | number)类型

类型断言

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式。 其一是“尖括号”语法:

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

另一个为as语法

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

两种形式是等价的。 然而,在 TypeScript 里使用 JSX 时,只有as语法断言是被允许的。

JavaScript 程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来说就不友好

class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  getName() {
    return "my name is  " + this.name;
  }
}

let user = new User("James Lee");

最后一行,使用new构造了User类的一个实例。 它会调用之前定义的构造函数,创建一个User类型的新对象,并执行构造函数初始化

继承

基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类。

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

class Dog extends Animal {
  bark() {
    console.log("wang! wang!");
  }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();
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);
  }
  //重写父类方法
  move(distance = 5) {
    console.log("Slithering...");
    super.move(distance);
  }
}

class Horse extends Animal {
  constructor(name: string) {
    super(name);
  }
  //重写父类方法
  move(distance = 45) {
    console.log("Galloping...");
    super.move(distance);
  }
}

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

sam.move();
tom.move(34)

Snake类和Horse类都创建了move方法,重写了从Animal继承来的move方法,使得move方法根据不同的类而具有不同的功能。

派生类包含了一个构造函数,必须调用super(),它会执行基类的构造函数。 而且,在构造函数里访问this的属性之前,我们一定要调用super()。 这个是 TypeScript 强制执行的一条重要规则。

修饰符

public

在 TypeScript 里,成员都默认为public

可以明确的将一个成员标记成public

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

private

当成员被标记成private时,就不能在的类的外部访问,用来封装成员变量

class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name; // error

TypeScript 使用的是结构性类型系统。 当比较两种不同的类型时,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

然而,当比较带有privateprotected成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个private成员,那么只有当另外一个类型中也存在这样一个private成员, 并且它们都是来自同一处声明时,这两个类型是兼容的。 对于protected成员也使用这个规则。

class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

class Rhino extends Animal {
  constructor() {
    super("Rhino");
  }
}

class Employee {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");

animal = rhino;
animal = employee; // error: Animal 与 Employee 不兼容.

protected:

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); // error

构造函数也可以被标记成protected, 但这个类不能在包含它的类外被实例化,但是能被继承:

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"); // error: 'Person' 的构造函数是被保护的.

readonly修饰符:

可以使用readonly关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化:

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor(theName: string) {
    this.name = theName;
  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name 是只读的.

参数属性

参数属性可以方便在一个地方定义并初始化一个成员,例如:

class Animal {
   //把声明和赋值合并至一处
  constructor(private name: string) {}
  move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

参数属性通过给构造函数参数添加一个访问限定符来声明。 使用private限定一个参数属性会声明并初始化一个私有成员;对于publicprotected来说也是一样

存取器

TypeScript 支持通过 getters/setters 来截取对对象成员的访问, 能够有效的控制对对象成员的访问

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

存取器要求将编译器设置为输出 ECMAScript 5 或更高。 不支持降级到 ECMAScript 3。 其次,只带有get不带有set的存取器自动被推断为readonly

接口

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 在 TypeScript 里,接口的作用就是为这些类型命名或为你的代码或第三方代码定义契约

//类型检查器会查看printLabel的调用
//printLabel有一个参数,要求这个对象参数有一个名为label类型为string的属性。
function printLabel(labelledObj: { label: string }) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

使用接口描述:必须包含一个label属性,类型为string

interface LabelledValue {
  label: string;
}

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

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

LabelledValue接口就像一个名字,用来描述上面例子里的要求,它代表了有一个label属性且类型为string的对象。 需要注意的是,这里并不能像在其它语言里一样,说传给printLabel的对象实现了这个接口。

类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以

可选属性:

接口里的属性不全都是必需的。 有些是只在某些条件下存在,或者根本不存在:

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

function createSquare(config: SquareConfig): { color: string; area: number } {
  let newSquare = { color: "white", area: 100 };
  if (config.color) {
    newSquare.color = config.color;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({ color: "black" });

带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号

可选属性的好处是可以对可能存在的属性进行预定义

只读属性:

一些对象属性只能在对象刚刚创建的时候修改其值。 可以在属性名前用readonly来指定只读属性:

interface Point {
  readonly x: number;
  readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!

额外的属性检查:

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

function createSquare(config: SquareConfig): { color: string; area: number } {
  // ...
}
//注意传入createSquare的参数拼写为colour而不是color
//TypeScript 会认为这段代码可能存在 bug。 对象字面量会被特殊对待而且会经过额外属性检查,当将它们赋值给变量或作为参数传递的时候
let mySquare = createSquare({ colour: "red", width: 100 })

绕开检查非常简单, 最简便的方法是使用类型断言:

let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);

最佳的方式是能够添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性:

interface SquareConfig {
  color?: string;
  width?: number;
  [propName: string]: any;
}

还有一种跳过这些检查的方式,它就是将这个对象赋值给一个另一个变量: 因为squareOptions不会经过额外属性检查,所以编译器不会报错

let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

在一段简单的代码里,不应该去绕开这些检查;对于包含方法和内部状态的复杂对象字面量来讲,可能需要使用这些技巧;如果遇到了额外类型检查出的错误,应该去审查一下类型声明

函数类型

接口能够描述 JavaScript 中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型

为了使用接口表示函数类型,需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型

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

创建一个函数类型的变量,并将一个同类型的函数赋值给这个变量:

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
};

对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配

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

函数的参数会被逐个进行检查,要求对应位置上的参数类型是兼容的。 如果没有指定类型,TypeScript 的类型系统会推断出参数类型,因为函数直接赋值给了SearchFunc类型变量。 函数的返回值类型是通过其返回值推断出来的(此例是falsetrue)。 如果让这个函数返回数字或字符串,类型检查器会警告我们函数的返回值类型与SearchFunc接口中的定义不匹配

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

类类型

与 Java 里接口的基本作用一样,TypeScript 也能够用它来明确的强制一个类去符合某种契约

interface ClockInterface {
  currentTime: Date;
}

class Clock implements ClockInterface {
  currentTime: Date;
  constructor(h: number, m: number) {}
}

在接口中描述一个方法,在类里实现它:

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 Square extends Shape {
  sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;

一个接口可以继承多个接口,创建出多个接口的合成接口:

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}
//多继承
interface Square extends Shape, PenStroke {
  sideLength: number;
}

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

//只能够是`Control`的子类们才能实现`SelectableControl`接口
// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
  select() {}
}

class Location {}

SelectableControl包含了Control的所有成员,包括私有成员state。 因为state是私有成员,所以只能够是Control的子类们才能实现SelectableControl接口。 因为只有Control的子类才能够拥有一个声明于Control的私有成员state,这对私有成员的兼容性是必需的。

Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上,SelectableControl就像Control一样,并拥有一个select方法。 ButtonTextBox类是SelectableControl的子类(因为它们都继承自Control并有select方法),但ImageLocation类并不是这样的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐