我为 Express 开了外挂

图片

本项目源码地址:https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Gist/LearnSource/OvernightDemo/html

随着 Nodejs 在前端涉及领域愈来愈广,也愈来愈成熟,相信不少朋友已经尝试或使用过 Nodejs 开发服务端项目了。 前端

本文我将和你们一块儿回顾 Express,而后介绍一个超级外挂——「OvernightJS」,它强大的地方在于,它将为 Express 路由提供 TypeScript 装饰器支持,使得咱们开发路由更加简单,代码复用性更好。 node

这里也但愿帮助你们对 TypeScript 的装饰器有更深了解。git

接下来跟本文主角 Leo 一块儿来看看这个外挂吧~es6

1、背景介绍

最近 Leo 打算使用 Express 来开始重构本身博客的服务端项目,通过认真研究和设计,并肯定完方案,Leo 开始下手啦:github

// app.ts

import express, { Application, Request, Response } from 'express';

const app: Application = express();

app.get('/'(req: Request, res: Response) => {
  res.send('Hello World!');
});

app.listen(3000()=> {
  console.log('Example app listening on port 3000!');
});

其中 tsconfig.json 配置以下:express

{
  "compilerOptions": {
    "target""es6",
    "module""commonjs",
    "strict"true,
  "esModuleInterop"true,
    "experimentalDecorators"true, // 开启装饰器
    "emitDecoratorMetadata"true,  // 开启元编程
    "skipLibCheck"true,
    "forceConsistentCasingInFileNames"true
  }
}

基本代码写完,测试能不能跑起来。 编程

Leo 在命令行使用 ts-node 命令行执行。(ts-node 用来直接运行 ts 文件,详细介绍请查看文档,这里不细讲咯):json

$ ts-node app.ts

看到命令行输出:api

Example app listening on port 3000!

服务跑起来了,心情愉快。

接下来 Leo 使用 Express 的路由方法写了其余接口:

// app.ts

app.get('/article', (req: Request, res: Response) => {res.send('Hello get!')});
app.post('/article', (req: Request, res: Response) => {res.send('Hello post!')});
app.put('/article', (req: Request, res: Response) => {res.send('Hello put!')});
app.delete('/article', (req: Request, res: Response) => {res.send('Hello delete!')});
app.get('/article/list', (req: Request, res: Response) => {res.send('Hello article/list!')});
// ... 等等其余接口

Express 路由方法派生自 HTTP 方法之一,附加到 express 类的实例。 支持对应于 HTTP 方法的如下路由方法:get、post、put、head、delete、options等等。

同事 Robin 看了看代码,问到:图片

随着接口越写越多,代码难免出现复杂和冗余的状况,为了解决这个问题,Leo 引入 Express 的 Router() ,来建立「可安装的模块化路由处理程序」。Router 实例是「完整的中间件」「路由系统」。所以,经常将其称为“「微型应用程序」”。

Leo 新建文件 app.router.ts ,从新实现上面接口:

// app.router.ts

import express, { Router, Request, Response } from 'express';
const router: Router = express.Router();

router.get('/'(req: Request, res: Response) => {res.send('Hello get!')});
router.post('/'(req: Request, res: Response) => {res.send('Hello post!')});
router.put('/'(req: Request, res: Response) => {res.send('Hello put!')});
router.delete('/'(req: Request, res: Response) => {res.send('Hello delete!')});
router.get('/user'(req: Request, res: Response) => {res.send('Hello api/user!')});

export default router;

接着在 app.ts 中使用,因为express.Router() 是个中间件,所以可使用 app.use() 来使用:

// app.ts

// 删除原来路由声明
import router from "../controller/app.router";
app.use('/api', router);

这里 app.use 第一个参数 /api 表示这一组路由对象的根路径,第二个参数 router 表示一组路由对象。

因而就实现了下面 API 接口:

  • /api
  • /api/user

肯定全部接口正常运行后,Leo 琢磨着,既然 Express 每一个路由都是由「路由名称」「路由处理方法」组成,那为何不能给 Express 加个外挂?为每一个路由添加装饰器来装饰。 

幸运的是,已经有大佬实现这个外挂了,它就是今天主角——OvernightJS。 

2、基础知识介绍

图片在开始介绍 Overnight 以前,咱们先回顾下“装饰器”和“Reflect”:

1. 装饰器

1.1 什么是装饰器?

TypeScript 中,装饰器(Decorators)是一种特殊类型的声明,它可以被附加到类声明、方法、访问符、属性或参数上,「本质上仍是个函数」。 

装饰器为咱们在类的声明及成员上经过「元编程语法」添加标注提供了一种方式。

须要记住这几点:

  • 装饰器是一个声明(表达式);
  • 该表达式被执行后,「返回一个函数」
  • 函数的入参分别为 targetname 和 descriptor
  • 执行该函数后,可能返回 descriptor 对象,用于配置 target对象;

更多装饰器详细介绍,请阅读文档《TypeScript 装饰器》(https://www.tslang.cn/docs/handbook/decorators.html)。

1.2 装饰器分类

装饰器通常包括:

  • 类装饰器(Class decorators);
  • 属性装饰器(Property decorators);
  • 方法装饰器(Method decorators);
  • 参数装饰器(Parameter decorators);

1.3 示例代码

这里以类装饰器(Class decorators)为例,介绍如何使用装饰器:

function MyDecorators(target: Function): void {
  target.prototype.say = function (): void {
    console.log("Hello 前端自习课!");
  };
}

@MyDecorators
class LeoClass {
  constructor() {}
  say(){console.log("Hello Leo")}
}

let leo = new LeoClass();
leo.say(); 
// 'Hello Leo!';

1.4 编译结果

装饰器实际上很是简单,编译出来之后,只是个函数,咱们接着看。 

这里以《1.3 示例代码》为例,看看它的编译结果:

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc{
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v{
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function"return Reflect.metadata(k, v);
};
function MyDecorators(target{
    target.prototype.say = function ({
        console.log("Hello 前端自习课!");
    };
}
let LeoClass = class LeoClass {
    constructor() { }
    say() { console.log("Hello Leo"); }
};
LeoClass = __decorate([
    MyDecorators,
    __metadata("design:paramtypes", [])
], LeoClass);
let leo = new LeoClass();
leo.say();
// 'Hello Leo!';

其实就是 __decorate 函数啦,具体你们能够自行细看咯~ 

从编译后 JS 代码中能够看出,「装饰器是在模块导入时便执行的」。以下:

LeoClass = __decorate([
    MyDecorators,
    __metadata("design:paramtypes", [])
], LeoClass);

1.5 小结

接下来经过下图来回顾装饰器的知识。图片

2. Reflect Metadata API

2.1 什么是 Reflect ?

Reflect(即反射)是 ES6 新增的一个「内置对象」,它提供用来「拦截和操做」 JavaScript 对象的 API。而且 「Reflect 的全部属性和方法都是静态的」,就像 Math 对象( Math.random() 等)。

更多 Reflect 详细介绍,请阅读文档《MDN Reflect》(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect)。

2.2 为何出现 Reflect?

其核心目的,是为了保持 JS 的简单,让咱们能够不用写不少代码,这里举个栗子

相关文章
相关标签/搜索