TypeScript入门手册

基础类型

https://www.tslang.cn/docs/handbook/basic-types.html

布尔值

let isDone: boolean = false;

数字

let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;

字符串

let name: string = "bob";

let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;

数组

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

元组 Tuple

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

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
x[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型

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

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

枚举

enum Color {Red, Green, Blue} // Red 默认为 0
let c: Color = Color.Green;
enum Color {Red = 1, Green, Blue} // Green -> 2 一次类推
let c: Color = Color.Green;
console.log(colorName);  // 显示'Green'因为上面代码里它的值是2
enum Color {Red = 1, Green = 2, Blue = 4} // 手动赋值
let c: Color = Color.Green;

Any

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
let list: any[] = [1, true, "free"];
list[1] = 100;

Void

function warnUser(): void {
    console.log("This is my warning message");
}
let unusable: void = undefined;

Null 和 Undefined

// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;

Never

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

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

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

Object

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 someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

变量声明

https://www.tslang.cn/docs/handbook/variable-declarations.html

var a = 10;

let hello = "Hello!";

const numLivesForCat = 9;

解构

解构数组

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
// swap variables
[first, second] = [second, first];
function f([first, second]: [number, number]) {
    console.log(first);
    console.log(second);
}
f(input);
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
let [, second, , fourth] = [1, 2, 3, 4];

对象解构

let o = {
    a: "foo",
    b: 12,
    c: "bar"
};
let { a, b } = o;
({ a, b } = { a: "baz", b: 101 });
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;

属性重命名

let { a: newName1, b: newName2 } = o;
// === 等价 ===
let newName1 = o.a;
let newName2 = o.b;
let {a, b}: {a: string, b: number} = o;

默认值

function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}

函数声明

type C = { a: string, b?: number }
function f({ a, b }: C): void {
    // ...
}
function f({ a="", b=0 } = {}): void {
    // ...
}
f();
function f({ a, b = 0 } = { a: "" }): void {
    // ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to {a: ""}, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument

展开

let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5]; // [0, 1, 2, 3, 4, 5]
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" }; // { food: "rich", price: "$$", ambiance: "noisy" }
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults }; // defaults里的food属性会重写food: "rich"

对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法:

class C {
  p = 12;
  m() {
  }
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!

接口

https://www.tslang.cn/docs/handbook/interfaces.html

TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

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

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

变形

interface LabelledValue {
  label: string;
}

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

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

可选属性

interface SquareConfig {
  color?: string; // 带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号。
  width?: number;
}

/*
可选属性的优势
    一、可以对可能存在的属性进行预定义
    二、可以捕获引用了不存在的属性时的错误。 
比如,我们故意将 createSquare里的color属性名拼错,就会得到一个错误提示
*/
function createSquare(config: SquareConfig): {color: string; area: number} {
  let newSquare = {color: "white", area: 100};
  if (config.clor) {
    // Error: Property 'clor' does not exist on type 'SquareConfig'
    newSquare.color = config.clor;
  }
  if (config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

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

只读属性

interface Point {
    /*
    一些对象属性只能在对象刚刚创建的时候修改其值。
    你可以在属性名前用 readonly来指定只读属性
    */
    readonly x: number;
    readonly y: number;
}
// TypeScript具有ReadonlyArray<T>类型,它与Array<T>相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
a = ro as number[]; // ok

额外的属性检查

函数类型

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

let mySearch: SearchFunc;
// 函数的参数名不需要与接口里定义的名字相匹配
// mySearch = function(source: string, subString: string) {
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}

可索引的类型

class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
    // TypeScript支持两种索引签名:字符串和数字。
    // 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
    [x: number]: Animal;
    [x: string]: Dog;
}
interface ReadonlyStringArray {
    // 可以将索引签名设置为只读,这样就防止了给索引赋值。
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

类类型

实现接口

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

// 接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员。
class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

类静态部分与实例部分的区别

// 定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 
interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface; // 静态部分
}
interface ClockInterface {
    tick(); // 实例部分
}
// 定义一个构造函数 createClock,它用传入的类型创建实例。
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    // 因为当一个类实现了一个接口时,只对其实例部分进行类型检查。
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}
// 因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 7, 32)里,会检查AnalogClock是否符合构造函数签名。
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

继承接口

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;

混合类型

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

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

// 一个对象可以同时做为函数和对象使用,并带有额外的属性。
let c = getCounter();
c(10);
c.reset();
c.interval = 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 {
}

作用域关键字

  • public 无限制访问(默认)
  • private 仅在本类中访问
  • protected 在派生类中仍然可以访问
// 抽象类
abstract class Department {
	
    constructor(public name: string) {
    }

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

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

// 继承
class AccountingDepartment extends Department {
    // 静态属性,可以通过AccountingDepartment.test来访问
	static test = 'test';
    
    private _departmentName: string;
    
    // 只读属性必须在声明时或构造函数里被初始化
    protected readonly departmentLogo: string = "★";
    
    constructor() {
        super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
    }
    
    // 参数属性test2
    constructor(private readonly test2: string) {
        super('Accounting and Auditing');
    }
    
    // 存取器
    get departmentName(): string {
        return this._departmentName
    }
    set departmentName(newName: string) {
        this._departmentName = newName;
    }

    // 实现抽象方法
    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(); // 错误: 方法在声明的抽象类中不存在