列举几个TypeScript的高级特性

136 阅读1分钟

1. 借助联合类型使用不同的类型

当某个函数只有一个参数,但期望该参数是某个类型或另一个类型时,就可以使用联合类型。 这种联合类型使用TypeScript提供的特殊运算符(并集): |

案例: 下面的方法判断输入值是否是数字, 参数只接受数字或字符串

isInt(value: string | number): boolean {
    if(typeof value === 'number') {
        return true
    }
    return false
}

2. 使用交叉类型组合类型

交叉类型由多个类型组合而成,具有各个类型的所有属性。 这种联合类型使用TypeScript提供的特殊运算符(交集): &

案例: 下面创建一个函数,使用两个类组合的交叉类型

class Grid {
    Width: number = 0;
    Height: number = 0;
    Padding: number = 0;
    constructor(padding: number) {
        this.Padding = padding;
    }
}

class Margin {
    Left : number = 0;
    Top : number = 0;
    Width : number = 10;
    Height : number = 20;
    Padding?: number;  //可选
}

function ConsolidatedGrid(grid: Grid, margin: Margin): Grid & Margin {
    let consolidatedGrid = <Grid & Margin>{...margin};
    consolidatedGrid.Left = margin.Left;
    consolidatedGrid.Top = margin.Top;
    consolidatedGrid.Width = margin.Width + grid.Width;
    consolidatedGrid.Height = margin.Height + grid.Height;
    consolidatedGrid.Padding = margin.Padding ? margin.Padding : grid.Padding;

    return consolidatedGrid;
}

let grid: Grid = <Grid>{Height: 20, Width: 10};
let x = ConsolidatedGrid(grid, <Margin>{Left: 5, Width: 5, Height: 5});

console.log(`Left : ${x.Left}, Top : ${x.Top}, Width : ${x.Width}, Height : ${x.Height}, Padding : ${x.Padding}`)
//输出: Left : 5, Top : undefined, Width : 15, Height : 25, Padding : undefined

3. 使用类型别名简化类型声明

类型别名常常与交叉类型和联合类型一起用。TypeScript允许创建类型别名,使我们不必使用 string | number | null 这样的引用来让代码变得杂乱不堪。编译器将把类型别名展开成为相应的代码。

我们来创建一个类型别名来代表联合类型 string | number :

type StringOrNumber = string | number;

创建更复杂的类型别名,添加对null的支持:

type NullableStringOrNumber = StringOrNumber | null;

4. 使用REST解构对象及处理可变数量的参数

在使用展开运算符构建对象的地方,也可以使用REST属性结构对象;当把对象内的属性或者数组内的元素给单独的变量时,就是在解构它们。

解构对象:

let staff = { name: 'jhon', age: 30, sex: 'male', department: 'Marketing Department'};

TypeScript 使用 JavaScript的简洁解构语法:

let { name, age, sex, department } = staff;

使用 REST解构:

let { name, otherinfo... } = staff; //info 包含 age, sex, department 的值
// 编译成ES5:
var name = staff.name, otherinfo = staff.otherinfo,  = __rest(staff, ["name", "otherinfo"]);
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};

REST解构数组:

const colors = ['red', 'blue', 'pink', 'white'];
let [red, blue, pink, white] = colors;
let [red, ...colorslice] = colors;
// 编译成ES5:
var red = colors[0], colorslice = colors.slice(1);

使用REST处理可变数量的参数

REST参数与REST属性并不相同,但是语法上非常相似;REST参数解决的是向函数传入可变数量的参数问题。

function PrintColors(log: string, ...colors: string[]): void {
    console.log(log);
    colors.forEach(color => {
        console.log(color);
    })
}

PrintColors('Color series', 'red', 'blue', 'pink', 'white');

因为 ES5不支持 REST参数,所以需要TypeScript编译生成件JavaScript模拟REST参数:


 // 编译成ES5:
function PrintColors(log) {
    var colors = [];
    for (var _i = 1; _i < arguments.length; _i++) {
        colors[_i - 1] = arguments[_i];
    }
    console.log(log);
    colors.forEach(function (color) {
        console.log(color);
    });
}
PrintColors('Color series', 'red', 'blue', 'pink', 'white');

在学习 TypeScript 过程中查看生成的JavaScript代码十分重要,TypeScript非常擅长隐藏复杂的处理,不让我们看到,经过查看JavaScript我们更好的理解TypeScript的工作机制。

5. 使用装饰器进行AOP

装饰器是作为一种实验性功能引入的,它是一段代码,可以在不修改类的内部实现的情况下,改变一个类的行为。 如果以前学习过Java或C#语言,你会发现装饰器和AOP技术很相似;AOP 技术允许我们切开一段代码并将其分离到另外一个位置, 从而提取出重复性代码。这意味着在我们的实现中,不必到处夹杂很大程度是样板代码,但是在运行应用程序时必须用到的一些代码。

因为装饰器是一个实验性特性,所以我们在启用装饰器要告诉 TypeScript,需要做配置:

tsc --target ES5 --experimentalDecorators
//也可以在tsconfig 文件中做如下设置:
"compilerOptions": {
    "target": "ES5",
    //other parameters...
    "experimentalDecorators": true
}

TypeScript 装饰器工厂是一个函数,它能够接受参数,并使这些参数返回实际的装饰器。

下面是一个在类中添加装饰器的示例:

let loginUser = { user: 'john', roles:[{role: 'user'}, { role: 'admin'}]};

//用于在装饰器函数里面做判读的方法
function IsRoleClass(role: string): boolean {
    return loginUser.roles.some(r => r.role === role);
}
//装饰器函数
function RoleClass(role: string) {
    return function(constructor: Function) {
        if(!IsRoleClass(role)) {
            throw new Error(`The user is not authorised to access this class`);
        }
    }
}

//添加装饰器 RoleClass
@RoleClass('admin')
class RestrictedClass {
    constructor() {
        console.log(`Inside the constructor`);
    }
    Validate(){
        console.log(`Validating`);
    }
}

TypeScript 装饰器 除了可以装饰类,还可以装饰构造函数和方法、还有装饰属性和访问器等,这里就不一一列举了。

6.使用混入(mixin)组成类型

混入其实就是一个函数,只不过这个函数接受一个类构造方法,而且返回一个类构造方法。 混入这种模式把行为和属性混合到类中。按照惯例,混入有以下特性: . 可以有状态(即实例属性) . 只能提供具体方法(与抽象方法相反) . 可以有构造方法,调用的顺序与混入类的顺序一致

TypeScript 没有内置混入的概念,不过我们可以自己动手轻易实现。 下面示例是在数据库中存储了人员记录,先创建一个Person基础类维护基础信息,再通过混入添加其他信息。

首先定义一个类型,用作混入的构造函数,可以为这个类型命名任何一个名称,但是针对TypeScript的混入,已经形成了一种约定,即下面的类型:

type Constructor<T ={}> = new(...args: any[]) => T;

//混入函数 RecordStatus
function RecordStatus<T extends Constructor>(base: T) {
    return class extends base {
        private delete: boolean = false;

        get Delete(): boolean {
            return this.delete;
        }
        Delete(): void {
            this.delete = true;
            console.log(`The record has been marked as deleted.`);
        }
    }
}

//混入函数 Timestamp, 记录上次更新的时间
function Timestamp<T extends Constructor>(base: T) {
    return class extends base {
        Updated: Date = new Date();
    }
}

//基础类
class Person{
  constructor(firstName: string, lastName: string) {
    this.FirstName = firstName;
    this.LastName = lastName;
  }

   FirstName: string;
   LastName: string;

}

//2个混入函数哪个放前面都可以
const ActivePerson = RecordStatus(Timestamp(Person));

let activePerson = new ActivePerson("Peter", "O'Hanlon");
activePerson.Updated = new Date();
activePerson.Delete();

console.log(`${activePerson.FirstName}`);
console.log(`${activePerson.LastName}`);
console.log(`${activePerson.Deleted}`);
console.log(`${activePerson.Updated}`);

7.使用泛型,将相同的代码用于不同的类型

泛型参数:在类型层面施加约束的占位类型,也称多态类型参数;T就像一个占位类型,类型检查器将根据上下文填充具体的类型。 泛型是 TypeScript 很重要的一环,这里涉及到函数多态的问题,后面小墨会出一篇文章专门讲解泛型。

下面我们看一下怎么使用泛型:

//不使用泛型,只能作用于number类型
class QueueOfInt {
    private queue: number[] = [];
    public Push(value: number): void {
        this.queue.push(value);
    }

    public Pop(): number | undefined {
        return this.queue.shift();
    }
}

const initQueue: QueueOfInt = new QueueOfInt();
initQueue.Push(10);
initQueue.Push(20);
console.log(intQueue.Pop()); // Prints 10
console.log(intQueue.Pop()); // Prints 20

//不使用泛型,只能作用于string类型
class QueueOfString {
    private queue : string[]= [];

    public Push(value : string) : void {
        this.queue.push(value);
    }

    public Pop() : string | undefined {
        return this.queue.shift();
    }
}

//使用泛型,实现了相同的代码用于不同的类型
class Queue<T> {
    private queue: T[] = [];

    public Push(value : T) : void {
        this.queue.push(value);
    }

    public Pop() : T | undefined {
        return this.queue.shift();
    }
}

console.log(`calling generic queue`);
const queue : Queue<number> = new Queue<number>();
const stringQueue : Queue<string> = new Queue<string>();
queue.Push(10);
queue.Push(20);
console.log(queue.Pop());
console.log(queue.Pop());
stringQueue.Push(`Hello`);
stringQueue.Push(`Generics`);
console.log(stringQueue.Pop());
console.log(stringQueue.Pop());

8. 使用Promise和async/await创建异步代码

我们常常需要编写以异步方式工作的代码,异步工作需要我们先启动一个任务,让它在后台运行, 同时去做另外一些工作。 ES6之后,JavaScript主要用Promise,加上async/await简洁的语法,实现异步工作流程。

有JavaScript基础的,对Promise都不陌生,我们在这就不做过多的赘述,在这我们主要看一下在TypeScript 使用异步方式与JavaScript 有啥区别。

其实区别也不大,基本上就是添加一些参数类型和返回类型约束,接下来我们看一下案例:

//调用某个web服务,有延时响应,用Promise实现模拟的异步操作

function ExpensiveWebCall(time : number): Promise<{}> {
    return new Promise((resolve, reject) => setTimeout(resolve, time));
}

class MyWebService {
    async CallExpensiveWebOperation() : Promise<void> {
        try {
            await ExpensiveWebCall(4000);
            console.log(`Finished web service`);
        } catch(error) {
            console.log(`Caught ${error}`);
        }
    }
}

console.log(`calling service`);
new MyWebService().CallExpensiveWebOperation();
console.log(`Processing continues until the web service returns`);

结语

在这里小墨只是列举了几个TypeScript的高级特性,其实 TypeScript还有像型变、类型拓宽、伴生对象、条件类型、类型断言、非空断言等高级特性,有兴趣的小伙伴可以继续去学习进阶,大家一起加油~