TypeScript 基础部分

213 阅读8分钟

基础类型

相同部分:booleannumberstring[]objectnullundefined

不同部分:元组 Tuple、枚举类型 enum、任意类型 any、空类型 void、不存在值 never

// 元组类型允许表示一个已知元素数量和类型的数组
let x: [string, number];
// 枚举类型可以给状态赋予友好的名字
// disabled = -1,close = 0,open = 1
enum GatewayStatus {disabled = -1, close, open}
let shenzhen = {
    status: GatewayStatus.close // 正
};
shenzhen.status = GatewayStatus[1] // 反

变量声明

对let/const作用域校验

解构

解构表达式要尽量保持小而简单,否则就不要用

接口(契约)

额外属性检查

// 例子
interface SquareConfig {
    color?: string;
    width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}
let mySquare = createSquare({ colour: "red", width: 100 }); // TypeScript会认为这段代码可能存在bug
  1. 类型断言
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
  1. 字符串索引签名
interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}
  1. 赋值给一个另一个变量
let squareOptions = { colour: "red", width: 100 };
let mySquare = createSquare(squareOptions);

函数类型

// 包含参数列表和返回值类型的函数定义
interface SearchFunc {
  (source: string, subString: string): boolean;
}
// 函数的参数名不是必须与定义相同,函数参数不是必须声明类型,typescript会自行推断
let mySearch: SearchFunc;
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}

可索引类型

// 支持字符串和数字两种索引
// 数字索引返回值必须是字符串索引返回值类型的子类
class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}
interface NotOkay { // 错误示例
    [x: number]: Animal;
    [x: string]: Dog;
}
// 索引签名设置为只读,防止给索引赋值
interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!

类类型

// 类:implements是定义:类实例化出来的对象必须符合接口,而不是类本身符合接口
// 因此,接口只对其实例部分进行类型检查
interface ClockInterface {
    currentTime: Date;
}
class Clock implements ClockInterface {
    currentTime: Date; // new Clock返回的实例需要有这个属性才算类型符合
    constructor(h: number, m: number) { } // 构造器不会去检查
}

接口继承:更灵活地将接口分割到可重用的模块里

// 一个接口可以继承多个接口,创建出多个接口的合成接口
interface Shape {
    color: string;
}
interface PenStroke {
    penWidth: number;
}
interface Square extends Shape, PenStroke {
    sideLength: number;
}

混合类型:都怪JavaScript,一切皆对象

接口继承类

// Typescript也会校验继承到类的private和protected成员
// 未继承Control,但属于SelectableControl接口的类,需要实现Control的部分和SelectableControl的select方法
class Control {
    private state: any;
}
interface SelectableControl extends Control {
    select(): void; 
}
class Button extends Control implements SelectableControl {
    select() { }// 继承了Control,所以不必须写state
}
class Image implements SelectableControl {
    select() { }// 没有继承Control,所以必须写state
}

继承:派生类的构造函数必须调用 super

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

  1. TypeScript里,成员都默认为 public
  2. private:不能在声明它的类的外部访问
  3. 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() {
        // 这里使用了超类中的name
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}
// 构造函数也可以被标记成 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"); // 错误: 'Person' 的构造函数是被保护的.
  1. readonly:只读属性必须在声明时或构造函数里被初始化
class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}

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

静态属性

// 在内部使用类名.属性名访问属性
class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

抽象类

// 抽象类做为其它派生类的基类使用,不能直接实例化。 // 不同于接口,抽象类可以包含成员的实现细节
// abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法
abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

构造函数:类定义会创建类的实例类型和一个构造函数

把类当接口:因为类可以创建出类型

杂项

// 参数属性:声明和赋值合并
// 给构造函数参数前面添加一个访问限定符来声明,例:private,public,protected
class Octopus {
    constructor(readonly name: string) {
    }
}

// TypeScript使用的是结构性类型系统
// 当两种不同的类型,如果所有成员的类型都是兼容的,即可以互相赋值等
// 然而,当带有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; // 错误: Animal 与 Employee 不兼容.

函数

完整函数类型:参数类型和返回值类型

可选参数和默认参数:可选参数必须跟在必须参数后面

剩余参数

// 把剩余参数收集到一个变量
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

this

  1. typescript会推断上下文
  2. this参数:指定this的类型,出现在参数列表的最前面(假的参数)
  3. 回调函数的this参数:太复杂,还是用箭头函数做回调函数吧

重载

// 注意,function pickCard(x): any并不是重载列表的一部分,因此这里只有两个重载:一个是接收对象另一个接收数字。 以其它参数调用 pickCard会产生错误。
let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

泛型

例子

// T为泛型变量,明确的传入类型和返回值类型。
function identity<T>(arg: T): T {
    return arg;
}
// 使用1
identity<string>("myString");
// 使用2
identity("myString");

泛型类

// 泛型类指的是实例部分的类型
// 与泛型接口差不多,确认类的所有属性都在使用相同的类型。
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; };

泛型约束

// T的约束要求
interface Lengthwise {
    length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}
loggingIdentity(3);  // Error, number doesn't have a .length property

在泛型约束中使用类型参数

function getProperty(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.

在泛型里使用类类型

function create<T>(c: {new(): T; }): T {
    return new c();
}
class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

枚举

使用枚举我们可以定义一些带名字的常量(状态)

数字枚举:默认从0开始递增

字符串枚举:每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化,且没有反向映射了

异构枚举:混合字符串和数字

计算的和常量成员:能在编译阶段求值的表达式

联合枚举与枚举成员的类型:用不到,看不懂

类型推论

// 最佳通用类型
let x = [0, 1, null]; // number和null
let zoo = [new Rhino(), new Elephant(), new Snake()]; // 无
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()]; // 明确的指出类型Animal
// 上下文类型
window.onmousedown = function(mouseEvent) {
    console.log(mouseEvent.button);  //<- Error
};
window.onmousedown = function(mouseEvent: any) {
    console.log(mouseEvent.button);  //<- Now, no error is given
};

类型兼容性

赋值时的判断,兼容则可以赋值,否则不行

函数兼容

// 参数兼容
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error
// 返回值兼容
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});

x = y; // OK
y = x; // Error, because x() lacks a location property

函数参数双向协变:没看懂

可选参数及剩余参数

函数重载

枚举:不同枚举类型之间是不兼容的

类:类的私有成员和受保护成员会影响兼容性

泛型

// 参数的使用方法相同
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;

x = y;  // OK, because y matches structure of x
// 参数的使用方法不同
interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y;  // Error, because x and y are not compatible
// 若没指定泛型类型,则为正确